--- /dev/null
+/*
+ * COPYRIGHT: See COPYING in the top level directory
+ * PROJECT: ReactOS system libraries
+ * FILE: lib/rtl/rxact.c
+ * PURPOSE: Registry Transaction API
+ * PROGRAMMERS: Timo Kreuzer (timo.kreuzer@reactos.org)
+ */
+
+/* INCLUDES *****************************************************************/
+
+#include <rtl.h>
+#include <ndk/cmfuncs.h>
+
+#define NDEBUG
+#include <debug.h>
+
+#define RXACT_DEFAULT_BUFFER_SIZE (4 * PAGE_SIZE)
+
+typedef struct _RXACT_INFO
+{
+ ULONG Revision;
+ ULONG Unknown1;
+ ULONG Unknown2;
+} RXACT_INFO, *PRXACT_INFO;
+
+typedef struct _RXACT_DATA
+{
+ ULONG ActionCount;
+ ULONG BufferSize;
+ ULONG CurrentSize;
+} RXACT_DATA, *PRXACT_DATA;
+
+typedef struct _RXACT_CONTEXT
+{
+ HANDLE RootDirectory;
+ HANDLE KeyHandle;
+ BOOLEAN CanUseHandles;
+ PRXACT_DATA Data;
+} RXACT_CONTEXT, *PRXACT_CONTEXT;
+
+typedef struct _RXACT_ACTION
+{
+ ULONG Size;
+ ULONG Type;
+ UNICODE_STRING KeyName;
+ UNICODE_STRING ValueName;
+ HANDLE KeyHandle;
+ ULONG ValueType;
+ ULONG ValueDataSize;
+ PVOID ValueData;
+} RXACT_ACTION, *PRXACT_ACTION;
+
+enum
+{
+ RXactDeleteKey = 1,
+ RXactSetValueKey = 2,
+};
+
+#define ALIGN_UP_BY ROUND_UP
+
+/* FUNCTIONS *****************************************************************/
+
+static
+VOID
+NTAPI
+RXactInitializeContext(
+ PRXACT_CONTEXT Context,
+ HANDLE RootDirectory,
+ HANDLE KeyHandle)
+{
+ Context->Data = NULL;
+ Context->RootDirectory = RootDirectory;
+ Context->CanUseHandles = TRUE;
+ Context->KeyHandle = KeyHandle;
+}
+
+static
+NTSTATUS
+NTAPI
+RXactpOpenTargetKey(
+ HANDLE RootDirectory,
+ ULONG ActionType,
+ PUNICODE_STRING KeyName,
+ PHANDLE KeyHandle)
+{
+ NTSTATUS Status;
+ ULONG Disposition;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+
+ /* Check what kind of action this is */
+ if (ActionType == RXactDeleteKey)
+ {
+ /* This is a delete, so open the key for delete */
+ InitializeObjectAttributes(&ObjectAttributes,
+ KeyName,
+ OBJ_CASE_INSENSITIVE,
+ RootDirectory,
+ NULL);
+ Status = ZwOpenKey(KeyHandle, DELETE, &ObjectAttributes);
+ }
+ else if (ActionType == RXactSetValueKey)
+ {
+ /* This is a create, so open or create with write access */
+ InitializeObjectAttributes(&ObjectAttributes,
+ KeyName,
+ OBJ_CASE_INSENSITIVE | OBJ_OPENIF,
+ RootDirectory,
+ NULL);
+ Status = ZwCreateKey(KeyHandle,
+ KEY_WRITE,
+ &ObjectAttributes,
+ 0,
+ NULL,
+ 0,
+ &Disposition);
+ }
+ else
+ {
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ return Status;
+}
+
+static
+NTSTATUS
+NTAPI
+RXactpCommit(
+ PRXACT_CONTEXT Context)
+{
+ PRXACT_DATA Data;
+ PRXACT_ACTION Action;
+ NTSTATUS Status, TmpStatus;
+ HANDLE KeyHandle;
+ ULONG i;
+
+ Data = Context->Data;
+
+ /* The first action record starts after the data header */
+ Action = (PRXACT_ACTION)(Data + 1);
+
+ /* Loop all recorded actions */
+ for (i = 0; i < Data->ActionCount; i++)
+ {
+ /* Translate relative offsets to actual pointers */
+ Action->KeyName.Buffer = (PWSTR)((PUCHAR)Data + (ULONG_PTR)Action->KeyName.Buffer);
+ Action->ValueName.Buffer = (PWSTR)((PUCHAR)Data + (ULONG_PTR)Action->ValueName.Buffer);
+ Action->ValueData = (PUCHAR)Data + (ULONG_PTR)Action->ValueData;
+
+ /* Check what kind of action this is */
+ if (Action->Type == RXactDeleteKey)
+ {
+ /* This is a delete action. Check if we can use a handle */
+ if ((Action->KeyHandle != INVALID_HANDLE_VALUE) && Context->CanUseHandles)
+ {
+ /* Delete the key by the given handle */
+ Status = ZwDeleteKey(Action->KeyHandle);
+ if (!NT_SUCCESS(Status))
+ {
+ return Status;
+ }
+ }
+ else
+ {
+ /* We cannot use a handle, open the key first by it's name */
+ Status = RXactpOpenTargetKey(Context->RootDirectory,
+ RXactDeleteKey,
+ &Action->KeyName,
+ &KeyHandle);
+ if (NT_SUCCESS(Status))
+ {
+ Status = ZwDeleteKey(KeyHandle);
+ TmpStatus = NtClose(KeyHandle);
+ ASSERT(NT_SUCCESS(TmpStatus));
+ if (!NT_SUCCESS(Status))
+ {
+ return Status;
+ }
+ }
+ else
+ {
+ /* Failed to open the key, it's ok, if it was not found */
+ if (Status != STATUS_OBJECT_NAME_NOT_FOUND)
+ return Status;
+ }
+ }
+ }
+ else if (Action->Type == RXactSetValueKey)
+ {
+ /* This is a set action. Check if we can use a handle */
+ if ((Action->KeyHandle != INVALID_HANDLE_VALUE) && Context->CanUseHandles)
+ {
+ /* Set the key value using the given key handle */
+ Status = ZwSetValueKey(Action->KeyHandle,
+ &Action->ValueName,
+ 0,
+ Action->ValueType,
+ Action->ValueData,
+ Action->ValueDataSize);
+ if (!NT_SUCCESS(Status))
+ {
+ return Status;
+ }
+ }
+ else
+ {
+ /* We cannot use a handle, open the key first by it's name */
+ Status = RXactpOpenTargetKey(Context->RootDirectory,
+ RXactSetValueKey,
+ &Action->KeyName,
+ &KeyHandle);
+ if (!NT_SUCCESS(Status))
+ {
+ return Status;
+ }
+
+ /* Set the key value */
+ Status = ZwSetValueKey(KeyHandle,
+ &Action->ValueName,
+ 0,
+ Action->ValueType,
+ Action->ValueData,
+ Action->ValueDataSize);
+
+ TmpStatus = NtClose(KeyHandle);
+ ASSERT(NT_SUCCESS(TmpStatus));
+
+ if (!NT_SUCCESS(Status))
+ {
+ return Status;
+ }
+ }
+ }
+ else
+ {
+ ASSERT(FALSE);
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ /* Go to the next action record */
+ Action = (PRXACT_ACTION)((PUCHAR)Action + Action->Size);
+ }
+
+ return STATUS_SUCCESS;
+}
+
+NTSTATUS
+NTAPI
+RtlStartRXact(
+ PRXACT_CONTEXT Context)
+{
+ PRXACT_DATA Buffer;
+
+ /* We must not have a buffer yet */
+ if (Context->Data != NULL)
+ {
+ return STATUS_RXACT_INVALID_STATE;
+ }
+
+ /* Allocate a buffer */
+ Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, RXACT_DEFAULT_BUFFER_SIZE);
+ if (Buffer == NULL)
+ {
+ return STATUS_NO_MEMORY;
+ }
+
+ /* Initialize the buffer */
+ Buffer->ActionCount = 0;
+ Buffer->BufferSize = RXACT_DEFAULT_BUFFER_SIZE;
+ Buffer->CurrentSize = sizeof(RXACT_DATA);
+ Context->Data = Buffer;
+
+ return STATUS_SUCCESS;
+}
+
+NTSTATUS
+NTAPI
+RtlAbortRXact(
+ PRXACT_CONTEXT Context)
+{
+ /* We must have a data buffer */
+ if (Context->Data == NULL)
+ {
+ return STATUS_RXACT_INVALID_STATE;
+ }
+
+ /* Free the buffer */
+ RtlFreeHeap(RtlGetProcessHeap(), 0, Context->Data);
+
+ /* Reinitialize the context */
+ RXactInitializeContext(Context, Context->RootDirectory, Context->KeyHandle);
+
+ return STATUS_SUCCESS;
+}
+
+NTSTATUS
+NTAPI
+RtlInitializeRXact(
+ HANDLE RootDirectory,
+ BOOLEAN Commit,
+ PRXACT_CONTEXT *OutContext)
+{
+ NTSTATUS Status, TmpStatus;
+ PRXACT_CONTEXT Context;
+ PKEY_VALUE_FULL_INFORMATION KeyValueInformation;
+ KEY_VALUE_BASIC_INFORMATION KeyValueBasicInfo;
+ UNICODE_STRING ValueName;
+ UNICODE_STRING KeyName;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ RXACT_INFO TransactionInfo;
+ ULONG Disposition;
+ ULONG ValueType;
+ ULONG ValueDataLength;
+ ULONG Length;
+ HANDLE KeyHandle;
+
+ /* Open or create the 'RXACT' key in the root directory */
+ RtlInitUnicodeString(&KeyName, L"RXACT");
+ InitializeObjectAttributes(&ObjectAttributes,
+ &KeyName,
+ OBJ_CASE_INSENSITIVE | OBJ_OPENIF,
+ RootDirectory,
+ NULL);
+ Status = ZwCreateKey(&KeyHandle,
+ KEY_READ | KEY_WRITE | DELETE,
+ &ObjectAttributes,
+ 0,
+ NULL,
+ 0,
+ &Disposition);
+ if (!NT_SUCCESS(Status))
+ {
+ return Status;
+ }
+
+ /* Allocate a new context */
+ Context = RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof(*Context));
+ *OutContext = Context;
+ if (Context == NULL)
+ {
+ TmpStatus = ZwDeleteKey(KeyHandle);
+ ASSERT(NT_SUCCESS(TmpStatus));
+
+ TmpStatus = NtClose(KeyHandle);
+ ASSERT(NT_SUCCESS(TmpStatus));
+
+ return STATUS_NO_MEMORY;
+ }
+
+ /* Initialize the context */
+ RXactInitializeContext(Context, RootDirectory, KeyHandle);
+
+ /* Check if we created a new key */
+ if (Disposition == REG_CREATED_NEW_KEY)
+ {
+ /* The key is new, set the default value */
+ TransactionInfo.Revision = 1;
+ RtlInitUnicodeString(&ValueName, NULL);
+ Status = ZwSetValueKey(KeyHandle,
+ &ValueName,
+ 0,
+ REG_NONE,
+ &TransactionInfo,
+ sizeof(TransactionInfo));
+ if (!NT_SUCCESS(Status))
+ {
+ TmpStatus = ZwDeleteKey(KeyHandle);
+ ASSERT(NT_SUCCESS(TmpStatus));
+
+ TmpStatus = NtClose(KeyHandle);
+ ASSERT(NT_SUCCESS(TmpStatus));
+
+ RtlFreeHeap(RtlGetProcessHeap(), 0, *OutContext);
+ return Status;
+ }
+
+ return STATUS_RXACT_STATE_CREATED;
+ }
+ else
+ {
+ /* The key exited, get the default key value */
+ ValueDataLength = sizeof(TransactionInfo);
+ Status = RtlpNtQueryValueKey(KeyHandle,
+ &ValueType,
+ &TransactionInfo,
+ &ValueDataLength,
+ 0);
+ if (!NT_SUCCESS(Status))
+ {
+ TmpStatus = NtClose(KeyHandle);
+ ASSERT(NT_SUCCESS(TmpStatus));
+ RtlFreeHeap(RtlGetProcessHeap(), 0, Context);
+ return Status;
+ }
+
+ /* Check if the value date is valid */
+ if ((ValueDataLength != sizeof(TransactionInfo)) ||
+ (TransactionInfo.Revision != 1))
+ {
+ TmpStatus = NtClose(KeyHandle);
+ ASSERT(NT_SUCCESS(TmpStatus));
+ RtlFreeHeap(RtlGetProcessHeap(), 0, Context);
+ return STATUS_UNKNOWN_REVISION;
+ }
+
+ /* Query the 'Log' key value */
+ RtlInitUnicodeString(&ValueName, L"Log");
+ Status = ZwQueryValueKey(KeyHandle,
+ &ValueName,
+ KeyValueBasicInformation,
+ &KeyValueBasicInfo,
+ sizeof(KeyValueBasicInfo),
+ &Length);
+ if (!NT_SUCCESS(Status))
+ {
+ /* There is no 'Log', so we are done */
+ return STATUS_SUCCESS;
+ }
+
+ /* Check if the caller asked to commit the current state */
+ if (!Commit)
+ {
+ /* We have a log, that must be committed first! */
+ return STATUS_RXACT_COMMIT_NECESSARY;
+ }
+
+ /* Query the size of the 'Log' key value */
+ Status = ZwQueryValueKey(KeyHandle,
+ &ValueName,
+ KeyValueFullInformation,
+ NULL,
+ 0,
+ &Length);
+ if (Status != STATUS_BUFFER_TOO_SMALL)
+ {
+ return Status;
+ }
+
+ /* Allocate a buffer for the key value information */
+ KeyValueInformation = RtlAllocateHeap(RtlGetProcessHeap(), 0, Length);
+ if (KeyValueInformation == NULL)
+ {
+ return STATUS_NO_MEMORY;
+ }
+
+ /* Query the 'Log' key value */
+ Status = ZwQueryValueKey(KeyHandle,
+ &ValueName,
+ KeyValueFullInformation,
+ KeyValueInformation,
+ Length,
+ &Length);
+ if (!NT_SUCCESS(Status))
+ {
+ RtlFreeHeap(RtlGetProcessHeap(), 0, KeyValueInformation);
+ RtlFreeHeap(RtlGetProcessHeap(), 0, Context);
+ return Status;
+ }
+
+ /* Set the Data pointer to the key value data */
+ Context->Data = (PRXACT_DATA)((PUCHAR)KeyValueInformation +
+ KeyValueInformation->DataOffset);
+
+ /* This is an old log, don't use handles when committing! */
+ Context->CanUseHandles = FALSE;
+
+ /* Commit the data */
+ Status = RXactpCommit(Context);
+ if (!NT_SUCCESS(Status))
+ {
+ RtlFreeHeap(RtlGetProcessHeap(), 0, KeyValueInformation);
+ RtlFreeHeap(RtlGetProcessHeap(), 0, Context);
+ return Status;
+ }
+
+ /* Delete the old key */
+ Status = NtDeleteValueKey(KeyHandle, &ValueName);
+ ASSERT(NT_SUCCESS(Status));
+
+ /* Set the data member to the allocated buffer, so it will get freed */
+ Context->Data = (PRXACT_DATA)KeyValueInformation;
+
+ /* Abort the old transaction */
+ Status = RtlAbortRXact(Context);
+ ASSERT(NT_SUCCESS(Status));
+
+ return Status;
+ }
+}
+
+NTSTATUS
+NTAPI
+RtlAddAttributeActionToRXact(
+ PRXACT_CONTEXT Context,
+ ULONG ActionType,
+ PUNICODE_STRING KeyName,
+ HANDLE KeyHandle,
+ PUNICODE_STRING ValueName,
+ ULONG ValueType,
+ PVOID ValueData,
+ ULONG ValueDataSize)
+{
+ ULONG ActionSize;
+ ULONG RequiredSize;
+ ULONG BufferSize;
+ ULONG CurrentOffset;
+ PRXACT_DATA NewData;
+ PRXACT_ACTION Action;
+
+ /* Validate ActionType parameter */
+ if ((ActionType != RXactDeleteKey) && (ActionType != RXactSetValueKey))
+ {
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ /* Calculate the size of the new action record */
+ ActionSize = ALIGN_UP_BY(ValueName->Length, sizeof(ULONG)) +
+ ALIGN_UP_BY(ValueDataSize, sizeof(ULONG)) +
+ ALIGN_UP_BY(KeyName->Length, sizeof(ULONG)) +
+ ALIGN_UP_BY(sizeof(RXACT_ACTION), sizeof(ULONG));
+
+ /* Calculate the new buffer size we need */
+ RequiredSize = ActionSize + Context->Data->CurrentSize;
+
+ /* Check for integer overflow */
+ if (RequiredSize < ActionSize)
+ {
+ return STATUS_NO_MEMORY;
+ }
+
+ /* Check if the buffer is large enough */
+ BufferSize = Context->Data->BufferSize;
+ if (RequiredSize > BufferSize)
+ {
+ /* Increase by a factor of 2, until it is large enough */
+ while (BufferSize < RequiredSize)
+ {
+ BufferSize *= 2;
+ }
+
+ /* Allocate a new buffer from the heap */
+ NewData = RtlAllocateHeap(RtlGetProcessHeap(), 0, BufferSize);
+ if (NewData == NULL)
+ {
+ return STATUS_NO_MEMORY;
+ }
+
+ /* Copy the old buffer to the new one */
+ RtlCopyMemory(NewData, Context->Data, Context->Data->CurrentSize);
+
+ /* Free the old buffer and use the new one */
+ RtlFreeHeap(RtlGetProcessHeap(), 0, Context->Data);
+ Context->Data = NewData;
+ NewData->BufferSize = BufferSize;
+ }
+
+ /* Get the next action record */
+ Action = (RXACT_ACTION *)((PUCHAR)Context->Data + Context->Data->CurrentSize);
+
+ /* Fill in the fields */
+ Action->Size = ActionSize;
+ Action->Type = ActionType;
+ Action->KeyName = *KeyName;
+ Action->ValueName = *ValueName;
+ Action->ValueType = ValueType;
+ Action->ValueDataSize = ValueDataSize;
+ Action->KeyHandle = KeyHandle;
+
+ /* Copy the key name (and convert the pointer to a buffer offset) */
+ CurrentOffset = Context->Data->CurrentSize + sizeof(RXACT_ACTION);
+ Action->KeyName.Buffer = UlongToPtr(CurrentOffset);
+ RtlCopyMemory((PUCHAR)Context->Data + CurrentOffset,
+ KeyName->Buffer,
+ KeyName->Length);
+
+ /* Copy the value name (and convert the pointer to a buffer offset) */
+ CurrentOffset += ALIGN_UP_BY(KeyName->Length, sizeof(ULONG));
+ Action->ValueName.Buffer = UlongToPtr(CurrentOffset);
+ RtlCopyMemory((PUCHAR)Context->Data + CurrentOffset,
+ ValueName->Buffer,
+ ValueName->Length);
+
+ /* Update the offset */
+ CurrentOffset += ALIGN_UP_BY(ValueName->Length, sizeof(ULONG));
+
+ /* Is this a set action? */
+ if (ActionType == RXactSetValueKey)
+ {
+ /* Copy the key value data as well */
+ Action->ValueData = UlongToPtr(CurrentOffset);
+ RtlCopyMemory((PUCHAR)Context->Data + CurrentOffset,
+ ValueData,
+ ValueDataSize);
+ CurrentOffset += ALIGN_UP_BY(ValueDataSize, sizeof(ULONG));
+ }
+
+ /* Update data site and action count */
+ Context->Data->CurrentSize = CurrentOffset;
+ Context->Data->ActionCount++;
+
+ return STATUS_SUCCESS;
+}
+
+NTSTATUS
+NTAPI
+RtlAddActionToRXact(
+ PRXACT_CONTEXT Context,
+ ULONG ActionType,
+ PUNICODE_STRING KeyName,
+ ULONG ValueType,
+ PVOID ValueData,
+ ULONG ValueDataSize)
+{
+ UNICODE_STRING ValueName;
+
+ /* Create a key and set the default key value or delete a key. */
+ RtlInitUnicodeString(&ValueName, NULL);
+ return RtlAddAttributeActionToRXact(Context,
+ ActionType,
+ KeyName,
+ INVALID_HANDLE_VALUE,
+ &ValueName,
+ ValueType,
+ ValueData,
+ ValueDataSize);
+}
+
+NTSTATUS
+NTAPI
+RtlApplyRXactNoFlush(
+ PRXACT_CONTEXT Context)
+{
+ NTSTATUS Status;
+
+ /* Commit the transaction */
+ Status = RXactpCommit(Context);
+ if (!NT_SUCCESS(Status))
+ {
+ return Status;
+ }
+
+ /* Reset the transaction */
+ Status = RtlAbortRXact(Context);
+ ASSERT(NT_SUCCESS(Status));
+
+ return Status;
+}
+
+NTSTATUS
+NTAPI
+RtlApplyRXact(
+ PRXACT_CONTEXT Context)
+{
+ UNICODE_STRING ValueName;
+ NTSTATUS Status;
+
+ /* Temporarily safe the current transaction in the 'Log' key value */
+ RtlInitUnicodeString(&ValueName, L"Log");
+ Status = ZwSetValueKey(Context->KeyHandle,
+ &ValueName,
+ 0,
+ REG_BINARY,
+ Context->Data,
+ Context->Data->CurrentSize);
+ if (!NT_SUCCESS(Status))
+ {
+ return Status;
+ }
+
+ /* Flush the key */
+ Status = NtFlushKey(Context->KeyHandle);
+ if (!NT_SUCCESS(Status))
+ {
+ NtDeleteValueKey(Context->KeyHandle, &ValueName);
+ return Status;
+ }
+
+ /* Now commit the transaction */
+ Status = RXactpCommit(Context);
+ if (!NT_SUCCESS(Status))
+ {
+ NtDeleteValueKey(Context->KeyHandle, &ValueName);
+ return Status;
+ }
+
+ /* Delete the 'Log' key value */
+ Status = NtDeleteValueKey(Context->KeyHandle, &ValueName);
+ ASSERT(NT_SUCCESS(Status));
+
+ /* Reset the transaction */
+ Status = RtlAbortRXact(Context);
+ ASSERT(NT_SUCCESS(Status));
+
+ return STATUS_SUCCESS;
+}
+