/*++

Copyright (c) 1989  Microsoft Corporation

Module Name:

    rxact.c

Abstract:

    This Module implements a simple transaction mechanism for registry
    database operations which helps eliminate the possibility of partial
    updates being made.  The cases covered are specifically partial updates
    caused by system crashes or program aborts during multiple-write updates.


    WARNING:  This package does not yet deal with full-disk problems
              automatically.  If a full disk is encountered during
              transaction commit, then manual interaction may be required
              to free enough space to complete the commit.  There is
              no means provided for backing out the commit.

Author:

    Jim Kelly       (JimK)     15-May-1991
    Robert Reichel  (RobertRe) 15-July-1992

Environment:

    Pure Runtime Library Routine

Revision History:



--*/


/*
////////////////////////////////////////////////////////////////////////////

High Level Description:

    The simple transaction mechanism expects the following to be true:

       (1) A single server is responsible for operations on an entire
           sub-tree of the registry database.  For example, the security
           account manager server (SAM) is responsible for everything
           below \REGISTRY\LOCAL_MACHINE\SECURITY\SAM.

       (2) Transactions on the sub-tree are serialized by the server
           responsible for the sub-tree.  That is, the server will not
           start a second user request until all previous user requests
           have been completed.

    The simple transaction mechanism helps eliminate the problem of partial
    updates caused by system crash or program abort during multiple-write
    updates to the registry database.  This is achieved by:

       (1) Keeping all actions in-memory until instructed to commit.  The
           presence of in-memory data structures implicitly indicates
           that a transaction is in progress.

           The initial state is no transaction in progress.

       (2) Providing a service which allows a server to initiate a transaction.
           This allocates in-memory data structures, thereby changing the
           state to transaction in progress.

       (3) Keeping a log of all keys in the sub-tree that are to be
           updated in a single transaction.  Each record in this log
           contains the following information:

                   (a) The name of the sub-key effected

                   (b) The operation to be performed on the sub-key
                       either DELETE or SET_VALUE.  Note that these
                       operations are idempotent and may be applied
                       again in the event that the server aborts during
                       an initial commit.

                   (c) The new value of the sub-key (if applicable)

                   (d) (optionally) The attribute name of the subkey
                       to be operated on.

                    (note that SET_VALUE is used to create new sub-keys
                     as well as updated existing ones).

            The entire list of sub-keys to be modified must be entered
            into this log before ANY of the sub-keys is actually modified.

       (4)  Providing a commit service that applies all changes indicated
            in the change log.  This is done by first writing the contents
            of the in-memory structures to a single key value ("Log") in
            the registry and flushing the data to disk.  The presence of
            the "Log" value and data imply that a commit is in progress.

            All necessary changes are applied, the "Log" value and its
            data are deleted, and in-memory data structres are freed,
            thereby changing the state to no-transaction.


    The package also includes a service which must be called upon server
    startup.  This service checks to make sure the state of the sub-tree
    is NO_TRANSACTION.  If it is not, then one of the actions below is
    performed based upon the current state of the sub-tree:

        COMMITTING - This means the server was previously aborted while
            a transaction was being committed (applied to the registry).
            In this case, the commit is performed again from the beginning
            of the change log.  After the commit is completed, the state
            of the sub-tree is set to NO_TRANSACTION.

////////////////////////////////////////////////////////////////////////////
*/



/*
////////////////////////////////////////////////////////////////////////////

Detailed Description:

    Registry State
    --------------

    The registry state of a subtree is kept in a sub-key of that tree
    named:

            "RXACT"

    The value field of that registry key includes a revision field.


    RXact Context
    -------------

    A call to RtlInitializeRXact will return a pointer to an
    RTL_RXACT_CONTEXT structure.  This structure contains:

    (1) the passed RootRegistryKey (eg, key to "Sam"),

    (2) a handle to the top of the RXact subtree (eg, key to
        "Sam\RXACT"),

    (3) a flag indicating if handles stored in the log are
        valid,

    (4) a pointer to the current RXactLog.

    The subsystem calling RtlInitializeRXact must keep this returned
    pointer and pass it back to RXact in all subsequent calls.


    Operation Log
    -------------

    The operation log of a registry sub-tree transaction is kept as sequence
    of "operation log entries".

    An in-memory log is a block of heap memory allocted by RtlStartRXact.
    It has a header which contains:

    (1) The count of operations in the log.

    (2) The maximum size of the log.

    (3) The amount of the log currently in use.

    The log data itself follows the header directly.


    Operation Log Entries
    ---------------------

    An operation log entry is described by the following structure:

    typedef struct _RXACT_LOG_ENTRY {
        ULONG LogEntrySize;
        RTL_RXACT_OPERATION Operation;
        UNICODE_STRING SubKeyName;       // Self-relativized (Buffer is really offset)
        UNICODE_STRING AttributeName;    // Self-relativized (Buffer is really offset)
        HANDLE KeyHandle;                // optional, not valid if read from disk.
        ULONG NewKeyValueType;
        ULONG NewKeyValueLength;
        PVOID NewKeyValue;               // Contains offset to data from start of log
    } RXACT_LOG_ENTRY, *PRXACT_LOG_ENTRY;

    The log entry contains all of the information passed in during a call
    to RtlAddActionToRXact or RtlAddAttributeActionToRXact.

    The UNICODE_STRING structures contain an offset to the string data
    rather than a pointer.  These offsets are relative to the start of
    the log data, and are adjusted in place as each log entry is commited.

    The KeyHandle is valid if it is not equal to INVALID_HANDLE_VALUE and
    if the HandlesValid flag in the RXactContext structure is TRUE.  This
    is so that we do not attempt to use the handles if the log has been
    read from disk after a reboot.


////////////////////////////////////////////////////////////////////////////
*/


#include "ntrtlp.h"

//
// Cannot include <windows.h> from kernel code
//
#define INVALID_HANDLE_VALUE (HANDLE)-1





///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//    Local Macros & Definitions                                             //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

//
// Revision level of a registry transaction .
//

#define RTLP_RXACT_REVISION1          (1l)
#define RTLP_RXACT_CURRENT_REVISION   RTLP_RXACT_REVISION1


#define RTLP_RXACT_KEY_NAME           L"RXACT"

#define RTLP_RXACT_LOG_NAME           L"Log"

#define RTLP_INITIAL_LOG_SIZE         0x4000

//
//  Given a value return its longword aligned equivalent value
//

#define DwordAlign(Value) (                       \
    (ULONG)((((ULONG)(Value)) + 3) & 0xfffffffc)  \
    )

//
// The value field of the RXACT registry key is one of the following data
// structures.
//

//
// The state of a registry sub-tree is one of the following:
//
//         RtlpRXactStateNoTransaction - There is not a transaction in progress.
//
//         RtlpRXactStateCommitting    - The actions of a transaction are being
//                                       applied to the registry database.
//

typedef enum _RTLP_RXACT_STATE {
    RtlpRXactStateNoTransaction = 2,
    RtlpRXactStateCommitting
} RTLP_RXACT_STATE, *PRTLP_RXACT_STATE;


typedef struct _RTLP_RXACT {
    ULONG Revision;
    RTLP_RXACT_STATE State;   // no longer used
    ULONG OperationCount;     // no longer used
} RTLP_RXACT, *PRTLP_RXACT;


typedef struct _RXACT_LOG_ENTRY {
    ULONG LogEntrySize;
    RTL_RXACT_OPERATION Operation;
    UNICODE_STRING SubKeyName;       // Self-relativized (Buffer is really offset)
    UNICODE_STRING AttributeName;    // Self-relativized (Buffer is really offset)
    HANDLE KeyHandle;                // optional, not valid if read from disk.
    ULONG NewKeyValueType;
    ULONG NewKeyValueLength;
    PVOID NewKeyValue;               // Contains offset to data from start of log
} RXACT_LOG_ENTRY, *PRXACT_LOG_ENTRY;




////////////////////////////////////////////////////////////////////////////////
//                                                                            //
//                      Prototypes for local procedures                       //
//                                                                            //
////////////////////////////////////////////////////////////////////////////////




NTSTATUS
RXactpCommit(
    IN PRTL_RXACT_CONTEXT RXactContext
    );

NTSTATUS
RXactpOpenTargetKey(
    IN HANDLE RootRegistryKey,
    IN RTL_RXACT_OPERATION Operation,
    IN PUNICODE_STRING SubKeyName,
    OUT PHANDLE TargetKey
    );



VOID
RXactInitializeContext(
    IN PRTL_RXACT_CONTEXT RXactContext,
    IN HANDLE RootRegistryKey,
    IN HANDLE RXactKey
    );


#if defined(ALLOC_PRAGMA) && defined(NTOS_KERNEL_RUNTIME)
#pragma alloc_text(PAGE,RXactpCommit)
#pragma alloc_text(PAGE,RXactpOpenTargetKey)
#pragma alloc_text(PAGE,RXactInitializeContext)
#pragma alloc_text(PAGE,RtlInitializeRXact)
#pragma alloc_text(PAGE,RtlStartRXact)
#pragma alloc_text(PAGE,RtlAbortRXact)
#pragma alloc_text(PAGE,RtlAddAttributeActionToRXact)
#pragma alloc_text(PAGE,RtlAddActionToRXact)
#pragma alloc_text(PAGE,RtlApplyRXact)
#pragma alloc_text(PAGE,RtlApplyRXactNoFlush)
#endif


///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//    Exported Procedures   (defined in ntrtl.h)                             //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////


NTSTATUS
RtlInitializeRXact(
    IN HANDLE RootRegistryKey,
    IN BOOLEAN CommitIfNecessary,
    OUT PRTL_RXACT_CONTEXT *RXactContext
    )

/*++

Routine Description:

    This routine should be called by a server exactly once when it starts.
    This routine will check to see that the registry transaction information
    exists for the specified registry sub-tree, and will create it if it
    doesn't exist.

Arguments:

    RootRegistryKey - A handle to the registry key within whose sub-tree
        a transaction is to be initialized.

    CommitIfNecessary - A BOOLEAN value indicating whether or not any
        previously aborted commit discovered should be commited at this
        time.  A value of TRUE indicates the commit should be applied
        if encountered.  A value of FALSE indicates a previously
        aborted COMMIT should not be committed at this time.

    RXactContext - Returns a pointer to an RTL_RXACT_CONTEXT structure
        allocated out of the local heap.  The caller must keep this
        pointer and pass it back in for all future RXact transactions
        for the passed RootRegistryKey.


Return Value:

    STATUS_SUCCESS - Indicates the transaction state already exists for the
        registry sub-tree and is already in the NO_TRANSACTION state.

    STATUS_UNKNOWN_REVISION - Indicates that a transaction state already
        exists for the specified sub-tree, but is a revision level that is
        unknown by this service.

    STATUS_RXACT_STATE_CREATED - This informational level status indicates
        that a specified registry sub-tree transaction state did not yet
        exist and had to be created.

    STATUS_RXACT_COMMIT_NECESSARY - This warning level status indicates that the
        transaction state already exists for the registry sub-tree, but that
        a transaction commit was previously aborted.  The commit has NOT been
        completed.  Another call to this service with a CommitIfNecessary value
        of TRUE may be used to commit the transaction.


    STATUS_RXACT_INVALID_STATE - Indicates that the transaction state
        of the registry sub-tree is incompatible with the requested operation.
        For example, a request to start a new transaction while one is already
        in progress, or a request to apply a transaction when one is not
        currently in progress.

--*/

{

    HANDLE RXactKey;
    LARGE_INTEGER LastWriteTime;
    NTSTATUS Status, TmpStatus;
    OBJECT_ATTRIBUTES RXactAttributes;
    PKEY_VALUE_FULL_INFORMATION FullInformation;
    RTLP_RXACT RXactKeyValue;
    UCHAR BasicInformation[128];      // Should be more than long enough
    ULONG Disposition;
    ULONG KeyValueLength;
    ULONG KeyValueType;
    ULONG ResultLength;
    UNICODE_STRING RXactKeyName;
    UNICODE_STRING ValueName;
    UNICODE_STRING NullName;

    RTL_PAGED_CODE();

    //
    // Initialize some stuff
    //

    KeyValueLength = (ULONG)sizeof( RTLP_RXACT );
    KeyValueType   = 0;         // Not used by RXact

    RtlInitUnicodeString( &NullName, NULL );

    //
    // Create or open the RXACT key.
    //

    RtlInitUnicodeString( &RXactKeyName, RTLP_RXACT_KEY_NAME);

    InitializeObjectAttributes(
        &RXactAttributes,
        &RXactKeyName,
        OBJ_CASE_INSENSITIVE | OBJ_OPENIF,
        RootRegistryKey,
        NULL);

//    Status = RtlpNtCreateKey(
//                 &RXactKey,
//                 (KEY_READ | KEY_WRITE | DELETE),
//                 &RXactAttributes,
//                 0,
//                 NULL,
//                 &Disposition
//                 );

    Status = NtCreateKey( &RXactKey,
                          (KEY_READ | KEY_WRITE | DELETE),
                          &RXactAttributes,
                          0,                          //TitleIndex
                          NULL,                       //Class OPTIONAL,
                          REG_OPTION_NON_VOLATILE,    //CreateOptions,
                          &Disposition
                          );

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

    //
    // Allocate the RXactContext block
    //

    *RXactContext = RtlAllocateHeap( RtlProcessHeap(), 0, sizeof( RTL_RXACT_CONTEXT ));

    if ( *RXactContext == NULL ) {

        //
        // Something prevented value assignment...
        // Get rid of the RXact key and return the error
        //

        TmpStatus = NtDeleteKey( RXactKey );
        ASSERT(NT_SUCCESS(TmpStatus)); //Safe to ignore, notify security group
        TmpStatus = NtClose( RXactKey );
        ASSERT(NT_SUCCESS(TmpStatus)); //Safe to ignore, notify security group

        return( STATUS_NO_MEMORY );
    }

    //
    // Initialize the newly created RXactContext structure.
    //

    RXactInitializeContext( *RXactContext, RootRegistryKey, RXactKey );

    //
    // If we created (as opposed to opened an existing) rxact key,
    // then we need to initialize it.
    //

    if ( Disposition == REG_CREATED_NEW_KEY ) {

        RXactKeyValue.Revision       = RTLP_RXACT_REVISION1;

        Status = NtSetValueKey( RXactKey,
                                &NullName,       // ValueName
                                0,               // TitleIndex
                                KeyValueType,
                                &RXactKeyValue,
                                KeyValueLength
                                );

        if ( !NT_SUCCESS(Status) ) {

            //
            // Something prevented value assignment...
            // Get rid of the RXact key and return the error
            //

            TmpStatus = NtDeleteKey( RXactKey );
            ASSERT(NT_SUCCESS(TmpStatus)); //Safe to ignore, notify security group
            TmpStatus = NtClose( RXactKey );
            ASSERT(NT_SUCCESS(TmpStatus)); //Safe to ignore, notify security group

            RtlFreeHeap( RtlProcessHeap(), 0, *RXactContext );

            return( Status );
        }

        return( STATUS_RXACT_STATE_CREATED );
    }



    //
    // We have opened an existing RXACT key.
    // See if it is a revision level we know about.
    //

    Status = RtlpNtQueryValueKey(
                 RXactKey,              // KeyHandle
                 &KeyValueType,         // KeyValueType
                 &RXactKeyValue,        // KeyValue
                 &KeyValueLength,       // KeyValueLength
                 &LastWriteTime         // LastWriteTime
                 );


    if ( !NT_SUCCESS(Status) ) {

        //
        // Something prevented value query...
        //

        TmpStatus = NtClose( RXactKey );
        ASSERT(NT_SUCCESS(TmpStatus)); //Safe to ignore, notify security group
        RtlFreeHeap( RtlProcessHeap(), 0, *RXactContext );
        return( Status );
    }


    if ( KeyValueLength != (ULONG)sizeof(RTLP_RXACT) ) {
        TmpStatus = NtClose( RXactKey );
        ASSERT(NT_SUCCESS(TmpStatus)); //Safe to ignore, notify security group
        RtlFreeHeap( RtlProcessHeap(), 0, *RXactContext );
        return( STATUS_UNKNOWN_REVISION );
    }

    if (RXactKeyValue.Revision != RTLP_RXACT_REVISION1) {
        TmpStatus = NtClose( RXactKey );
        ASSERT(NT_SUCCESS(TmpStatus)); //Safe to ignore, notify security group
        RtlFreeHeap( RtlProcessHeap(), 0, *RXactContext );
        return( STATUS_UNKNOWN_REVISION );
    }



    //
    // Right revision...
    // See if there is a transaction or commit in progress.  If not,
    // return success
    //

    //
    // If a log file exists, then we are committing.
    //

    RtlInitUnicodeString( &ValueName, RTLP_RXACT_LOG_NAME );

    Status = NtQueryValueKey(
                 RXactKey,
                 &ValueName,
                 KeyValueBasicInformation,
                 &BasicInformation,
                 128,
                 &ResultLength
                 );

    if ( NT_SUCCESS( Status )) {

        //
        // We found a value called 'Log'.  This means that a commit
        // was in progress.
        //

        if ( CommitIfNecessary ) {

            //
            // Query the full value of the log, then call a low level routine
            // to actually perform the commit.
            //

            Status = NtQueryValueKey(
                         RXactKey,
                         &ValueName,
                         KeyValueFullInformation,
                         NULL,
                         0,
                         &ResultLength
                         );

            if ( Status != STATUS_BUFFER_TOO_SMALL ) {
                return( Status );
            }

            FullInformation = RtlAllocateHeap( RtlProcessHeap(), 0, ResultLength );

            if ( FullInformation == NULL ) {
                return( STATUS_NO_MEMORY );
            }


            Status = NtQueryValueKey(
                         RXactKey,
                         &ValueName,
                         KeyValueFullInformation,
                         FullInformation,
                         ResultLength,
                         &ResultLength
                         );

            if ( !NT_SUCCESS( Status )) {

                RtlFreeHeap( RtlProcessHeap(), 0, FullInformation );
                RtlFreeHeap( RtlProcessHeap(), 0, *RXactContext );
                return( Status );
            }

            //
            // The log information is buried in the returned FullInformation
            // buffer.  Dig it out and make the RXactLog in the RXactContext
            // structure point to it.  Then commit.
            //

            (*RXactContext)->RXactLog = (PRTL_RXACT_LOG)((PCHAR)FullInformation + FullInformation->DataOffset);

            //
            // Don't use any handles we may find in the log file
            //

            (*RXactContext)->HandlesValid = FALSE;

            Status = RXactpCommit( *RXactContext );

            if ( !NT_SUCCESS( Status )) {

                RtlFreeHeap( RtlProcessHeap(), 0, FullInformation );
                RtlFreeHeap( RtlProcessHeap(), 0, *RXactContext );
                return( Status );
            }


            //
            // The commit was successful.  Clean up.
            // Delete the log file value and data
            //

            Status = NtDeleteValueKey( RXactKey, &ValueName );

            //
            // This should never fail
            //

            ASSERT( NT_SUCCESS( Status ));

            //
            // Get rid of the in memory data structures.  Abort
            // will free the RXactLog, so put what we want
            // freed in there and it will go away.
            //

            (*RXactContext)->RXactLog = (PRTL_RXACT_LOG)FullInformation;

            Status = RtlAbortRXact( *RXactContext );

            //
            // This should never fail
            //

            ASSERT( NT_SUCCESS( Status ));
            return( Status );
        } else {

            return( STATUS_RXACT_COMMIT_NECESSARY );
        }

    } else {

        //
        // No log, so nothing to do here.
        //

        return( STATUS_SUCCESS );
    }

}



VOID
RXactInitializeContext(
    IN PRTL_RXACT_CONTEXT RXactContext,
    IN HANDLE RootRegistryKey,
    IN HANDLE RXactKey
    )

/*++

Routine Description:

    Initializes an in-memory RXactContext structure.

Arguments:

    RXactContext - Supplies a pointer to an RXact Context created
        by RtlInitializeRXact.

    RootRegistryKey - Supplies the RootRegistryKey for this component.

    RXactKey - Supplies the {RootRegistryKey}\RXactKey for this component


Return Value:

    None.

--*/

{
    //
    // Initialize the RXactContext for this client
    //

    RXactContext->RootRegistryKey      = RootRegistryKey;
    RXactContext->HandlesValid         = TRUE;
    RXactContext->RXactLog             = NULL;
    RXactContext->RXactKey             = RXactKey;

    return;
}



NTSTATUS
RtlStartRXact(
    IN PRTL_RXACT_CONTEXT RXactContext
    )

/*++

Routine Description:

    This routine is used to start a new transaction in a registry sub-tree.
    Transactions must be serialized by the server so that only one transaction
    is in progress at a time.

Arguments:

    RXactContext - Supplies a pointer to an RTL_RXACT_CONTEXT structure
        that is not currently in use.

Return Value:

    STATUS_SUCCESS - Indicates the transaction was started.

    STATUS_RXACT_INVALID_STATE - Indicates that the transaction state
        of the registry sub-tree is incompatible with the requested operation.
        For example, a request to start a new transaction while one is already
        in progress, or a request to apply a transaction when one is not
        currently in progress.  This may also indicate that there is no
        transaction state at all for the specified registry sub-tree.

--*/
{
    PRTL_RXACT_LOG RXactLogHeader;

    RTL_PAGED_CODE();

    //
    // Allocate in-memory log file and initialize.  This implicitly
    // sets the state to 'transaction in progress'.
    //

    if ( RXactContext->RXactLog != NULL ) {

        //
        // There is already a transaction in progress for this
        // context.  Return an error.
        //

        return( STATUS_RXACT_INVALID_STATE );
    }

    RXactLogHeader = RtlAllocateHeap( RtlProcessHeap(), 0, RTLP_INITIAL_LOG_SIZE );

    if ( RXactLogHeader == NULL ) {
        return( STATUS_NO_MEMORY );
    }

    //
    // Fill in the log header information at the top of the
    // newly allocated buffer.
    //


    RXactLogHeader->OperationCount = 0;
    RXactLogHeader->LogSize        = RTLP_INITIAL_LOG_SIZE;
    RXactLogHeader->LogSizeInUse   = sizeof( RTL_RXACT_LOG );

    RXactContext->RXactLog = RXactLogHeader;

    return( STATUS_SUCCESS );

}


NTSTATUS
RtlAbortRXact(
    IN PRTL_RXACT_CONTEXT RXactContext
    )

/*++

Routine Description:

    This routine is used to abort a transaction in a registry sub-tree.

Arguments:

    RootRegistryKey - A handle to the registry key within whose sub-tree
        the transaction is to be aborted.

Return Value:

    STATUS_SUCCESS - Indicates the transaction was aborted.


    STATUS_UNKNOWN_REVISION - Indicates that a transaction state
        exists for the specified sub-tree, but has a revision level that is
        unknown by this service.


    STATUS_RXACT_INVALID_STATE - Indicates that the transaction state
        of the registry sub-tree is incompatible with the requested operation.
        For example, a request to start a new transaction while one is already
        in progress, or a request to apply a transaction when one is not
        currently in progress.  This may also indicate that there is no
        transaction state at all for the specified registry sub-tree.

--*/

{
    RTL_PAGED_CODE();

    if ( RXactContext->RXactLog == NULL ) {

        //
        // There is no transaction in progress for this
        // context.  Return an error.
        //

        return( STATUS_RXACT_INVALID_STATE );
    }

    (VOID) RtlFreeHeap( RtlProcessHeap(), 0, RXactContext->RXactLog );

    //
    // Reinitialize the RXactContext structure with the same initial data.
    //

    RXactInitializeContext(
        RXactContext,
        RXactContext->RootRegistryKey,
        RXactContext->RXactKey
        );


    return( STATUS_SUCCESS );

}



NTSTATUS
RtlAddAttributeActionToRXact(
    IN PRTL_RXACT_CONTEXT RXactContext,
    IN RTL_RXACT_OPERATION Operation,
    IN PUNICODE_STRING SubKeyName,
    IN HANDLE KeyHandle OPTIONAL,
    IN PUNICODE_STRING AttributeName,
    IN ULONG NewValueType,
    IN PVOID NewValue,
    IN ULONG NewValueLength
    )

/*++

Routine Description:

    This routine is used to add a new action to the transaction operation log.
    Upon commit, these operations are applied in the order they are added
    to the log.

    This routine differs from RtlAddActionToRXact in that it takes an Attribute
    Name parameter, rather than using the default ("NULL") Attribute of the
    specified key.


Arguments:

    RXactContext - Supplies a pointer to the RXactContext structure for this
        subsystem's root registry key.

    Operation - Indicates the type of operation to perform (e.g., delete
        a sub-key or set the value of a sub-key).  Sub-keys may be created
        by setting a value of a previously non-existent sub-key.  This will
        cause all sub-keys between the root and the specified sub-key to
        be created.

    SubKeyName - Specifies the name of the target registry key.  This name
        is relative to the Root of the Registry transaction sub-tree
        and must NOT start with a delimiter character ("\").

    KeyHandle - Optionally supplies a handle to the target key.  If
        not specified, the name passed for SubKeyName will determine
        the target key.

    AttributeName - Supplies the name of the key attribute to be
        modified.

    NewKeyValueType - (Optional) Contains the KeyValueType to assign
        to the target registry key.  This parameter is ignored if the
        Operation is not RtlRXactOperationSetValue.

    NewKeyValue -  (Optional) Points to a buffer containing the value
        to assign to the specified target registry key.  This parameter
        is ignored if the Operation is not RtlRXactOperationSetValue.

    NewKeyValueLength - Indicates the length (number of bytes) of the
        NewKeyValue buffer.  This parameter is ignored if the Operation
        is not RtlRXactOperationSetValue.


Return Value:

    STATUS_SUCCESS - Indicates the request completed successfully..

    STATUS_INVALID_PARAMETER - Indicates that an unknown Operation
        was requested.

    STATUS_NO_MEMORY - Insufficient memeory was available to complete
        this operation.

    STATUS_UNKNOWN_REVISION - Indicates that a transaction state
        exists for the specified sub-tree, but has a revision level that is
        unknown by this service.


--*/

{

    PRTL_RXACT_LOG   NewLog;
    PRXACT_LOG_ENTRY Base;

    ULONG End;
    ULONG LogEntrySize;
    ULONG NewLogSize;

    RTL_PAGED_CODE();

    //
    // Make sure we were passed a legitimate operation.
    //

    if (  (Operation != RtlRXactOperationDelete)  &&
          (Operation != RtlRXactOperationSetValue)   ) {
        return STATUS_INVALID_PARAMETER;
    }

    //
    // Compute the total size of the new data
    //

    LogEntrySize = sizeof( RXACT_LOG_ENTRY )               +
                   DwordAlign( SubKeyName->Length )        +
                   DwordAlign( AttributeName->Length )     +
                   DwordAlign( NewValueLength );

    LogEntrySize = ALIGN_UP( LogEntrySize, PVOID );

    //
    // Make sure there is enough space in the current
    // log file for this data.  If not, we must create
    // a larger log, copy all the old data, and then
    // append this to the end.
    //

    if ( RXactContext->RXactLog->LogSizeInUse + LogEntrySize >
                                   RXactContext->RXactLog->LogSize ) {

        //
        // We must allocate a bigger log file.
        //

        NewLogSize = RXactContext->RXactLog->LogSize;

        do {

            NewLogSize = NewLogSize * 2;

        } while ( NewLogSize <
            ( RXactContext->RXactLog->LogSizeInUse + LogEntrySize ) );

        NewLog = RtlAllocateHeap( RtlProcessHeap(), 0, NewLogSize );

        if ( NewLog == NULL ) {
            return( STATUS_NO_MEMORY );
        }

        //
        // Copy over previous information
        //

        RtlCopyMemory( NewLog, RXactContext->RXactLog, RXactContext->RXactLog->LogSizeInUse );

        //
        // Free the old log file
        //

        RtlFreeHeap( RtlProcessHeap(), 0, RXactContext->RXactLog );

        //
        // Install the new log file and adjust its size in its header
        //

        RXactContext->RXactLog = NewLog;
        RXactContext->RXactLog->LogSize = NewLogSize;
    }

    //
    // The log file is big enough, append data to
    // the end.
    //

    Base = (PRXACT_LOG_ENTRY)((PCHAR)(RXactContext->RXactLog) +
                             (RXactContext->RXactLog->LogSizeInUse));


    //
    // Append each parameter to the end of the log.  Unicode string data
    // will be appended to the end of the entry.  The Buffer field in the
    // Unicode string structure will contain the offset to the Buffer,
    // relative to the beginning of the log file.
    //

    Base->LogEntrySize      = LogEntrySize;
    Base->Operation         = Operation;
    Base->SubKeyName        = *SubKeyName;
    Base->AttributeName     = *AttributeName;
    Base->NewKeyValueType   = NewValueType;
    Base->NewKeyValueLength = NewValueLength;
    Base->KeyHandle         = KeyHandle;

    //
    // Fill in the variable length data: SubKeyName, AttributeName,
    // and NewKeyValue
    //

    //
    // End is an offset relative to the beginning of the entire log
    // structure.  It is initialized to 'point' to the offset immediately
    // following the structure we just filled in above.
    //

    End = (ULONG)((RXactContext->RXactLog->LogSizeInUse) +
                 sizeof( *Base ));


    //
    // Append SubKeyName information to the log file
    //

    RtlMoveMemory (
        (PCHAR)(RXactContext->RXactLog) + End,
        SubKeyName->Buffer,
        SubKeyName->Length
        );

    Base->SubKeyName.Buffer = (PWSTR)ULongToPtr(End);
    End += DwordAlign( SubKeyName->Length );



    //
    // Append AttributeName information to the log file
    //


    RtlMoveMemory(
        (PCHAR)(RXactContext->RXactLog) + End,
        AttributeName->Buffer,
        AttributeName->Length
        );

    Base->AttributeName.Buffer = (PWSTR)ULongToPtr(End);
    End += DwordAlign( AttributeName->Length );



    //
    // Append NewKeyValue information (if present) to the log file
    //

    if ( Operation == RtlRXactOperationSetValue ) {

        RtlMoveMemory(
            (PCHAR)(RXactContext->RXactLog) + End,
            NewValue,
            NewValueLength
            );

        Base->NewKeyValue = (PVOID)ULongToPtr(End);
        End += DwordAlign( NewValueLength );
    }

    End = ALIGN_UP( End, PVOID );

    RXactContext->RXactLog->LogSizeInUse = End;
    RXactContext->RXactLog->OperationCount++;

    //
    // We're done
    //

    return(STATUS_SUCCESS);
}


NTSTATUS
RtlAddActionToRXact(
    IN PRTL_RXACT_CONTEXT RXactContext,
    IN RTL_RXACT_OPERATION Operation,
    IN PUNICODE_STRING SubKeyName,
    IN ULONG NewKeyValueType,
    IN PVOID NewKeyValue OPTIONAL,
    IN ULONG NewKeyValueLength
    )

/*++

Routine Description:

    This routine is used to add a new action to the transaction operation log.
    Upon commit, these operations are applied in the order they are added
    to the log.

Arguments:

    RXactContext - Supplies a pointer to the RXactContext structure for this
        subsystem's root registry key.

    Operation - Indicates the type of operation to perform (e.g., delete
        a sub-key or set the value of a sub-key).  Sub-keys may be created
        by setting a value of a previously non-existent sub-key.  This will
        cause all sub-keys between the root and the specified sub-key to
        be created.

    SubKeyName - Specifies the name of the target registry key.  This name
        is relative to the Root of the Registry transaction sub-tree
        and must NOT start with a delimiter character ("\").

    NewKeyValueType - (Optional) Contains the KeyValueType to assign
        to the target registry key.  This parameter is ignored if the
        Operation is not RtlRXactOperationSetValue.

    NewKeyValue -  (Optional) Points to a buffer containing the value
        to assign to the specified target registry key.  This parameter
        is ignored if the Operation is not RtlRXactOperationSetValue.

    NewKeyValueLength - Indicates the length (number of bytes) of the
        NewKeyValue buffer.  This parameter is ignored if the Operation
        is not RtlRXactOperationSetValue.

Return Value:

    STATUS_SUCCESS - Indicates the request completed successfully..


    STATUS_UNKNOWN_REVISION - Indicates that a transaction state
        exists for the specified sub-tree, but has a revision level that is
        unknown by this service.

    Others - Other status values that may be returned from registry key
        services (such as STATUS_ACCESS_DENIED).

--*/
{
    UNICODE_STRING AttributeName;
    NTSTATUS Status;

    RTL_PAGED_CODE();

    RtlInitUnicodeString( &AttributeName, NULL );

    Status = RtlAddAttributeActionToRXact(
                 RXactContext,
                 Operation,
                 SubKeyName,
                 INVALID_HANDLE_VALUE,
                 &AttributeName,
                 NewKeyValueType,
                 NewKeyValue,
                 NewKeyValueLength
                 );

    return( Status );


}



NTSTATUS
RtlApplyRXact(
    IN PRTL_RXACT_CONTEXT RXactContext
    )

/*++

Routine Description:

    This routine is used to apply the changes of a registry sub-tree
    Transaction to that registry sub-tree.  This routine is meant to be
    called for the common case, where the hive is automatically
    lazy-flushed.  That means that this routine must write the change log
    to disk, then flush the hive (to ensure that pieces of changes aren't
    lazy-written to disk before this routine finishes an atomic operation),
    the apply the changes, then delete the change log.

    The actual changes will be lazy-written to disk, but the registry
    guarantees that none or all will make it.  If the machine goes down
    while this routine is executing, the flushed change log guarantees
    that the hive can be put into a consistent state.

Arguments:

    RXactContext - Supplies a pointer to the RXactContext structure for this
        subsystem's root registry key.

Return Value:

    STATUS_SUCCESS - Indicates the transaction was completed.

    STATUS_UNKNOWN_REVISION - Indicates that a transaction state
        exists for the specified sub-tree, but has a revision level that is
        unknown by this service.


    STATUS_RXACT_INVALID_STATE - Indicates that the transaction state
        of the registry sub-tree is incompatible with the requested operation.
        For example, a request to start a new transaction while one is already
        in progress, or a request to apply a transaction when one is not
        currently in progress.  This may also indicate that there is no
        transaction state at all for the specified registry sub-tree.


--*/
{
    NTSTATUS Status;
    UNICODE_STRING LogName;
    HANDLE RXactKey;

    RTL_PAGED_CODE();

    //
    // Commit the contents of the current log to disk
    //

    RXactKey = RXactContext->RXactKey;

    RtlInitUnicodeString( &LogName, RTLP_RXACT_LOG_NAME );

    Status = NtSetValueKey( RXactKey,
                            &LogName,        // ValueName
                            0,               // TitleIndex
                            REG_BINARY,
                            RXactContext->RXactLog,
                            RXactContext->RXactLog->LogSizeInUse
                            );

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

    Status = NtFlushKey( RXactKey );

    if ( !NT_SUCCESS( Status )) {

        //
        // If this fails, maintain the in-memory data,
        // but get rid of what we just tried to write
        // to disk.
        //
        // Ignore the error, since we're in a funky
        // state right now.
        //

        (VOID) NtDeleteValueKey( RXactKey, &LogName );

        return( Status );
    }

    //
    // The log is safe, now execute what is in it
    //

    Status = RXactpCommit( RXactContext );

    if ( !NT_SUCCESS( Status )) {

        //
        // As above, try to get rid of what's on
        // disk, leave the in-memory stuff alone,
        // so that the caller may try again.
        //

        (VOID) NtDeleteValueKey( RXactKey, &LogName );

        return( Status );
    }

    //
    // Delete the log file value and data
    //

    Status = NtDeleteValueKey( RXactKey, &LogName );

    //
    // This should never fail
    //

    ASSERT( NT_SUCCESS( Status ));

    //
    // Get rid of the in memory data structures.  Abort
    // does exactly what we want to do.
    //

    Status = RtlAbortRXact( RXactContext );

    //
    // This should never fail
    //

    ASSERT( NT_SUCCESS( Status ));

    return( STATUS_SUCCESS );

}



NTSTATUS
RtlApplyRXactNoFlush(
    IN PRTL_RXACT_CONTEXT RXactContext
    )

/*++

Routine Description:

    This routine is used to apply the changes of a registry sub-tree
    Transaction to that registry sub-tree.  This routine should only be
    called for special hives that do not have automatic lazy-flushing.
    The caller must decide when to flush the hive in order to guarantee
    a consistent hive.

Arguments:

    RXactContext - Supplies a pointer to the RXactContext structure for this
        subsystem's root registry key.

Return Value:

    STATUS_SUCCESS - Indicates the transaction was completed.

    STATUS_UNKNOWN_REVISION - Indicates that a transaction state
        exists for the specified sub-tree, but has a revision level that is
        unknown by this service.


    STATUS_RXACT_INVALID_STATE - Indicates that the transaction state
        of the registry sub-tree is incompatible with the requested operation.
        For example, a request to start a new transaction while one is already
        in progress, or a request to apply a transaction when one is not
        currently in progress.  This may also indicate that there is no
        transaction state at all for the specified registry sub-tree.


--*/
{
    NTSTATUS Status;

    RTL_PAGED_CODE();

    //
    // Execute the contents of the RXACT log.
    //

    Status = RXactpCommit( RXactContext );

    if ( NT_SUCCESS( Status ) ) {

        //
        // Get rid of the in memory data structures.  Abort
        // does exactly what we want to do.
        //

        Status = RtlAbortRXact( RXactContext );

        //
        // This should never fail
        //

        ASSERT( NT_SUCCESS( Status ));
    }

    return( Status );

}



///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//    Internal Procedures   (defined in within this file)                    //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////





NTSTATUS
RXactpCommit(
    IN PRTL_RXACT_CONTEXT RXactContext
    )

/*++

Routine Description:

    This routine commits the operations in the operation log.

    When all changes have been applied, the transaction state
    is changed to NO_TRANSACTION.

Arguments:

    RXactContext - Supplies a pointer to the RXactContext structure for this
        subsystem's root registry key.

Return Value:

    STATUS_SUCCESS - Indicates the transaction was completed.



--*/
{
    BOOLEAN HandlesValid;

    HANDLE TargetKey;
    HANDLE RXactKey;
    HANDLE RootRegistryKey;

    PRTL_RXACT_LOG      RXactLog;
    PRXACT_LOG_ENTRY    RXactLogEntry;
    RTL_RXACT_OPERATION Operation;

    ULONG OperationCount;
    ULONG i;

    NTSTATUS Status = STATUS_SUCCESS;
    NTSTATUS TmpStatus = STATUS_SUCCESS;
    BOOLEAN CloseTargetKey;

    //
    // Extract information from the RXactContext to simplify
    // the code that follows
    //

    RootRegistryKey = RXactContext->RootRegistryKey;
    RXactKey        = RXactContext->RXactKey;
    RXactLog        = RXactContext->RXactLog;

    OperationCount  = RXactLog->OperationCount;

    HandlesValid    = RXactContext->HandlesValid;


    //
    // Keep a pointer to the beginning of the current log entry.
    //

    RXactLogEntry = (PRXACT_LOG_ENTRY)((PCHAR)RXactLog + sizeof( RTL_RXACT_LOG ));


    //
    // Go through and perform each operation log.  Notice that some operation
    // logs may already have been deleted by a previous commit attempt.
    // So, don't get alarmed if we don't successfully open some operation
    // log entry keys.
    //

    for ( i=0 ; i<OperationCount ; i++ ) {

        //
        // Turn the self-relative offsets in the structure
        // back into real pointers.
        //

        RXactLogEntry->SubKeyName.Buffer = (PWSTR) ((PCHAR)RXactLogEntry->SubKeyName.Buffer +
                                                    (ULONG_PTR)RXactLog);

        RXactLogEntry->AttributeName.Buffer = (PWSTR) ((PCHAR)RXactLogEntry->AttributeName.Buffer +
                                                       (ULONG_PTR)RXactLog);

        RXactLogEntry->NewKeyValue = (PVOID)((PCHAR)RXactLogEntry->NewKeyValue + (ULONG_PTR)RXactLog);

        Operation = RXactLogEntry->Operation;

        //
        // Perform this operation
        //

        switch (Operation) {
            case RtlRXactOperationDelete:

                //
                // Open the target key and delete it.
                // The name is relative to the RootRegistryKey.
                //

                if ( ((RXactLogEntry->KeyHandle == INVALID_HANDLE_VALUE) || !HandlesValid) ) {

                    Status = RXactpOpenTargetKey(
                                 RootRegistryKey,
                                 RtlRXactOperationDelete,
                                 &RXactLogEntry->SubKeyName,
                                 &TargetKey
                                 );

                    if ( !NT_SUCCESS(Status)) {

                        //
                        // We must allow the object not to be found,
                        // because we may be replaying this log after
                        // it had been partially executed.
                        //

                        if ( Status != STATUS_OBJECT_NAME_NOT_FOUND ) {

                            return( Status );

                        } else {

                            break;
                        }
                    }

                    CloseTargetKey = TRUE;

                } else {

                    TargetKey = RXactLogEntry->KeyHandle;
                    CloseTargetKey = FALSE;
                }


                //
                // If this fails, then it is an error
                // because the key should exist at
                // this point.
                //

                Status = NtDeleteKey( TargetKey );


                //
                // Only close the target key if we opened it
                //

                if ( CloseTargetKey ) {

                    TmpStatus = NtClose( TargetKey );

                    //
                    // If we opened this handle, then we should
                    // be able to close it, whether it has been
                    // deleted or not.
                    //

                    ASSERT(NT_SUCCESS(TmpStatus));        // safe to ignore, but curious...
                }


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

                break;

            case RtlRXactOperationSetValue:

                //
                // Open the target key.
                // The name is relative to the RootRegistryKey.
                //

                if ( ((RXactLogEntry->KeyHandle == INVALID_HANDLE_VALUE) || !HandlesValid) ) {

                    Status = RXactpOpenTargetKey(
                                 RootRegistryKey,
                                 RtlRXactOperationSetValue,
                                 &RXactLogEntry->SubKeyName,
                                 &TargetKey
                                 );

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

                    CloseTargetKey = TRUE;

                } else {

                    TargetKey = RXactLogEntry->KeyHandle;
                    CloseTargetKey = FALSE;
                }

                //
                // Assign to the target key's new value
                //

                Status = NtSetValueKey( TargetKey,
                                        &RXactLogEntry->AttributeName,
                                        0,               // TitleIndex
                                        RXactLogEntry->NewKeyValueType,
                                        RXactLogEntry->NewKeyValue,
                                        RXactLogEntry->NewKeyValueLength
                                        );

                //
                // Only close the target key if we opened it
                //

                if ( CloseTargetKey ) {

                    TmpStatus = NtClose( TargetKey );
                    ASSERT(NT_SUCCESS(TmpStatus));        // safe to ignore, but curious...

                }

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

                break;



            default:

                //
                // Unknown operation type.  This should never happen.
                //

                ASSERT( FALSE );

                return(STATUS_INVALID_PARAMETER);

        }

        RXactLogEntry = (PRXACT_LOG_ENTRY)((PCHAR)RXactLogEntry + RXactLogEntry->LogEntrySize);

    }

    //
    // Commit complete
    //

    return( STATUS_SUCCESS );

}




NTSTATUS
RXactpOpenTargetKey(
    IN HANDLE RootRegistryKey,
    IN RTL_RXACT_OPERATION Operation,
    IN PUNICODE_STRING SubKeyName,
    OUT PHANDLE TargetKey
    )

/*++

Routine Description:

    This routine opens the target registry key of an operation.

Arguments:

    RootRegistryKey - A handle to the registry key within whose sub-tree
        a transaction is to be initialized.

    Operation - Indicates what operation is to be performed on the target.
        This will effect how the target is opened.

    OperationNameKey - A handle to the operation log sub-key
        containing the name of the target registry key.

    TargetKey - Receives a handle to the target registry key.

Return Value:

    STATUS_SUCCESS - Indicates the operation log entry was opened.

    STATUS_NO_MEMORY - Ran out of heap.


--*/
{

    NTSTATUS Status;
    OBJECT_ATTRIBUTES TargetKeyAttributes;
    ACCESS_MASK DesiredAccess;
    ULONG Disposition;


    if (Operation == RtlRXactOperationDelete) {

        DesiredAccess = DELETE;

        InitializeObjectAttributes(
            &TargetKeyAttributes,
            SubKeyName,
            OBJ_CASE_INSENSITIVE,
            RootRegistryKey,
            NULL);

//        Status = RtlpNtOpenKey(
//                     TargetKey,
//                     DesiredAccess,
//                     &TargetKeyAttributes,
//                     0);

        Status = NtOpenKey( TargetKey,
                            DesiredAccess,
                            &TargetKeyAttributes
                            );


    } else if (Operation == RtlRXactOperationSetValue) {

        DesiredAccess = KEY_WRITE;

        InitializeObjectAttributes(
            &TargetKeyAttributes,
            SubKeyName,
            OBJ_CASE_INSENSITIVE | OBJ_OPENIF,
            RootRegistryKey,
            NULL);

        Status = NtCreateKey(
                     TargetKey,
                     DesiredAccess,
                     &TargetKeyAttributes,
                     0,
                     NULL,
                     REG_OPTION_NON_VOLATILE,
                     &Disposition
                     );

    } else {
        return STATUS_INVALID_PARAMETER;
    }



    return( Status );

}



//NTSTATUS
//RXactpAssignTargetValue(
//    IN PVOID NewKeyValue,
//    IN ULONG NewKeyValueLength,
//    IN ULONG NewKeyValueType,
//    IN HANDLE TargetKey,
//    IN PUNICODE_STRING AttributeName
//    );


//NTSTATUS
//RXactpAssignTargetValue(
//    IN PVOID NewKeyValue,
//    IN ULONG NewKeyValueLength,
//    IN ULONG NewKeyValueType,
//    IN HANDLE TargetKey,
//    IN PUNICODE_STRING AttributeName
//    )
//
///*++
//
//Routine Description:
//
//    This routine copies the value of an operation log entry to its
//    corresponding target key.  The target key must already be open.
//
//Arguments:
//
//    NewKeyValue - The new value for the key being modified.
//
//    NewKeyValueLength - The size in bytes of the new value information.
//
//    NewKeyValueType - The type of the data for the new key.
//
//    TargetKey - A handle to the target registry key.
//
//    AttributeName - Supplies the name of the key attribute being edited.
//
//Return Value:
//
//    STATUS_SUCCESS - Indicates the value was successfully applied to
//        the target registry key.
//
//    STATUS_NO_MEMORY - ran out of heap.
//
//
//--*/
//{
//    NTSTATUS Status;
//
//    //
//    // Now apply the value to the target key
//    //
//    // Even if there is no key value, we need to do the assign so that
//    // the key value type is assigned.
//    //
//
//    Status = NtSetValueKey( TargetKey,
//                            AttributeName,
//                            0,               // TitleIndex
//                            NewKeyValueType,
//                            NewKeyValue,
//                            NewKeyValueLength
//                            );
//
//
//    return( Status );
//}