/*
 * PROJECT:     ReactOS Drivers
 * LICENSE:     BSD - See COPYING.ARM in the top level directory
 * FILE:        drivers/sac/driver/util.c
 * PURPOSE:     Driver for the Server Administration Console (SAC) for EMS
 * PROGRAMMERS: ReactOS Portable Systems Group
 */

/* INCLUDES *******************************************************************/

#include "sacdrv.h"

#include <ndk/rtlfuncs.h>

/* GLOBALS ********************************************************************/

PCHAR Utf8ConversionBuffer;
ULONG Utf8ConversionBufferSize = PAGE_SIZE;

PSAC_MACHINE_INFO MachineInformation;

PVOID RequestSacCmdEventObjectBody;
PKEVENT RequestSacCmdEventWaitObjectBody;
PVOID RequestSacCmdSuccessEventObjectBody;
PKEVENT RequestSacCmdSuccessEventWaitObjectBody;
PVOID RequestSacCmdFailureEventObjectBody;
PKEVENT RequestSacCmdFailureEventWaitObjectBody;
PFILE_OBJECT ServiceProcessFileObject;
BOOLEAN HaveUserModeServiceCmdEventInfo;

PSAC_MESSAGE_ENTRY GlobalMessageTable;
ULONG GlobalMessageTableCount;

LONG SerialPortConsumerIndex, SerialPortProducerIndex;
PCHAR SerialPortBuffer;

/* FUNCTIONS ******************************************************************/

BOOLEAN
NTAPI
SacTranslateUtf8ToUnicode(IN CHAR Utf8Char,
                          IN PCHAR Utf8Buffer,
                          OUT PWCHAR Utf8Value)
{
    ULONG i;

    /* Find out how many valid characters we have in the buffer */
    i = 0;
    while (Utf8Buffer[i++] && (i < 3));

    /* If we have at least 3, shift everything by a byte */
    if (i >= 3)
    {
        /* The last input character goes at the end */
        Utf8Buffer[0] = Utf8Buffer[1];
        Utf8Buffer[1] = Utf8Buffer[2];
        Utf8Buffer[2] = Utf8Char;
    }
    else
    {
        /* We don't have more than 3 characters, place the input at the index */
        Utf8Buffer[i] = Utf8Char;
    }

    /* Print to debugger */
    SAC_DBG(SAC_DBG_ENTRY_EXIT, "SacTranslateUtf8ToUnicode - About to decode the UTF8 buffer.\n");
    SAC_DBG(SAC_DBG_ENTRY_EXIT, "                                  UTF8[0]: 0x%02lx UTF8[1]: 0x%02lx UTF8[2]: 0x%02lx\n",
            Utf8Buffer[0],
            Utf8Buffer[1],
            Utf8Buffer[2]);

    /* Is this a simple ANSI character? */
    if (!(Utf8Char & 0x80))
    {
        /* Return it as Unicode, nothing left to do */
        SAC_DBG(SAC_DBG_ENTRY_EXIT, "SACDRV: SacTranslateUTf8ToUnicode - Case1\n");
        *Utf8Value = (WCHAR)Utf8Char;
        Utf8Buffer[0] = Utf8Buffer[1];
        Utf8Buffer[1] = Utf8Buffer[2];
        Utf8Buffer[2] = UNICODE_NULL;
        return TRUE;
    }

    /* Anything else is not yet supported */
    ASSERT(FALSE);
    return FALSE;
}

BOOLEAN
NTAPI
SacTranslateUnicodeToUtf8(IN PWCHAR SourceBuffer,
                          IN ULONG SourceBufferLength,
                          OUT PCHAR DestinationBuffer,
                          IN ULONG DestinationBufferSize,
                          OUT PULONG UTF8Count,
                          OUT PULONG ProcessedCount)
{
    *UTF8Count = 0;
    *ProcessedCount = 0;

    while ((*SourceBuffer) &&
           (*UTF8Count < DestinationBufferSize) &&
           (*ProcessedCount < SourceBufferLength))
    {
        if (*SourceBuffer & 0xFF80)
        {
            if (*SourceBuffer & 0xF800)
            {
                if ((*UTF8Count + 3) >= DestinationBufferSize) break;
                DestinationBuffer[*UTF8Count] = ((*SourceBuffer >> 12) & 0xF) | 0xE0;
                ++*UTF8Count;
                DestinationBuffer[*UTF8Count] = ((*SourceBuffer >> 6) & 0x3F) | 0x80;
            }
            else
            {
                if ((*UTF8Count + 2) >= DestinationBufferSize) break;
                DestinationBuffer[*UTF8Count] = ((*SourceBuffer >> 6) & 31) | 0xC0;
            }
            ++*UTF8Count;
            DestinationBuffer[*UTF8Count] = (*SourceBuffer & 0x3F) | 0x80;
        }
        else
        {
            DestinationBuffer[*UTF8Count] = (*SourceBuffer & 0x7F);
        }

        ++*UTF8Count;
        ++*ProcessedCount;
        ++SourceBuffer;
    }

    ASSERT(*ProcessedCount <= SourceBufferLength);
    ASSERT(*UTF8Count <= DestinationBufferSize);
    return TRUE;
}

PWCHAR
NTAPI
GetMessage(IN ULONG MessageIndex)
{
    PSAC_MESSAGE_ENTRY MessageEntry;
    ULONG i;
    PWCHAR MessageData = NULL;

    /* Loop all cached messages */
    for (i = 0; i < GlobalMessageTableCount; i++)
    {
        /* Check if this one matches the index */
        MessageEntry = &GlobalMessageTable[i];
        if (MessageEntry->Index == MessageIndex)
        {
            /* It does, return the buffer */
            MessageData = MessageEntry->Buffer;
            break;
        }
    }

    /* We should always find it */
    if (!MessageData) ASSERT(FALSE);
    return MessageData;
}

NTSTATUS
NTAPI
UTF8EncodeAndSend(IN PWCHAR String)
{
    ULONG ProcessedCount, Utf8Count, i;
    NTSTATUS Status = STATUS_SUCCESS;

    /* Call the translator routine */
    if (SacTranslateUnicodeToUtf8(String,
                                  wcslen(String),
                                  Utf8ConversionBuffer,
                                  Utf8ConversionBufferSize,
                                  &Utf8Count,
                                  &ProcessedCount))
    {
        /* Loop every character */
        for (i = 0; i < Utf8Count; i++)
        {
            /* Send it to the terminal */
            Status = HeadlessDispatch(HeadlessCmdPutData,
                                      &Utf8ConversionBuffer[i],
                                      sizeof(Utf8ConversionBuffer[i]),
                                      NULL,
                                      NULL);
            if (!NT_SUCCESS(Status)) break;
        }
    }
    else
    {
        /* Conversion failed */
        Status = STATUS_UNSUCCESSFUL;
    }

    /* All done */
    return Status;
}

VOID
NTAPI
SacFormatMessage(IN PWCHAR FormattedString,
                 IN PWCHAR MessageString,
                 IN ULONG MessageSize)
{
    SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC SacFormatMessage: Entering.\n");

    /* Check if any of the parameters are NULL or zero */
    if (!(MessageString) || !(FormattedString) || !(MessageSize))
    {
        SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC SacFormatMessage: Exiting with invalid parameters.\n");
        return;
    }

    /* Keep going as long as there's still characters */
    while ((MessageString[0]) && (MessageSize))
    {
        /* Is it a non-formatting character? */
        if (MessageString[0] != L'%')
        {
            /* Just write it back into the buffer and keep going */
            *FormattedString++ = MessageString[0];
            MessageString++;
        }
        else
        {
            /* Go over the format characters we recognize */
            switch (MessageString[1])
            {
                case L'0':
                    *FormattedString = UNICODE_NULL;
                    return;

                case L'%':
                    *FormattedString++ = L'%';
                    break;

                case L'\\':
                    *FormattedString++ = L'\r';
                    *FormattedString++ = L'\n';
                    break;

                case L'r':
                    *FormattedString++ = L'\r';
                    break;

                case L'b':
                    *FormattedString++ = L' ';
                    break;

                case L'.':
                    *FormattedString++ = L'.';
                    break;

                case L'!':
                    *FormattedString++ = L'!';
                    break;

                default:
                    /* Only move forward one character */
                    MessageString--;
                    break;
            }

            /* Move forward two characters */
            MessageString += 2;
        }

        /* Move to the next character*/
        MessageSize--;
    }

    /* All done */
    *FormattedString = UNICODE_NULL;
    SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC SacFormatMessage: Exiting.\n");
}

NTSTATUS
NTAPI
PreloadGlobalMessageTable(IN PVOID ImageBase)
{
    NTSTATUS Status, Status2;
    ULONG MessageId, TotalLength, TextSize, i;
    PWCHAR StringBuffer;
    PMESSAGE_RESOURCE_ENTRY MessageEntry;
    PAGED_CODE();
    SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC PreloadGlobalMessageTable: Entering.\n");

    /* Nothing to do if we already have a table */
    Status = STATUS_SUCCESS;
    if (GlobalMessageTable) goto Exit;

    /* Loop through up to 200 messages */
    TotalLength = 0;
    for (MessageId = 1; MessageId != SAC_MAX_MESSAGES; MessageId++)
    {
        /* Find this message ID in the string table*/
        Status2 = RtlFindMessage(ImageBase,
                                 11,
                                 LANG_NEUTRAL,
                                 MessageId,
                                 &MessageEntry);
        if (NT_SUCCESS(Status2))
        {
            /* Make sure it's Unicode */
            ASSERT(MessageEntry->Flags & MESSAGE_RESOURCE_UNICODE);

            /* Remove the space taken up by the OS header, and add our own */
            TotalLength += MessageEntry->Length -
                           FIELD_OFFSET(MESSAGE_RESOURCE_ENTRY, Text) +
                           sizeof(SAC_MESSAGE_ENTRY);

            /* One more in the table */
            GlobalMessageTableCount++;
        }
    }

    /* We should've found at least one message... */
    if (!TotalLength)
    {
        /* Bail out otherwise */
        SAC_DBG(SAC_DBG_INIT, "SAC PreloadGlobalMessageTable: No Messages.\n");
        Status = STATUS_INVALID_PARAMETER;
        goto Exit;
    }

    /* Allocate space for the buffers and headers */
    GlobalMessageTable = SacAllocatePool(TotalLength, GLOBAL_BLOCK_TAG);
    if (!GlobalMessageTable)
    {
        /* Bail out if we couldn't allocate it */
        Status = STATUS_NO_MEMORY;
        goto Exit;
    }

    /* All the buffers are going to be at the end of the table */
    StringBuffer = (PWCHAR)(&GlobalMessageTable[GlobalMessageTableCount]);

    /* Now loop over our entries again */
    for (i = 0, MessageId = 1; MessageId != SAC_MAX_MESSAGES; MessageId++)
    {
        /* Make sure the message is still there...! */
        Status2 = RtlFindMessage(ImageBase,
                                 11,
                                 LANG_NEUTRAL,
                                 MessageId,
                                 &MessageEntry);
        if (NT_SUCCESS(Status2))
        {
            /* Write the entry in the message table*/
            GlobalMessageTable[i].Index = MessageId;
            GlobalMessageTable[i].Buffer = StringBuffer;

            /* The structure includes the size of the header, elide it */
            TextSize = MessageEntry->Length -
                       FIELD_OFFSET(MESSAGE_RESOURCE_ENTRY, Text);

            /* Format the message into the entry. It should be same or smaller */
            SacFormatMessage(StringBuffer, (PWCHAR)MessageEntry->Text, TextSize);
            ASSERT((ULONG)(wcslen(StringBuffer)*sizeof(WCHAR)) <= TextSize);

            /* Move to the next buffer space */
            StringBuffer += (TextSize / sizeof(WCHAR));

            /* Move to the next entry, make sure the status is full success */
            i++;
            Status = STATUS_SUCCESS;
        }
    }

Exit:
    /* All done, return the status code */
    SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC TearDownGlobalMessageTable: Exiting with status 0x%0x\n", Status);
    return Status;
}

NTSTATUS
NTAPI
TearDownGlobalMessageTable(VOID)
{
    SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC TearDownGlobalMessageTable: Entering.\n");

    /* Free the table if one existed */
    if (GlobalMessageTable) SacFreePool(GlobalMessageTable);

    SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC TearDownGlobalMessageTable: Exiting\n");
    return STATUS_SUCCESS;
}

NTSTATUS
NTAPI
GetRegistryValueBuffer(IN PCWSTR KeyName,
                       IN PWCHAR ValueName,
                       IN PKEY_VALUE_PARTIAL_INFORMATION* Buffer)
{
    NTSTATUS Status;
    OBJECT_ATTRIBUTES ObjectAttributes;
    UNICODE_STRING DestinationString;
    HANDLE Handle;
    ULONG ResultLength = 0;
    SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC GetRegistryValueBuffer: Entering.\n");
    CHECK_PARAMETER1(KeyName);
    CHECK_PARAMETER2(ValueName);

    /* Open the specified key */
    RtlInitUnicodeString(&DestinationString, KeyName); 
    InitializeObjectAttributes(&ObjectAttributes,
                               &DestinationString,
                               OBJ_CASE_INSENSITIVE,
                               NULL,
                               NULL);
    Status = ZwOpenKey(&Handle,
                       KEY_WRITE | SYNCHRONIZE | KEY_READ,
                       &ObjectAttributes);
    if (!NT_SUCCESS(Status))
    {
        /* Bail out on failure */
        SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC GetRegistryValueBuffer: failed ZwOpenKey: %X.\n", Status);
        return Status;
    }

    /* Query the size of the key */
    RtlInitUnicodeString(&DestinationString, ValueName);
    Status = ZwQueryValueKey(Handle,
                             &DestinationString,
                             KeyValuePartialInformation,
                             NULL,
                             0,
                             &ResultLength);
    if (!ResultLength) return Status;

    /* Allocate the buffer for the partial info structure and our integer data */
    ResultLength += sizeof(ULONG);
    *Buffer = SacAllocatePool(ResultLength, GLOBAL_BLOCK_TAG);
    if (!*Buffer)
    {
        SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC GetRegistryValueBuffer: failed allocation\n");
        return Status;
    }

    /* Now read the data */
    Status = ZwQueryValueKey(Handle,
                             &DestinationString,
                             KeyValuePartialInformation,
                             *Buffer,
                             ResultLength,
                             &ResultLength);
    if (!NT_SUCCESS(Status))
    {
        /* Free the buffer if we couldn't read the data */
        SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC GetRegistryValueBuffer: failed ZwQueryValueKey: %X.\n", Status);
        SacFreePool(*Buffer);
    }

    /* Return the result */
    SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC SetRegistryValue: Exiting.\n");
    return Status;
}

NTSTATUS
NTAPI
SetRegistryValue(IN PCWSTR KeyName,
                 IN PWCHAR ValueName,
                 IN ULONG Type,
                 IN PVOID Data,
                 IN ULONG DataSize)
{
    NTSTATUS Status;
    OBJECT_ATTRIBUTES ObjectAttributes;
    UNICODE_STRING DestinationString;
    HANDLE Handle;
    SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC SetRegistryValue: Entering.\n");
    CHECK_PARAMETER1(KeyName);
    CHECK_PARAMETER2(ValueName);
    CHECK_PARAMETER4(Data);

    /* Open the specified key */
    RtlInitUnicodeString(&DestinationString, KeyName); 
    InitializeObjectAttributes(&ObjectAttributes,
                               &DestinationString,
                               OBJ_CASE_INSENSITIVE,
                               NULL,
                               NULL);
    Status = ZwOpenKey(&Handle,
                       KEY_WRITE | SYNCHRONIZE | KEY_READ,
                       &ObjectAttributes);
    if (!NT_SUCCESS(Status))
    {
        /* Bail out on failure */
        SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC SetRegistryValue: failed ZwOpenKey: %X.\n", Status);
        return Status;
    }

    /* Set the specified value */
    RtlInitUnicodeString(&DestinationString, ValueName);
    Status = ZwSetValueKey(Handle, &DestinationString, 0, Type, Data, DataSize);
    if (!NT_SUCCESS(Status))
    {
        /* Print error on failure */
        SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC SetRegistryValue: failed ZwSetValueKey: %X.\n", Status);
    }

    /* Close the handle and exit */
    NtClose(Handle);
    SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC SetRegistryValue: Exiting.\n");
    return Status;
}

NTSTATUS
NTAPI
CopyRegistryValueData(IN PVOID* Buffer,
                      IN PKEY_VALUE_PARTIAL_INFORMATION PartialInfo)
{
    NTSTATUS Status = STATUS_SUCCESS;
    CHECK_PARAMETER1(Buffer);
    CHECK_PARAMETER2(PartialInfo);

    /* Allocate space for registry data */
    *Buffer = SacAllocatePool(PartialInfo->DataLength, GLOBAL_BLOCK_TAG);
    if (*Buffer)
    {
        /* Copy the data into the buffer */
        RtlCopyMemory(*Buffer, PartialInfo->Data, PartialInfo->DataLength);
    }
    else
    {
        /* Set the correct error code */
        SAC_DBG(SAC_DBG_UTIL, "SAC CopyRegistryValueBuffer: Failed ALLOCATE.\n");
        Status = STATUS_NO_MEMORY;
    }

    /* Return the result */
    return Status;
}

NTSTATUS
NTAPI
TranslateMachineInformationXML(IN PWCHAR *Buffer,
                               IN PWCHAR ExtraData)
{
    NTSTATUS Status;
    SIZE_T Size;
    PWCHAR p;
    CHECK_PARAMETER1(Buffer);

    /* Start by believing the world is beautiful */
    Status = STATUS_SUCCESS;

    /* First, the header */
    Size = wcslen(L"<machine-info>\r\n");

    /* Do we have a machine name? */
    if (MachineInformation->MachineName)
    {
        /* Go and add it in */
        Size += wcslen(MachineInformation->MachineName);
        Size += wcslen(L"<name>%s</name>\r\n");
    }

    /* Do we have a GUID? */
    if (MachineInformation->MachineGuid)
    {
        /* Go and add it in */
        Size += wcslen(MachineInformation->MachineGuid);
        Size += wcslen(L"<guid>%s</guid>\r\n");
    }

    /* Do we know the processor? */
    if (MachineInformation->ProcessorArchitecture)
    {
        /* Go and add it in */
        Size += wcslen(MachineInformation->ProcessorArchitecture);
        Size += wcslen(L"<processor-architecture>%s</processor-architecture>\r\n");
    }

    /* Do we have the version? */
    if (MachineInformation->MajorVersion)
    {
        /* Go and add it in */
        Size += wcslen(MachineInformation->MajorVersion);
        Size += wcslen(L"<os-version>%s</os-version>\r\n");
    }

    /* Do we have the build? */
    if (MachineInformation->BuildNumber)
    {
        /* Go and add it in */
        Size += wcslen(MachineInformation->BuildNumber);
        Size += wcslen(L"<os-build-number>%s</os-build-number>\r\n");
    }

    /* Do we have the product type? */
    if (MachineInformation->ProductType)
    {
        /* Go and add it in */
        Size += wcslen(MachineInformation->ProductType);
        Size += wcslen(L"<os-product>%s</os-product>\r\n");
    }

    /* Do we have a service pack? */
    if (MachineInformation->ServicePack)
    {
        /* Go and add it in */
        Size += wcslen(MachineInformation->ServicePack);
        Size += wcslen(L"<os-service-pack>%s</os-service-pack>\r\n");
    }

    /* Anything else we need to know? Add it in too */
    if (ExtraData) Size += wcslen(ExtraData);

    /* Finally, add the footer */
    Size += wcslen(L"</machine-info>\r\n");

    /* Convert to bytes and add a NULL */
    Size += sizeof(ANSI_NULL);
    Size *= sizeof(WCHAR);

    /* Allocate space for the buffer */
    p = SacAllocatePool(Size, GLOBAL_BLOCK_TAG);
    *Buffer = p;
    if (!p) return STATUS_NO_MEMORY;

    wcscpy(p, L"<machine-info>\r\n");
    p += wcslen(L"<machine-info>\r\n");

    if (MachineInformation->MachineName)
    {
        p += swprintf(p,
                      L"<name>%s</name>\r\n",
                      MachineInformation->MachineName);
    }

    if (MachineInformation->MachineGuid)
    {
        p += swprintf(p,
                      L"<guid>%s</guid>\r\n",
                      MachineInformation->MachineGuid);
    }

    if (MachineInformation->ProcessorArchitecture)
    {
        p += swprintf(p,
                      L"<processor-architecture>%s</processor-architecture>\r\n",
                      MachineInformation->ProcessorArchitecture);
    }

    if (MachineInformation->MajorVersion)
    {
        p += swprintf(p,
                      L"<os-version>%s</os-version>\r\n",
                      MachineInformation->MajorVersion);
    }

    if (MachineInformation->BuildNumber)
    {
        p += swprintf(p,
                      L"<os-build-number>%s</os-build-number>\r\n",
                      MachineInformation->BuildNumber);
    }

    if (MachineInformation->ProductType)
    {
        p += swprintf(p,
                      L"<os-product>%s</os-product>\r\n",
                      MachineInformation->ProductType);
    }

    if (MachineInformation->ServicePack)
    {
        p += swprintf(p,
                      L"<os-service-pack>%s</os-service-pack>\r\n",
                      MachineInformation->ServicePack);
    }

    if (ExtraData)
    {
        wcscpy(p, ExtraData);
        p += wcslen(ExtraData);
    }

    wcscpy(p, L"</machine-info>\r\n");
    SAC_DBG(SAC_DBG_ENTRY_EXIT, "MachineInformation: %S\n", *Buffer);
    ASSERT((((ULONG)wcslen(*Buffer) + 1) * sizeof(WCHAR)) <= Size);
    return Status;
}

VOID
NTAPI
InitializeMachineInformation(VOID)
{
    NTSTATUS Status;
    PWCHAR GuidString, MajorVersion, ServicePack, BuildNumber, MessageBuffer;
    PWCHAR ProductType;
    ULONG SuiteTypeMessage;
    BOOLEAN SetupInProgress = FALSE;
    GUID SystemGuid;
    SIZE_T RealSize, Size, OutputSize;
    PKEY_VALUE_PARTIAL_INFORMATION PartialInfo;
    RTL_OSVERSIONINFOEXW VersionInformation;
    SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC Initialize Machine Information : Entering.\n");

    /* Don't do anything if we already queried this */
    if (MachineInformation)
    {
        SAC_DBG(SAC_DBG_MACHINE, "SAC Initialize Machine Information:: MachineInformationBuffer already initialized.\n");
        return;
    }

    /* Allocate the machine information */
    MachineInformation = SacAllocatePool(sizeof(*MachineInformation),
                                         GLOBAL_BLOCK_TAG);
    if (!MachineInformation)
    {
        goto Fail;
    }

    /* Zero it out for now */
    RtlZeroMemory(MachineInformation, sizeof(*MachineInformation));

    /* Query OS version */
    RtlZeroMemory(&VersionInformation, sizeof(VersionInformation));
    VersionInformation.dwOSVersionInfoSize = sizeof(VersionInformation);
    Status = RtlGetVersion((PRTL_OSVERSIONINFOW)&VersionInformation);
    if (!NT_SUCCESS(Status))
    {
        SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC InitializeMachineInformation: Exiting (2).\n");
        goto Fail;
    }

    /* Check if setup is in progress */
    Status = GetRegistryValueBuffer(L"\\Registry\\Machine\\System\\Setup",
                                    L"SystemSetupInProgress",
                                    &PartialInfo);
    if (NT_SUCCESS(Status))
    {
        /* The key is there, is the value set? */
        if (*(PULONG)PartialInfo->Data) SetupInProgress = TRUE;
        SacFreePool(PartialInfo);
        if (SetupInProgress)
        {
            /* Yes, so we'll use a special hostname to identify this */
            MessageBuffer = GetMessage(SAC_UNINITIALIZED_MSG);
            Size = wcslen(MessageBuffer);
            ASSERT(Size > 0);
            RealSize = Size * sizeof(WCHAR) + sizeof(UNICODE_NULL);

            /* Make room for it and copy it in there */
            MachineInformation->MachineName = SacAllocatePool(RealSize,
                                                              GLOBAL_BLOCK_TAG);
            if (MachineInformation->MachineName)
            {
                wcscpy(MachineInformation->MachineName, MessageBuffer);
            }
        }
    }

    /* If we are not in setup mode, or if we failed to check... */
    if (!SetupInProgress)
    {
        /* Query the computer name */
        Status = GetRegistryValueBuffer(L"\\Registry\\Machine\\System\\"
                                        L"CurrentControlSet\\Control\\"
                                        L"ComputerName\\ComputerName",
                                        L"ComputerName",
                                        &PartialInfo);
        if (!NT_SUCCESS(Status))
        {
            /* It's not critical, but we won't have it */
            SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC InitializeMachineInformation: Failed to get machine name.\n");
        }
        else
        {
            /* We have the name, copy it from the registry */
            Status = CopyRegistryValueData((PVOID*)&MachineInformation->
                                           MachineName,
                                           PartialInfo);
            SacFreePool(PartialInfo);
            if (!NT_SUCCESS(Status))
            {
                SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC InitializeMachineInformation: Exiting (20).\n");
                goto Fail;
            }
        }
    }

    /* Next step, try to get the machine GUID */
    RtlZeroMemory(&SystemGuid, sizeof(SystemGuid));
    OutputSize = sizeof(SystemGuid);
    Status = HeadlessDispatch(HeadlessCmdQueryGUID,
                              NULL,
                              0,
                              &SystemGuid,
                              &OutputSize);
    if (!NT_SUCCESS(Status))
    {
        SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC InitializeMachineInformation: Failed to get Machine GUID.\n");
    }
    else
    {
        /* We have it -- make room for it */
        GuidString = SacAllocatePool(0x50, GLOBAL_BLOCK_TAG);
        if (!GuidString)
        {
            SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC InitializeMachineInformation: Exiting (31).\n");
            goto Fail;
        }

        /* Build the string with the GUID in it, and save the ppointer to it */
        swprintf(GuidString,
                 L"%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
                 SystemGuid.Data1,
                 SystemGuid.Data2,
                 SystemGuid.Data3,
                 SystemGuid.Data4[0],
                 SystemGuid.Data4[1],
                 SystemGuid.Data4[2],
                 SystemGuid.Data4[3],
                 SystemGuid.Data4[4],
                 SystemGuid.Data4[5],
                 SystemGuid.Data4[6],
                 SystemGuid.Data4[7]);
        MachineInformation->MachineGuid = GuidString;
    }

    /* Next, query the processor architecture */
    Status = GetRegistryValueBuffer(L"\\Registry\\Machine\\System\\"
                                    L"CurrentControlSet\\Control\\"
                                    L"Session Manager\\Environment",
                                    L"PROCESSOR_ARCHITECTURE",
                                    &PartialInfo);
    if (!NT_SUCCESS(Status))
    {
        /* It's not critical, but we won't have it */
        SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC InitializeMachineInformation: Exiting (30).\n");
    }
    else
    {
        /* We have it! Copy the value from the registry */
        Status = CopyRegistryValueData((PVOID*)&MachineInformation->
                                       ProcessorArchitecture,
                                       PartialInfo);
        SacFreePool(PartialInfo);
        if (!NT_SUCCESS(Status))
        {
            SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC InitializeMachineInformation: Exiting (30).\n");
            goto Fail;
        }
    }

    /* Now allocate a buffer for the OS version number */
    MajorVersion = SacAllocatePool(0x18, GLOBAL_BLOCK_TAG);
    if (!MajorVersion)
    {
        SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC InitializeMachineInformation: Exiting (50).\n");
        goto Fail;
    }

    /* Build the buffer and set a pointer to it */
    swprintf(MajorVersion,
             L"%d.%d",
             VersionInformation.dwMajorVersion,
             VersionInformation.dwMinorVersion);
    MachineInformation->MajorVersion = MajorVersion;

    /* Now allocate a buffer for the OS build number */
    BuildNumber = SacAllocatePool(0xC, GLOBAL_BLOCK_TAG);
    if (!BuildNumber)
    {
        SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC InitializeMachineInformation: Exiting (60).\n");
        goto Fail;
    }

    /* Build the buffer and set a pointer to it */
    swprintf(BuildNumber, L"%d", VersionInformation.dwBuildNumber);
    MachineInformation->BuildNumber = BuildNumber;

    /* Now check what kind of SKU this is */
    if (ExVerifySuite(DataCenter))
    {
        SuiteTypeMessage = SAC_DATACENTER_SUITE_MSG;
    }
    else if (ExVerifySuite(EmbeddedNT))
    {
        SuiteTypeMessage = SAC_EMBEDDED_SUITE_MSG;
    }
    else if (ExVerifySuite(Enterprise))
    {
        SuiteTypeMessage = SAC_ENTERPRISE_SUITE_MSG;
    }
    else
    {
        /* Unknown or perhaps a client SKU */
        SuiteTypeMessage = SAC_NO_SUITE_MSG;
    }

    /* Get the string that corresponds to the SKU type */
    MessageBuffer = GetMessage(SuiteTypeMessage);
    if (!MessageBuffer)
    {
        /* We won't have it, but this isn't critical */
        SAC_DBG(SAC_DBG_INIT, "SAC InitializeMachineInformation: Failed to get product type.\n");
    }
    else
    {
        /* Calculate the size we need to hold the string */
        Size = wcslen(MessageBuffer);
        ASSERT(Size > 0);
        RealSize = Size * sizeof(WCHAR) + sizeof(UNICODE_NULL);

        /* Allocate a buffer for it */
        ProductType = SacAllocatePool(RealSize, GLOBAL_BLOCK_TAG);
        if (!ProductType)
        {
            SAC_DBG(SAC_DBG_INIT, "SAC InitializeMachineInformation: Failed product type memory allocation.\n");
            goto Fail;
        }

        /* Copy the string and set the pointer */
        RtlCopyMemory(ProductType, MessageBuffer, RealSize);
        MachineInformation->ProductType = ProductType;
    }

    /* Check if this is a SP version or RTM version */
    if (VersionInformation.wServicePackMajor)
    {
        /* This is a service pack, allocate a buffer for the version */
        ServicePack = SacAllocatePool(0x18, GLOBAL_BLOCK_TAG);
        if (ServicePack)
        {
            /* Build the buffer and set a pointer to it */
            swprintf(ServicePack,
                     L"%d.%d",
                     VersionInformation.wServicePackMajor,
                     VersionInformation.wServicePackMinor);
            MachineInformation->ServicePack = ServicePack;

            /* We've collected all the machine info and are done! */
            return;
        }

        /* This is the failure path */
        SAC_DBG(SAC_DBG_INIT, "SAC InitializeMachineInformation: Failed service pack memory allocation.\n");
    }
    else
    {
        /* Get a generic string that indicates there's no service pack */
        MessageBuffer = GetMessage(SAC_NO_DATA_MSG);
        Size = wcslen(MessageBuffer);
        ASSERT(Size > 0);
        RealSize = Size * sizeof(WCHAR) + sizeof(UNICODE_NULL);

        /* Allocate memory for the "no service pack" string */
        ServicePack = SacAllocatePool(RealSize, GLOBAL_BLOCK_TAG);
        if (ServicePack)
        {
            /* Copy the buffer and set a pointer to it */
            RtlCopyMemory(ServicePack, MessageBuffer, RealSize);
            MachineInformation->ServicePack = ServicePack;

            /* We've collected all the machine info and are done! */
            return;
        }

        SAC_DBG(SAC_DBG_INIT, "SAC InitializeMachineInformation: Failed service pack memory allocation.\n");
    }

Fail:
    /* In the failure path, always cleanup the machine information buffer */
    if (MachineInformation)
    {
        SacFreePool(MachineInformation);
    }
    SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC Initialize Machine Information : Exiting with error.\n");
}

NTSTATUS
NTAPI
GetCommandConsoleLaunchingPermission(OUT PBOOLEAN Permission)
{
    NTSTATUS Status;
    PKEY_VALUE_PARTIAL_INFORMATION Dummy;

    /* Assume success and read the key */
    *Permission = TRUE;
    Status = GetRegistryValueBuffer(L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\sacdrv",
                                    L"DisableCmdSessions",
                                    &Dummy);
    if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
    {
        /* The default is success */
        Status = STATUS_SUCCESS;
    }
    else
    {
        /* Only if the key is present and set, do we disable permission */
        if (NT_SUCCESS(Status)) *Permission = FALSE;
    }

    /* Return status */
    return Status;
}

NTSTATUS
NTAPI
ImposeSacCmdServiceStartTypePolicy(VOID)
{
    NTSTATUS Status;
    PKEY_VALUE_PARTIAL_INFORMATION Buffer = NULL;
    PULONG Data;

    /* Read the service start type*/
    Status = GetRegistryValueBuffer(L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\sacsvr",
                                    L"Start",
                                    &Buffer);
    if (!NT_SUCCESS(Status)) return Status;

    /* If there's no start type, fail, as this is unusual */
    if (!Buffer) return STATUS_UNSUCCESSFUL;

    /* Read the value */
    Status = CopyRegistryValueData((PVOID*)&Data, Buffer);
    SacFreePool(Buffer);
    if (!NT_SUCCESS(Status)) return Status;

    /* Check what the current start type is */
    switch (*Data)
    {
        /* It's boot, system, or disabled */
        case 1:
        case 2:
        case 4:
            /* Leave it as is */
            return Status;

        case 3:

            /* It's set to automatic, set it to system instead */
            *Data = 2;
            Status = SetRegistryValue(L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\sacsvr",
                                      L"Start",
                                      REG_DWORD,
                                      Data,
                                      sizeof(ULONG));
            if (!NT_SUCCESS(Status))
            {
                SAC_DBG(SAC_DBG_INIT, "SAC ImposeSacCmdServiceStartTypePolicy: Failed SetRegistryValue: %X\n", Status);
            }
            break;

        default:
            ASSERT(FALSE);
    }

    return Status;
}

VOID
NTAPI
InitializeCmdEventInfo(VOID)
{
    /* Check if we were already initialized */
    if (HaveUserModeServiceCmdEventInfo)
    {
        /* Full state expected */
        ASSERT(RequestSacCmdEventObjectBody);
        ASSERT(RequestSacCmdSuccessEventObjectBody);
        ASSERT(RequestSacCmdFailureEventObjectBody);

        /* Dereference each wait object in turn */
        if (RequestSacCmdEventObjectBody)
        {
            ObDereferenceObject(RequestSacCmdEventObjectBody);
        }

        if (RequestSacCmdSuccessEventObjectBody)
        {
            ObDereferenceObject(RequestSacCmdSuccessEventObjectBody);
        }

        if (RequestSacCmdFailureEventObjectBody)
        {
            ObDereferenceObject(RequestSacCmdFailureEventObjectBody);
        }
    }

    /* Claer everything */ 
    RequestSacCmdEventObjectBody = NULL;
    RequestSacCmdEventWaitObjectBody = NULL;
    RequestSacCmdSuccessEventObjectBody = NULL;
    RequestSacCmdSuccessEventWaitObjectBody = NULL;
    RequestSacCmdFailureEventObjectBody = NULL;
    RequestSacCmdFailureEventWaitObjectBody = NULL;
    ServiceProcessFileObject = NULL;

    /* Reset state */
    HaveUserModeServiceCmdEventInfo = FALSE;
}

NTSTATUS
NTAPI
RegisterBlueScreenMachineInformation(VOID)
{
    PWCHAR XmlBuffer;
    PHEADLESS_CMD_SET_BLUE_SCREEN_DATA BsBuffer;
    SIZE_T Length, HeaderLength, TotalLength;
    NTSTATUS Status;
    ULONG i;

    /* Create the XML buffer and make sure it's OK */
    Status = TranslateMachineInformationXML(&XmlBuffer, NULL);
    CHECK_PARAMETER_WITH_STATUS(NT_SUCCESS(Status), Status);
    CHECK_PARAMETER_WITH_STATUS(XmlBuffer, STATUS_UNSUCCESSFUL);

    /* Compute the sizes and allocate a buffer for it */
    Length = wcslen(XmlBuffer);
    HeaderLength = strlen("MACHINEINFO");
    TotalLength = HeaderLength +
                  Length +
                  sizeof(*BsBuffer) +
                  2 * sizeof(ANSI_NULL);
    BsBuffer = SacAllocatePool(TotalLength, GLOBAL_BLOCK_TAG);
    CHECK_PARAMETER_WITH_STATUS(BsBuffer, STATUS_NO_MEMORY);

    /* Copy the XML property name */
    strcpy((PCHAR)BsBuffer->Data, "MACHINEINFO");
    BsBuffer->ValueIndex = HeaderLength + sizeof(ANSI_NULL);

    /* Copy the data and NULL-terminate it */
    for (i = 0; i < Length; i++)
    {
        BsBuffer->Data[BsBuffer->ValueIndex + i] = XmlBuffer[i];
    }
    BsBuffer->Data[BsBuffer->ValueIndex + i] = ANSI_NULL;

    /* Let the OS save the buffer for later */
    Status = HeadlessDispatch(HeadlessCmdSetBlueScreenData,
                              BsBuffer,
                              TotalLength,
                              NULL,
                              NULL);

    /* Failure or not, we don't need this anymore */
    SacFreePool(BsBuffer);
    SacFreePool(XmlBuffer);

    /* Return the result */
    SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC Initialize Machine Information: Exiting.\n");
    return Status;
}

VOID
NTAPI
FreeMachineInformation(VOID)
{
    ASSERT(MachineInformation);

    /* Free every cached string of machine information */
    if (MachineInformation->MachineName) SacFreePool(MachineInformation);
    if (MachineInformation->MachineGuid) SacFreePool(MachineInformation->MachineGuid);
    if (MachineInformation->ProcessorArchitecture) SacFreePool(MachineInformation->ProcessorArchitecture);
    if (MachineInformation->MajorVersion) SacFreePool(MachineInformation->MajorVersion);
    if (MachineInformation->BuildNumber) SacFreePool(MachineInformation->BuildNumber);
    if (MachineInformation->ProductType) SacFreePool(MachineInformation->ProductType);
    if (MachineInformation->ServicePack) SacFreePool(MachineInformation->ServicePack);
}

BOOLEAN
NTAPI
VerifyEventWaitable(IN HANDLE Handle,
                    OUT PVOID *WaitObject,
                    OUT PVOID *ActualWaitObject)
{
    PVOID Object;
    NTSTATUS Status;
    POBJECT_TYPE ObjectType;

    /* Reference the object */
    Status = ObReferenceObjectByHandle(Handle,
                                       EVENT_ALL_ACCESS,
                                       NULL,
                                       KernelMode,
                                       &Object,
                                       NULL);
    *WaitObject = Object;
    if (!NT_SUCCESS(Status))
    {
        SAC_DBG(SAC_DBG_INIT, "SAC VerifyEventWaitable: Unable to reference event object (%lx)\n", Status);
        return FALSE;
    }

    /* Check if the object itself is NOT being used */
    ObjectType = OBJECT_TO_OBJECT_HEADER(Object)->Type;
    if (ObjectType->TypeInfo.UseDefaultObject == FALSE)
    {
        /* Get the actual object that's being used for the wait */
        *ActualWaitObject = (PVOID)((ULONG_PTR)Object +
                                    (ULONG_PTR)ObjectType->DefaultObject);
        return TRUE;
    }

    /* Drop the reference we took */
    SAC_DBG(SAC_DBG_INIT, "SAC VerifyEventWaitable: event object not waitable!\n");
    ObDereferenceObject(*WaitObject);
    return FALSE;
}

NTSTATUS
NTAPI
SerialBufferGetChar(OUT PCHAR Char)
{
    /* Check if nothing's been produced yet */
    if (SerialPortConsumerIndex == SerialPortProducerIndex)
    {
        return STATUS_NO_DATA_DETECTED;
    }

    /* Consume the produced character and clear it*/
    *Char = SerialPortBuffer[SerialPortConsumerIndex];
    SerialPortBuffer[SerialPortConsumerIndex] = ANSI_NULL;

    /* Advance the index and return success */
    _InterlockedExchange(&SerialPortConsumerIndex,
                         (SerialPortConsumerIndex + 1) &
                         (SAC_SERIAL_PORT_BUFFER_SIZE - 1));
    return STATUS_SUCCESS;
}

ULONG
NTAPI
GetMessageLineCount(IN ULONG MessageIndex)
{
    ULONG LineCount = 0;
    PWCHAR Buffer;

    /* Get the message buffer */
    Buffer = GetMessage(MessageIndex);
    if (Buffer)
    {
        /* Scan it looking for new lines, and increment the count each time */
        while (*Buffer) if (*Buffer++ == L'\n') ++LineCount;
    }

    /* Return the line count */
    return LineCount;
}

ULONG
ConvertAnsiToUnicode(
    IN PWCHAR pwch,
    IN PCHAR pch,
    IN ULONG length
    )
{
    return STATUS_NOT_IMPLEMENTED;
}

BOOLEAN
IsCmdEventRegistrationProcess(
    IN PFILE_OBJECT FileObject
    )
{
    return FALSE;
}

NTSTATUS
InvokeUserModeService(
    VOID
    )
{
    return STATUS_NOT_IMPLEMENTED;
}

NTSTATUS
TranslateMachineInformationText(
    IN PWCHAR Buffer)
{
    return STATUS_NOT_IMPLEMENTED;
}

NTSTATUS
CopyAndInsertStringAtInterval(
    IN PWCHAR SourceStr,
    IN ULONG Interval,
    IN PWCHAR InsertStr,
    OUT PWCHAR pDestStr
    )
{
    return STATUS_NOT_IMPLEMENTED;
}

NTSTATUS
RegisterSacCmdEvent(
    IN PVOID Object,
    IN PKEVENT SetupCmdEvent[]
    )
{
    return STATUS_NOT_IMPLEMENTED;
}

NTSTATUS
UnregisterSacCmdEvent(
    IN PFILE_OBJECT FileObject
    )
{
    return STATUS_NOT_IMPLEMENTED;
}