/*
 * PROJECT:         ReactOS EventLog Service
 * LICENSE:         GPL - See COPYING in the top level directory
 * FILE:            base/services/eventlog/file.c
 * PURPOSE:         Event log file support wrappers
 * COPYRIGHT:       Copyright 2005 Saveliy Tretiakov
 *                  Michael Martin
 *                  Hermes Belusca-Maito
 */

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

#include "eventlog.h"
#include <ndk/iofuncs.h>
#include <ndk/kefuncs.h>

#define NDEBUG
#include <debug.h>

/* LOG FILE LIST - GLOBALS ***************************************************/

static LIST_ENTRY LogFileListHead;
static CRITICAL_SECTION LogFileListCs;

/* LOG FILE LIST - FUNCTIONS *************************************************/

VOID LogfListInitialize(VOID)
{
    InitializeCriticalSection(&LogFileListCs);
    InitializeListHead(&LogFileListHead);
}

PLOGFILE LogfListItemByName(LPCWSTR Name)
{
    PLIST_ENTRY CurrentEntry;
    PLOGFILE Item, Result = NULL;

    ASSERT(Name);

    EnterCriticalSection(&LogFileListCs);

    CurrentEntry = LogFileListHead.Flink;
    while (CurrentEntry != &LogFileListHead)
    {
        Item = CONTAINING_RECORD(CurrentEntry, LOGFILE, ListEntry);

        if (Item->LogName && !_wcsicmp(Item->LogName, Name))
        {
            Result = Item;
            break;
        }

        CurrentEntry = CurrentEntry->Flink;
    }

    LeaveCriticalSection(&LogFileListCs);
    return Result;
}

#if 0
/* Index starting from 1 */
DWORD LogfListItemIndexByName(LPCWSTR Name)
{
    PLIST_ENTRY CurrentEntry;
    DWORD Result = 0;
    DWORD i = 1;

    ASSERT(Name);

    EnterCriticalSection(&LogFileListCs);

    CurrentEntry = LogFileListHead.Flink;
    while (CurrentEntry != &LogFileListHead)
    {
        PLOGFILE Item = CONTAINING_RECORD(CurrentEntry, LOGFILE, ListEntry);

        if (Item->LogName && !_wcsicmp(Item->LogName, Name))
        {
            Result = i;
            break;
        }

        CurrentEntry = CurrentEntry->Flink;
        i++;
    }

    LeaveCriticalSection(&LogFileListCs);
    return Result;
}
#endif

/* Index starting from 1 */
PLOGFILE LogfListItemByIndex(DWORD Index)
{
    PLIST_ENTRY CurrentEntry;
    PLOGFILE Result = NULL;
    DWORD i = 1;

    EnterCriticalSection(&LogFileListCs);

    CurrentEntry = LogFileListHead.Flink;
    while (CurrentEntry != &LogFileListHead)
    {
        if (i == Index)
        {
            Result = CONTAINING_RECORD(CurrentEntry, LOGFILE, ListEntry);
            break;
        }

        CurrentEntry = CurrentEntry->Flink;
        i++;
    }

    LeaveCriticalSection(&LogFileListCs);
    return Result;
}

DWORD LogfListItemCount(VOID)
{
    PLIST_ENTRY CurrentEntry;
    DWORD i = 0;

    EnterCriticalSection(&LogFileListCs);

    CurrentEntry = LogFileListHead.Flink;
    while (CurrentEntry != &LogFileListHead)
    {
        CurrentEntry = CurrentEntry->Flink;
        i++;
    }

    LeaveCriticalSection(&LogFileListCs);
    return i;
}

static VOID
LogfListAddItem(PLOGFILE Item)
{
    EnterCriticalSection(&LogFileListCs);
    InsertTailList(&LogFileListHead, &Item->ListEntry);
    LeaveCriticalSection(&LogFileListCs);
}

static VOID
LogfListRemoveItem(PLOGFILE Item)
{
    EnterCriticalSection(&LogFileListCs);
    RemoveEntryList(&Item->ListEntry);
    LeaveCriticalSection(&LogFileListCs);
}


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

// PELF_ALLOCATE_ROUTINE
static
PVOID NTAPI
LogfpAlloc(IN SIZE_T Size,
           IN ULONG Flags,
           IN ULONG Tag)
{
    UNREFERENCED_PARAMETER(Tag);
    return RtlAllocateHeap(GetProcessHeap(), Flags, Size);
}

// PELF_FREE_ROUTINE
static
VOID NTAPI
LogfpFree(IN PVOID Ptr,
          IN ULONG Flags,
          IN ULONG Tag)
{
    UNREFERENCED_PARAMETER(Tag);
    RtlFreeHeap(GetProcessHeap(), Flags, Ptr);
}

// PELF_FILE_READ_ROUTINE
static
NTSTATUS NTAPI
LogfpReadFile(IN  PEVTLOGFILE LogFile,
              IN  PLARGE_INTEGER FileOffset,
              OUT PVOID   Buffer,
              IN  SIZE_T  Length,
              OUT PSIZE_T ReadLength OPTIONAL)
{
    NTSTATUS Status;
    PLOGFILE pLogFile = (PLOGFILE)LogFile;
    IO_STATUS_BLOCK IoStatusBlock;

    if (ReadLength)
        *ReadLength = 0;

    Status = NtReadFile(pLogFile->FileHandle,
                        NULL,
                        NULL,
                        NULL,
                        &IoStatusBlock,
                        Buffer,
                        Length,
                        FileOffset,
                        NULL);

    if (ReadLength)
        *ReadLength = IoStatusBlock.Information;

    return Status;
}

// PELF_FILE_WRITE_ROUTINE
static
NTSTATUS NTAPI
LogfpWriteFile(IN  PEVTLOGFILE LogFile,
               IN  PLARGE_INTEGER FileOffset,
               IN  PVOID   Buffer,
               IN  SIZE_T  Length,
               OUT PSIZE_T WrittenLength OPTIONAL)
{
    NTSTATUS Status;
    PLOGFILE pLogFile = (PLOGFILE)LogFile;
    IO_STATUS_BLOCK IoStatusBlock;

    if (WrittenLength)
        *WrittenLength = 0;

    Status = NtWriteFile(pLogFile->FileHandle,
                         NULL,
                         NULL,
                         NULL,
                         &IoStatusBlock,
                         Buffer,
                         Length,
                         FileOffset,
                         NULL);

    if (WrittenLength)
        *WrittenLength = IoStatusBlock.Information;

    return Status;
}

// PELF_FILE_SET_SIZE_ROUTINE
static
NTSTATUS NTAPI
LogfpSetFileSize(IN PEVTLOGFILE LogFile,
                 IN ULONG FileSize,    // SIZE_T
                 IN ULONG OldFileSize) // SIZE_T
{
    NTSTATUS Status;
    PLOGFILE pLogFile = (PLOGFILE)LogFile;
    IO_STATUS_BLOCK IoStatusBlock;
    FILE_END_OF_FILE_INFORMATION FileEofInfo;
    FILE_ALLOCATION_INFORMATION FileAllocInfo;

    UNREFERENCED_PARAMETER(OldFileSize);

    // FIXME: Should we round up FileSize ??

    FileEofInfo.EndOfFile.QuadPart = FileSize;
    Status = NtSetInformationFile(pLogFile->FileHandle,
                                  &IoStatusBlock,
                                  &FileEofInfo,
                                  sizeof(FileEofInfo),
                                  FileEndOfFileInformation);
    if (!NT_SUCCESS(Status))
        return Status;

    FileAllocInfo.AllocationSize.QuadPart = FileSize;
    Status = NtSetInformationFile(pLogFile->FileHandle,
                                  &IoStatusBlock,
                                  &FileAllocInfo,
                                  sizeof(FileAllocInfo),
                                  FileAllocationInformation);

    return Status;
}

// PELF_FILE_FLUSH_ROUTINE
static
NTSTATUS NTAPI
LogfpFlushFile(IN PEVTLOGFILE LogFile,
               IN PLARGE_INTEGER FileOffset,
               IN ULONG Length)
{
    PLOGFILE pLogFile = (PLOGFILE)LogFile;
    IO_STATUS_BLOCK IoStatusBlock;

    UNREFERENCED_PARAMETER(FileOffset);
    UNREFERENCED_PARAMETER(Length);

    return NtFlushBuffersFile(pLogFile->FileHandle, &IoStatusBlock);
}

NTSTATUS
LogfCreate(PLOGFILE* LogFile,
           PCWSTR    LogName,
           PUNICODE_STRING FileName,
           ULONG     MaxSize,
           ULONG     Retention,
           BOOLEAN   Permanent,
           BOOLEAN   Backup)
{
    NTSTATUS Status;
    OBJECT_ATTRIBUTES ObjectAttributes;
    IO_STATUS_BLOCK IoStatusBlock;
    FILE_STANDARD_INFORMATION FileStdInfo;
    PLOGFILE pLogFile;
    SIZE_T LogNameLen;
    BOOLEAN CreateNew;

    pLogFile = LogfpAlloc(sizeof(*pLogFile), HEAP_ZERO_MEMORY, TAG_ELF);
    if (!pLogFile)
    {
        DPRINT1("Cannot allocate heap!\n");
        return STATUS_NO_MEMORY;
    }

    LogNameLen = (LogName ? wcslen(LogName) : 0) + 1;
    pLogFile->LogName = LogfpAlloc(LogNameLen * sizeof(WCHAR), HEAP_ZERO_MEMORY, 0);
    if (pLogFile->LogName == NULL)
    {
        DPRINT1("Cannot allocate heap\n");
        Status = STATUS_NO_MEMORY;
        goto Quit;
    }

    if (LogName)
        StringCchCopyW(pLogFile->LogName, LogNameLen, LogName);

    InitializeObjectAttributes(&ObjectAttributes,
                               FileName,
                               OBJ_CASE_INSENSITIVE,
                               NULL,
                               NULL);

    DPRINT("Going to create or open %wZ\n", FileName);
    Status = NtCreateFile(&pLogFile->FileHandle,
                          Backup ? (GENERIC_READ | SYNCHRONIZE)
                                 : (GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE),
                          &ObjectAttributes,
                          &IoStatusBlock,
                          NULL,
                          FILE_ATTRIBUTE_NORMAL,
                          FILE_SHARE_READ,
                          Backup ? FILE_OPEN : FILE_OPEN_IF,
                          FILE_SYNCHRONOUS_IO_NONALERT,
                          NULL,
                          0);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("Cannot create file `%wZ' (Status 0x%08lx)\n", FileName, Status);
        goto Quit;
    }

    CreateNew = (IoStatusBlock.Information == FILE_CREATED);
    DPRINT("%wZ %s successfully\n", FileName, CreateNew ? "created" : "opened");

    /*
     * Retrieve the log file size and check whether the file is not too large;
     * this log format only supports files of theoretical size < 0xFFFFFFFF .
     *
     * As it happens that, on Windows (and ReactOS), retrieving the End-Of-File
     * information using NtQueryInformationFile with the FileEndOfFileInformation
     * class is invalid (who knows why...), use instead the FileStandardInformation
     * class, and the EndOfFile member of the returned FILE_STANDARD_INFORMATION
     * structure will give the desired information.
     */
    Status = NtQueryInformationFile(pLogFile->FileHandle,
                                    &IoStatusBlock,
                                    &FileStdInfo,
                                    sizeof(FileStdInfo),
                                    FileStandardInformation);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("EventLog: NtQueryInformationFile failed (Status 0x%08lx)\n", Status);
        goto Quit;
    }
    if (FileStdInfo.EndOfFile.HighPart != 0)
    {
        DPRINT1("EventLog: Log `%wZ' is too large.\n", FileName);
        Status = STATUS_EVENTLOG_FILE_CORRUPT; // STATUS_FILE_TOO_LARGE;
        goto Quit;
    }

    DPRINT("Initializing LogFile `%S'\n", pLogFile->LogName);

    Status = ElfCreateFile(&pLogFile->LogFile,
                           FileName,
                           FileStdInfo.EndOfFile.LowPart,
                           MaxSize,
                           Retention,
                           CreateNew,
                           Backup,
                           LogfpAlloc,
                           LogfpFree,
                           LogfpSetFileSize,
                           LogfpWriteFile,
                           LogfpReadFile,
                           LogfpFlushFile);
    if (!NT_SUCCESS(Status))
        goto Quit;

    pLogFile->Permanent = Permanent;

    RtlInitializeResource(&pLogFile->Lock);

    LogfListAddItem(pLogFile);

Quit:
    if (!NT_SUCCESS(Status))
    {
        if (pLogFile->FileHandle != NULL)
            NtClose(pLogFile->FileHandle);

        if (pLogFile->LogName)
            LogfpFree(pLogFile->LogName, 0, 0);

        LogfpFree(pLogFile, 0, TAG_ELF);
    }
    else
    {
        *LogFile = pLogFile;
    }

    return Status;
}

VOID
LogfClose(PLOGFILE LogFile,
          BOOLEAN  ForceClose)
{
    if (LogFile == NULL)
        return;

    if (!ForceClose && LogFile->Permanent)
        return;

    RtlAcquireResourceExclusive(&LogFile->Lock, TRUE);

    LogfListRemoveItem(LogFile);

    ElfCloseFile(&LogFile->LogFile);
    NtClose(LogFile->FileHandle);
    LogfpFree(LogFile->LogName, 0, 0);

    RtlDeleteResource(&LogFile->Lock);

    LogfpFree(LogFile, 0, TAG_ELF);

    return;
}

VOID LogfCloseAll(VOID)
{
    EnterCriticalSection(&LogFileListCs);

    while (!IsListEmpty(&LogFileListHead))
    {
        LogfClose(CONTAINING_RECORD(LogFileListHead.Flink, LOGFILE, ListEntry), TRUE);
    }

    LeaveCriticalSection(&LogFileListCs);

    DeleteCriticalSection(&LogFileListCs);
}

NTSTATUS
LogfClearFile(PLOGFILE LogFile,
              PUNICODE_STRING BackupFileName)
{
    NTSTATUS Status;

    /* Lock the log file exclusive */
    RtlAcquireResourceExclusive(&LogFile->Lock, TRUE);

    if (BackupFileName->Length > 0)
    {
        /* Write a backup file */
        Status = LogfBackupFile(LogFile, BackupFileName);
        if (!NT_SUCCESS(Status))
        {
            DPRINT1("LogfBackupFile failed (Status 0x%08lx)\n", Status);
            goto Quit;
        }
    }

    Status = ElfReCreateFile(&LogFile->LogFile);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("LogfInitializeNew failed (Status 0x%08lx)\n", Status);
    }

Quit:
    /* Unlock the log file */
    RtlReleaseResource(&LogFile->Lock);
    return Status;
}

NTSTATUS
LogfBackupFile(PLOGFILE LogFile,
               PUNICODE_STRING BackupFileName)
{
    NTSTATUS Status;
    LOGFILE BackupLogFile;
    OBJECT_ATTRIBUTES ObjectAttributes;
    IO_STATUS_BLOCK IoStatusBlock;

    DPRINT("LogfBackupFile(%p, %wZ)\n", LogFile, BackupFileName);

    /* Lock the log file shared */
    RtlAcquireResourceShared(&LogFile->Lock, TRUE);

    InitializeObjectAttributes(&ObjectAttributes,
                               BackupFileName,
                               OBJ_CASE_INSENSITIVE,
                               NULL,
                               NULL);

    Status = NtCreateFile(&BackupLogFile.FileHandle,
                          GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE,
                          &ObjectAttributes,
                          &IoStatusBlock,
                          NULL,
                          FILE_ATTRIBUTE_NORMAL,
                          FILE_SHARE_READ,
                          FILE_CREATE,
                          FILE_WRITE_THROUGH | FILE_SYNCHRONOUS_IO_NONALERT,
                          NULL,
                          0);
    if (!NT_SUCCESS(Status))
    {
        DPRINT("Cannot create backup file `%wZ' (Status 0x%08lx)\n", BackupFileName, Status);
        goto Quit;
    }

    Status = ElfBackupFile(&LogFile->LogFile,
                           &BackupLogFile.LogFile);

Quit:
    /* Close the backup file */
    if (BackupLogFile.FileHandle != NULL)
        NtClose(BackupLogFile.FileHandle);

    /* Unlock the log file */
    RtlReleaseResource(&LogFile->Lock);

    return Status;
}


static NTSTATUS
ReadRecord(IN  PEVTLOGFILE LogFile,
           IN  ULONG RecordNumber,
           OUT PEVENTLOGRECORD Record,
           IN  SIZE_T  BufSize, // Length
           OUT PSIZE_T BytesRead OPTIONAL,
           OUT PSIZE_T BytesNeeded OPTIONAL,
           IN  BOOLEAN Ansi)
{
    NTSTATUS Status;
    PEVENTLOGRECORD UnicodeBuffer = NULL;
    PEVENTLOGRECORD Src, Dst;
    ANSI_STRING StringA;
    UNICODE_STRING StringW;
    PVOID SrcPtr, DstPtr;
    DWORD i;
    DWORD dwPadding;
    DWORD dwRecordLength;
    PDWORD pLength;

    if (!Ansi)
    {
        return ElfReadRecord(LogFile,
                             RecordNumber,
                             Record,
                             BufSize,
                             BytesRead,
                             BytesNeeded);
    }

    if (BytesRead)
        *BytesRead = 0;

    if (BytesNeeded)
        *BytesNeeded = 0;

    UnicodeBuffer = LogfpAlloc(BufSize, HEAP_ZERO_MEMORY, TAG_ELF_BUF);
    if (UnicodeBuffer == NULL)
    {
        DPRINT1("Alloc failed!\n");
        return STATUS_NO_MEMORY;
    }

    Status = ElfReadRecord(LogFile,
                           RecordNumber,
                           UnicodeBuffer,
                           BufSize,
                           BytesRead,
                           BytesNeeded);
    if (!NT_SUCCESS(Status))
        goto Quit;

    Src = UnicodeBuffer;
    Dst = Record;

    Dst->Reserved      = Src->Reserved;
    Dst->RecordNumber  = Src->RecordNumber;
    Dst->TimeGenerated = Src->TimeGenerated;
    Dst->TimeWritten   = Src->TimeWritten;
    Dst->EventID       = Src->EventID;
    Dst->EventType     = Src->EventType;
    Dst->EventCategory = Src->EventCategory;
    Dst->NumStrings    = Src->NumStrings;
    Dst->UserSidLength = Src->UserSidLength;
    Dst->DataLength    = Src->DataLength;

    SrcPtr = (PVOID)((ULONG_PTR)Src + sizeof(EVENTLOGRECORD));
    DstPtr = (PVOID)((ULONG_PTR)Dst + sizeof(EVENTLOGRECORD));

    /* Convert the module name */
    RtlInitUnicodeString(&StringW, SrcPtr);
    Status = RtlUnicodeStringToAnsiString(&StringA, &StringW, TRUE);
    if (NT_SUCCESS(Status))
    {
        RtlCopyMemory(DstPtr, StringA.Buffer, StringA.MaximumLength);
        DstPtr = (PVOID)((ULONG_PTR)DstPtr + StringA.MaximumLength);

        RtlFreeAnsiString(&StringA);
    }
    else
    {
        RtlZeroMemory(DstPtr, StringW.MaximumLength / sizeof(WCHAR));
        DstPtr = (PVOID)((ULONG_PTR)DstPtr + StringW.MaximumLength / sizeof(WCHAR));
    }
    SrcPtr = (PVOID)((ULONG_PTR)SrcPtr + StringW.MaximumLength);

    /* Convert the computer name */
    RtlInitUnicodeString(&StringW, SrcPtr);
    Status = RtlUnicodeStringToAnsiString(&StringA, &StringW, TRUE);
    if (NT_SUCCESS(Status))
    {
        RtlCopyMemory(DstPtr, StringA.Buffer, StringA.MaximumLength);
        DstPtr = (PVOID)((ULONG_PTR)DstPtr + StringA.MaximumLength);

        RtlFreeAnsiString(&StringA);
    }
    else
    {
        RtlZeroMemory(DstPtr, StringW.MaximumLength / sizeof(WCHAR));
        DstPtr = (PVOID)((ULONG_PTR)DstPtr + StringW.MaximumLength / sizeof(WCHAR));
    }

    /* Add the padding and the User SID */
    dwPadding = sizeof(ULONG) - (((ULONG_PTR)DstPtr - (ULONG_PTR)Dst) % sizeof(ULONG));
    RtlZeroMemory(DstPtr, dwPadding);

    SrcPtr = (PVOID)((ULONG_PTR)Src + Src->UserSidOffset);
    DstPtr = (PVOID)((ULONG_PTR)DstPtr + dwPadding);

    Dst->UserSidOffset = (DWORD)((ULONG_PTR)DstPtr - (ULONG_PTR)Dst);
    RtlCopyMemory(DstPtr, SrcPtr, Src->UserSidLength);

    /* Convert the strings */
    SrcPtr = (PVOID)((ULONG_PTR)Src + Src->StringOffset);
    DstPtr = (PVOID)((ULONG_PTR)DstPtr + Src->UserSidLength);
    Dst->StringOffset = (DWORD)((ULONG_PTR)DstPtr - (ULONG_PTR)Dst);

    for (i = 0; i < Dst->NumStrings; i++)
    {
        RtlInitUnicodeString(&StringW, SrcPtr);
        Status = RtlUnicodeStringToAnsiString(&StringA, &StringW, TRUE);
        if (NT_SUCCESS(Status))
        {
            RtlCopyMemory(DstPtr, StringA.Buffer, StringA.MaximumLength);
            DstPtr = (PVOID)((ULONG_PTR)DstPtr + StringA.MaximumLength);

            RtlFreeAnsiString(&StringA);
        }
        else
        {
            RtlZeroMemory(DstPtr, StringW.MaximumLength / sizeof(WCHAR));
            DstPtr = (PVOID)((ULONG_PTR)DstPtr + StringW.MaximumLength / sizeof(WCHAR));
        }
        SrcPtr = (PVOID)((ULONG_PTR)SrcPtr + StringW.MaximumLength);
    }

    /* Copy the binary data */
    SrcPtr = (PVOID)((ULONG_PTR)Src + Src->DataOffset);
    Dst->DataOffset = (ULONG_PTR)DstPtr - (ULONG_PTR)Dst;
    RtlCopyMemory(DstPtr, SrcPtr, Src->DataLength);
    DstPtr = (PVOID)((ULONG_PTR)DstPtr + Src->DataLength);

    /* Add the padding */
    dwPadding = sizeof(ULONG) - (((ULONG_PTR)DstPtr - (ULONG_PTR)Dst) % sizeof(ULONG));
    RtlZeroMemory(DstPtr, dwPadding);

    /* Set the record length at the beginning and the end of the record */
    dwRecordLength = (DWORD)((ULONG_PTR)DstPtr + dwPadding + sizeof(ULONG) - (ULONG_PTR)Dst);
    Dst->Length = dwRecordLength;
    pLength = (PDWORD)((ULONG_PTR)DstPtr + dwPadding);
    *pLength = dwRecordLength;

    if (BytesRead)
        *BytesRead = dwRecordLength;

    Status = STATUS_SUCCESS;

Quit:
    LogfpFree(UnicodeBuffer, 0, TAG_ELF_BUF);

    return Status;
}

/*
 * NOTE:
 *   'RecordNumber' is a pointer to the record number at which the read operation
 *   should start. If the record number is 0 and the flags given in the 'Flags'
 *   parameter contain EVENTLOG_SEQUENTIAL_READ, an adequate record number is
 *   computed.
 */
NTSTATUS
LogfReadEvents(PLOGFILE LogFile,
               ULONG    Flags,
               PULONG   RecordNumber,
               ULONG    BufSize,
               PBYTE    Buffer,
               PULONG   BytesRead,
               PULONG   BytesNeeded,
               BOOLEAN  Ansi)
{
    NTSTATUS Status;
    ULONG RecNum;
    SIZE_T ReadLength, NeededSize;
    ULONG BufferUsage;

    /* Parameters validation */

    /* EVENTLOG_SEQUENTIAL_READ and EVENTLOG_SEEK_READ are mutually exclusive */
    if ((Flags & EVENTLOG_SEQUENTIAL_READ) && (Flags & EVENTLOG_SEEK_READ))
        return STATUS_INVALID_PARAMETER;

    if (!(Flags & EVENTLOG_SEQUENTIAL_READ) && !(Flags & EVENTLOG_SEEK_READ))
        return STATUS_INVALID_PARAMETER;

    /* EVENTLOG_FORWARDS_READ and EVENTLOG_BACKWARDS_READ are mutually exclusive */
    if ((Flags & EVENTLOG_FORWARDS_READ) && (Flags & EVENTLOG_BACKWARDS_READ))
        return STATUS_INVALID_PARAMETER;

    if (!(Flags & EVENTLOG_FORWARDS_READ) && !(Flags & EVENTLOG_BACKWARDS_READ))
        return STATUS_INVALID_PARAMETER;

    if (!Buffer || !BytesRead || !BytesNeeded)
        return STATUS_INVALID_PARAMETER;

    /* In seek read mode, a record number of 0 is invalid */
    if (!(Flags & EVENTLOG_SEQUENTIAL_READ) && (*RecordNumber == 0))
        return STATUS_INVALID_PARAMETER;

    /* Lock the log file shared */
    RtlAcquireResourceShared(&LogFile->Lock, TRUE);

    /*
     * In sequential read mode, a record number of 0 means we need
     * to determine where to start the read operation. Otherwise
     * we just use the provided record number.
     */
    if ((Flags & EVENTLOG_SEQUENTIAL_READ) && (*RecordNumber == 0))
    {
        if (Flags & EVENTLOG_FORWARDS_READ)
        {
            *RecordNumber = ElfGetOldestRecord(&LogFile->LogFile);
        }
        else // if (Flags & EVENTLOG_BACKWARDS_READ)
        {
            *RecordNumber = ElfGetCurrentRecord(&LogFile->LogFile) - 1;
        }
    }

    RecNum = *RecordNumber;

    *BytesRead = 0;
    *BytesNeeded = 0;

    BufferUsage = 0;
    do
    {
        Status = ReadRecord(&LogFile->LogFile,
                            RecNum,
                            (PEVENTLOGRECORD)(Buffer + BufferUsage),
                            BufSize - BufferUsage,
                            &ReadLength,
                            &NeededSize,
                            Ansi);
        if (Status == STATUS_NOT_FOUND)
        {
            if (BufferUsage == 0)
            {
                Status = STATUS_END_OF_FILE;
                goto Quit;
            }
            else
            {
                break;
            }
        }
        else
        if (Status == STATUS_BUFFER_TOO_SMALL)
        {
            if (BufferUsage == 0)
            {
                *BytesNeeded = NeededSize;
                // Status = STATUS_BUFFER_TOO_SMALL;
                goto Quit;
            }
            else
            {
                break;
            }
        }
        else
        if (!NT_SUCCESS(Status))
        {
            DPRINT1("ElfReadRecord failed (Status 0x%08lx)\n", Status);
            goto Quit;
        }

        /* Go to the next event record */
        /*
         * NOTE: This implicitly supposes that all the other record numbers
         * are consecutive (and do not jump than more than one unit); but if
         * it is not the case, then we would prefer here to call some
         * "get_next_record_number" function.
         */
        if (Flags & EVENTLOG_FORWARDS_READ)
            RecNum++;
        else // if (Flags & EVENTLOG_BACKWARDS_READ)
            RecNum--;

        BufferUsage += ReadLength;
    }
    while (BufferUsage <= BufSize);

    *BytesRead = BufferUsage;
    *RecordNumber = RecNum;

    Status = STATUS_SUCCESS;

Quit:
    /* Unlock the log file */
    RtlReleaseResource(&LogFile->Lock);

    if (!NT_SUCCESS(Status))
        DPRINT1("LogfReadEvents failed (Status 0x%08lx)\n", Status);

    return Status;
}

NTSTATUS
LogfWriteRecord(PLOGFILE LogFile,
                PEVENTLOGRECORD Record,
                SIZE_T BufSize)
{
    NTSTATUS Status;
    LARGE_INTEGER SystemTime;

    // ASSERT(sizeof(*Record) == sizeof(RecBuf));

    if (!Record || BufSize < sizeof(*Record))
        return STATUS_INVALID_PARAMETER;

    /* Lock the log file exclusive */
    RtlAcquireResourceExclusive(&LogFile->Lock, TRUE);

    /*
     * Retrieve the record written time now, that will also be compared
     * with the existing events timestamps in case the log is wrapping.
     */
    NtQuerySystemTime(&SystemTime);
    RtlTimeToSecondsSince1970(&SystemTime, &Record->TimeWritten);

    Status = ElfWriteRecord(&LogFile->LogFile, Record, BufSize);
    if (Status == STATUS_LOG_FILE_FULL)
    {
        /* The event log file is full, queue a message box for the user and exit */
        // TODO!
        DPRINT1("Log file `%S' is full!\n", LogFile->LogName);
    }

    /* Unlock the log file */
    RtlReleaseResource(&LogFile->Lock);

    return Status;
}


PEVENTLOGRECORD
LogfAllocAndBuildNewRecord(PSIZE_T pRecSize,
                           ULONG   Time,
                           USHORT  wType,
                           USHORT  wCategory,
                           ULONG   dwEventId,
                           PUNICODE_STRING SourceName,
                           PUNICODE_STRING ComputerName,
                           ULONG   dwSidLength,
                           PSID    pUserSid,
                           USHORT  wNumStrings,
                           PWSTR   pStrings,
                           ULONG   dwDataSize,
                           PVOID   pRawData)
{
    SIZE_T RecSize;
    SIZE_T SourceNameSize, ComputerNameSize, StringLen;
    PBYTE Buffer;
    PEVENTLOGRECORD pRec;
    PWSTR str;
    UINT i, pos;

    SourceNameSize   = (SourceName   && SourceName->Buffer)   ? SourceName->Length   : 0;
    ComputerNameSize = (ComputerName && ComputerName->Buffer) ? ComputerName->Length : 0;

    RecSize = sizeof(EVENTLOGRECORD) + /* Add the sizes of the strings, NULL-terminated */
        SourceNameSize + ComputerNameSize + 2*sizeof(UNICODE_NULL);

    /* Align on DWORD boundary for the SID */
    RecSize = ROUND_UP(RecSize, sizeof(ULONG));

    RecSize += dwSidLength;

    /* Add the sizes for the strings array */
    ASSERT((pStrings == NULL && wNumStrings == 0) ||
           (pStrings != NULL && wNumStrings >= 0));
    for (i = 0, str = pStrings; i < wNumStrings; i++)
    {
        StringLen = wcslen(str) + 1; // str must be != NULL
        RecSize += StringLen * sizeof(WCHAR);
        str += StringLen;
    }

    /* Add the data size */
    RecSize += dwDataSize;

    /* Align on DWORD boundary for the full structure */
    RecSize = ROUND_UP(RecSize, sizeof(ULONG));

    /* Size of the trailing 'Length' member */
    RecSize += sizeof(ULONG);

    Buffer = RtlAllocateHeap(GetProcessHeap(), HEAP_ZERO_MEMORY, RecSize);
    if (!Buffer)
    {
        DPRINT1("Cannot allocate heap!\n");
        return NULL;
    }

    pRec = (PEVENTLOGRECORD)Buffer;
    pRec->Length = RecSize;
    pRec->Reserved = LOGFILE_SIGNATURE;

    /*
     * Do not assign here any precomputed record number to the event record.
     * The true record number will be assigned atomically and sequentially in
     * LogfWriteRecord, so that all the event records will have consistent and
     * unique record numbers.
     */
    pRec->RecordNumber = 0;

    /*
     * Set the generated time, and temporarily set the written time
     * with the generated time.
     */
    pRec->TimeGenerated = Time;
    pRec->TimeWritten   = Time;

    pRec->EventID = dwEventId;
    pRec->EventType = wType;
    pRec->EventCategory = wCategory;

    pos = sizeof(EVENTLOGRECORD);

    /* NOTE: Equivalents of RtlStringCbCopyUnicodeString calls */
    if (SourceNameSize)
    {
        StringCbCopyNW((PWSTR)(Buffer + pos), SourceNameSize + sizeof(UNICODE_NULL),
                       SourceName->Buffer, SourceNameSize);
    }
    pos += SourceNameSize + sizeof(UNICODE_NULL);
    if (ComputerNameSize)
    {
        StringCbCopyNW((PWSTR)(Buffer + pos), ComputerNameSize + sizeof(UNICODE_NULL),
                       ComputerName->Buffer, ComputerNameSize);
    }
    pos += ComputerNameSize + sizeof(UNICODE_NULL);

    /* Align on DWORD boundary for the SID */
    pos = ROUND_UP(pos, sizeof(ULONG));

    pRec->UserSidLength = 0;
    pRec->UserSidOffset = 0;
    if (dwSidLength)
    {
        RtlCopyMemory(Buffer + pos, pUserSid, dwSidLength);
        pRec->UserSidLength = dwSidLength;
        pRec->UserSidOffset = pos;
        pos += dwSidLength;
    }

    pRec->StringOffset = pos;
    for (i = 0, str = pStrings; i < wNumStrings; i++)
    {
        StringLen = wcslen(str) + 1; // str must be != NULL
        StringCchCopyW((PWSTR)(Buffer + pos), StringLen, str);
        str += StringLen;
        pos += StringLen * sizeof(WCHAR);
    }
    pRec->NumStrings = wNumStrings;

    pRec->DataLength = 0;
    pRec->DataOffset = 0;
    if (dwDataSize)
    {
        RtlCopyMemory(Buffer + pos, pRawData, dwDataSize);
        pRec->DataLength = dwDataSize;
        pRec->DataOffset = pos;
        pos += dwDataSize;
    }

    /* Align on DWORD boundary for the full structure */
    pos = ROUND_UP(pos, sizeof(ULONG));

    /* Initialize the trailing 'Length' member */
    *((PDWORD)(Buffer + pos)) = RecSize;

    *pRecSize = RecSize;
    return pRec;
}

VOID
LogfReportEvent(USHORT wType,
                USHORT wCategory,
                ULONG  dwEventId,
                USHORT wNumStrings,
                PWSTR  pStrings,
                ULONG  dwDataSize,
                PVOID  pRawData)
{
    NTSTATUS Status;
    UNICODE_STRING SourceName, ComputerName;
    PEVENTLOGRECORD LogBuffer;
    LARGE_INTEGER SystemTime;
    ULONG Time;
    SIZE_T RecSize;
    DWORD dwComputerNameLength;
    WCHAR szComputerName[MAX_COMPUTERNAME_LENGTH + 1];

    if (!EventLogSource)
        return;

    RtlInitUnicodeString(&SourceName, EventLogSource->szName);

    dwComputerNameLength = ARRAYSIZE(szComputerName);
    if (!GetComputerNameW(szComputerName, &dwComputerNameLength))
        szComputerName[0] = L'\0';

    RtlInitUnicodeString(&ComputerName, szComputerName);

    NtQuerySystemTime(&SystemTime);
    RtlTimeToSecondsSince1970(&SystemTime, &Time);

    LogBuffer = LogfAllocAndBuildNewRecord(&RecSize,
                                           Time,
                                           wType,
                                           wCategory,
                                           dwEventId,
                                           &SourceName,
                                           &ComputerName,
                                           0,
                                           NULL,
                                           wNumStrings,
                                           pStrings,
                                           dwDataSize,
                                           pRawData);
    if (LogBuffer == NULL)
    {
        DPRINT1("LogfAllocAndBuildNewRecord failed!\n");
        return;
    }

    Status = LogfWriteRecord(EventLogSource->LogFile, LogBuffer, RecSize);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("ERROR writing to event log `%S' (Status 0x%08lx)\n",
                EventLogSource->LogFile->LogName, Status);
    }

    LogfFreeRecord(LogBuffer);
}
