/*++

Copyright (c) 1990  Microsoft Corporation

Module Name:

    elfexts.c

Abstract:

    This function contains the eventlog ntsd debugger extensions

Author:

    Dan Hinsley (DanHi) 22-May-1993

Revision History:

--*/

#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <ntsdexts.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <time.h>
#include <elf.h>
#include <elfdef.h>
#include <elfcommn.h>
#include <elfproto.h>
#include <svcsp.h>
#include <elfextrn.h>

//#define DbgPrint(_x_) (lpOutputRoutine) _x_
#define DbgPrint(_x_)
#define MAX_NAME 256
#define printf (lpOutputRoutine)
#define GET_DATA(DebugeeAddr, LocalAddr, Length) \
    Status = ReadProcessMemory(                  \
                GlobalhCurrentProcess,           \
                (LPVOID)DebugeeAddr,             \
                LocalAddr,                       \
                Length,                          \
                NULL                             \
                );

PNTSD_OUTPUT_ROUTINE lpOutputRoutine;
PNTSD_GET_EXPRESSION lpGetExpressionRoutine;
PNTSD_CHECK_CONTROL_C lpCheckControlCRoutine;

HANDLE GlobalhCurrentProcess;
BOOL Status;

//
// Initialize the global function pointers
//

VOID
InitFunctionPointers(
    HANDLE hCurrentProcess,
    PNTSD_EXTENSION_APIS lpExtensionApis
    )
{
    //
    // Load these to speed access if we haven't already
    //

    if (!lpOutputRoutine) {
        lpOutputRoutine = lpExtensionApis->lpOutputRoutine;
        lpGetExpressionRoutine = lpExtensionApis->lpGetExpressionRoutine;
        lpCheckControlCRoutine = lpExtensionApis->lpCheckControlCRoutine;
    }

    //
    // Stick this in a global
    //

    GlobalhCurrentProcess = hCurrentProcess;
}

LPWSTR
GetUnicodeString(
    PUNICODE_STRING pUnicodeString
    )
{
    DWORD Pointer;
    UNICODE_STRING UnicodeString;

    GET_DATA(pUnicodeString, &UnicodeString, sizeof(UNICODE_STRING))
    Pointer = (DWORD) UnicodeString.Buffer;
    UnicodeString.Buffer = (LPWSTR) LocalAlloc(LMEM_ZEROINIT,
        UnicodeString.Length + sizeof(WCHAR));
    GET_DATA(Pointer, UnicodeString.Buffer, UnicodeString.Length)

    return(UnicodeString.Buffer);
}

DWORD
GetLogFileAddress(
    LPSTR LogFileName,
    PLOGFILE LogFile
    )
{
    ANSI_STRING AnsiString;
    UNICODE_STRING UnicodeString;
    DWORD Pointer;
    DWORD LogFileAnchor;
    LPWSTR ModuleName;

    //
    // Convert the string to UNICODE
    //

    RtlInitAnsiString(&AnsiString, LogFileName);
    RtlAnsiStringToUnicodeString(&UnicodeString, &AnsiString, TRUE);

    //
    // Walk the logfile list looking for a match
    //

    LogFileAnchor = (lpGetExpressionRoutine)("LogFilesHead");

    GET_DATA(LogFileAnchor, &Pointer, sizeof(DWORD))

    while (Pointer != LogFileAnchor) {
        GET_DATA(Pointer, LogFile, sizeof(LOGFILE))
        ModuleName = GetUnicodeString(LogFile->LogModuleName);
        if (!_wcsicmp(ModuleName, UnicodeString.Buffer)) {
            break;
        }
        LocalFree(ModuleName);
        Pointer = (DWORD) LogFile->FileList.Flink;
    }

    RtlFreeUnicodeString(&UnicodeString);

    if (Pointer == LogFileAnchor) {
        return(0);
    }
    else {
        LocalFree(ModuleName);
        return(Pointer);
    }
}

//
// Dump an individual record
//

DWORD
DumpRecord(
    DWORD Record,
    DWORD RecordNumber,
    DWORD StartOfFile,
    DWORD EndOfFile
    )
{
    DWORD BufferLen;
    PCHAR TimeBuffer;
    PEVENTLOGRECORD EventLogRecord;
    LPWSTR Module;
    LPWSTR Computer;
    DWORD FirstPiece = 0;

    GET_DATA(Record, &BufferLen, sizeof(DWORD))

    //
    // See if it's a ELF_SKIP_DWORD, and if it is, return the top of the
    // file
    //

    if (BufferLen == ELF_SKIP_DWORD) {
        return(StartOfFile + sizeof(ELF_LOGFILE_HEADER));
    }

    //
    // See if it's the EOF record
    //

    if (BufferLen == ELFEOFRECORDSIZE) {
        return(0);
    }

    BufferLen += sizeof(DWORD); // get room for length of next record
    EventLogRecord = (PEVENTLOGRECORD) LocalAlloc(LMEM_ZEROINIT, BufferLen);

    //
    // If the record wraps, grab it piecemeal
    //

    if (EndOfFile && BufferLen + Record > EndOfFile) {
        FirstPiece = EndOfFile - Record;
        GET_DATA(Record, EventLogRecord, FirstPiece);
        GET_DATA((StartOfFile + sizeof(ELF_LOGFILE_HEADER)),
            ((PBYTE) EventLogRecord + FirstPiece), BufferLen - FirstPiece)
    }
    else {
        GET_DATA(Record, EventLogRecord, BufferLen)
    }

    //
    // If it's greater than the starting record, print it out
    //

    if (EventLogRecord->RecordNumber >= RecordNumber) {
        printf("\nRecord %d is %d [0x%X] bytes long starting at 0x%X\n",
            EventLogRecord->RecordNumber, EventLogRecord->Length,
            EventLogRecord->Length, Record);
        Module = (LPWSTR)(EventLogRecord+1);
        Computer = (LPWSTR)((PBYTE) Module + ((wcslen(Module) + 1) * sizeof(WCHAR)));
        printf("\tGenerated by %ws from system %ws\n", Module, Computer);

        TimeBuffer = ctime((time_t *)&(EventLogRecord->TimeGenerated));
        if (TimeBuffer) {
            printf("\tGenerated at %s", TimeBuffer);
        }
        else {
            printf("\tGenerated time field is blank\n");
        }
        TimeBuffer = ctime((time_t *)&(EventLogRecord->TimeWritten));
        if (TimeBuffer) {
            printf("\tWritten at %s", TimeBuffer);
        }
        else {
            printf("\tTime written field is blank\n");
        }

        printf("\tEvent Id = %d\n", EventLogRecord->EventID);
        printf("\tEventType = ");
        switch (EventLogRecord->EventType) {
            case EVENTLOG_SUCCESS:
                printf("Success\n");
                break;
            case EVENTLOG_ERROR_TYPE:
                printf("Error\n");
                break;
            case EVENTLOG_WARNING_TYPE:
                printf("Warning\n");
                break;
            case EVENTLOG_INFORMATION_TYPE:
                printf("Information\n");
                break;
            case EVENTLOG_AUDIT_SUCCESS:
                printf("Audit Success\n");
                break;
            case EVENTLOG_AUDIT_FAILURE:
                printf("Audit Failure\n");
                break;
            default:
                printf("Invalid value 0x%X\n", EventLogRecord->EventType);
        }
        printf("\t%d strings at offset 0x%X\n", EventLogRecord->NumStrings,
            EventLogRecord->StringOffset);
        printf("\t%d bytes of data at offset 0x%X\n", EventLogRecord->DataLength,
            EventLogRecord->DataOffset);
    }

    if (FirstPiece) {
        Record = StartOfFile + sizeof(ELF_LOGFILE_HEADER) + BufferLen -
            FirstPiece;
    }
    else {
        Record += EventLogRecord->Length;
    }

    LocalFree(EventLogRecord);
    return(Record);
}

//
// Dump a record, or all records, or n records
//

VOID
record(
    HANDLE hCurrentProcess,
    HANDLE hCurrentThread,
    DWORD dwCurrentPc,
    PNTSD_EXTENSION_APIS lpExtensionApis,
    LPSTR lpArgumentString
    )
{
    DWORD Pointer;
    LOGFILE LogFile;
    DWORD StartOfFile;
    DWORD EndOfFile = 0;
    DWORD RecordNumber = 0;

    InitFunctionPointers(hCurrentProcess, lpExtensionApis);

    //
    // Evaluate the argument string to get the address of
    // the record to dump.
    //

    if (lpArgumentString && *lpArgumentString) {
        if (*lpArgumentString == '.') {
            if (GetLogFileAddress(lpArgumentString+1, &LogFile) == 0) {
                printf("Logfile %s not found\n", lpArgumentString+1);
                return;
            }
            Pointer = ((DWORD) (LogFile.BaseAddress)) + LogFile.BeginRecord;
        }
        else if (*lpArgumentString == '#') {
            RecordNumber = atoi(lpArgumentString + 1);
            printf("Dumping records starting at record #%d\n", RecordNumber);
            lpArgumentString = NULL;
        }
        else if (*lpArgumentString) {
            Pointer = (lpGetExpressionRoutine)(lpArgumentString);
        }
        else {
            printf("Invalid lead character 0x%02X\n", *lpArgumentString);
            return;
        }
    }

    if (!lpArgumentString || *lpArgumentString) {
        if (GetLogFileAddress("system", &LogFile) == 0) {
            printf("System Logfile not found\n");
            return;
        }
        Pointer = ((DWORD) (LogFile.BaseAddress)) + LogFile.BeginRecord;
    }

    StartOfFile = (DWORD) LogFile.BaseAddress;
    EndOfFile = (DWORD) LogFile.BaseAddress + LogFile.ActualMaxFileSize;

    //
    // Dump records starting wherever they told us to
    //

    while (Pointer < EndOfFile && Pointer && !(lpCheckControlCRoutine)()) {
        Pointer = DumpRecord(Pointer, RecordNumber, StartOfFile, EndOfFile);
    }


    return;
}

//
// Dump a single LogModule structure if it matches MatchName (NULL matches
// all)
//

PLIST_ENTRY
DumpLogModule(
    HANDLE hCurrentProcess,
    DWORD pLogModule,
    LPWSTR MatchName
    )
{
    LOGMODULE LogModule;
    WCHAR ModuleName[MAX_NAME / sizeof(WCHAR)];

    GET_DATA(pLogModule, &LogModule, sizeof(LogModule))
    GET_DATA(LogModule.ModuleName, &ModuleName, MAX_NAME)

    if (!MatchName || !_wcsicmp(MatchName, ModuleName)) {
        printf("\tModule Name %ws\n", ModuleName);
        printf("\tModule Atom 0x%X\n", LogModule.ModuleAtom);
        printf("\tPointer to LogFile 0x%X\n", LogModule.LogFile);
    }

    return (LogModule.ModuleList.Flink);
}

//
// Dump selected, or all, LogModule structures
//

VOID
logmodule(
    HANDLE hCurrentProcess,
    HANDLE hCurrentThread,
    DWORD dwCurrentPc,
    PNTSD_EXTENSION_APIS lpExtensionApis,
    LPSTR lpArgumentString
    )
{
    DWORD pLogModule;
    DWORD LogModuleAnchor;
    LPWSTR wArgumentString = NULL;
    ANSI_STRING AnsiString;
    UNICODE_STRING UnicodeString;

    InitFunctionPointers(hCurrentProcess, lpExtensionApis);
    UnicodeString.Buffer = NULL;

    //
    // Evaluate the argument string to get the address of
    // the logmodule to dump.  If no parm, dump them all.
    //

    if (lpArgumentString && *lpArgumentString == '.') {
        lpArgumentString++;
        RtlInitAnsiString(&AnsiString, lpArgumentString);
        RtlAnsiStringToUnicodeString(&UnicodeString, &AnsiString, TRUE);
    }
    else if (lpArgumentString && *lpArgumentString) {
        pLogModule = (lpGetExpressionRoutine)(lpArgumentString);
        DumpLogModule(hCurrentProcess, pLogModule, NULL);
        return;
    }

    LogModuleAnchor = (lpGetExpressionRoutine)("LogModuleHead");

    GET_DATA(LogModuleAnchor, &pLogModule, sizeof(DWORD))

    while (pLogModule != LogModuleAnchor && !(lpCheckControlCRoutine)()) {
        pLogModule =
            (DWORD) DumpLogModule(hCurrentProcess, pLogModule,
                UnicodeString.Buffer);
        if (!UnicodeString.Buffer) {
            printf("\n");
        }
    }
    if (UnicodeString.Buffer) {
        RtlFreeUnicodeString(&UnicodeString);
    }

    return;
}

//
// Dump a single LogFile structure if it matches MatchName (NULL matches
// all)
//

PLIST_ENTRY
DumpLogFile(
    HANDLE hCurrentProcess,
    DWORD pLogFile,
    LPWSTR MatchName
    )
{
    LOGFILE LogFile;
    LPWSTR UnicodeName;

    //
    // Get the fixed part of the structure
    //

    GET_DATA(pLogFile, &LogFile, sizeof(LogFile))

    //
    // Get the Default module name
    //

    UnicodeName = GetUnicodeString(LogFile.LogModuleName);

    //
    // See if we're just looking for a particular one.  If we are and
    // this isn't it, bail out.
    //

    if (MatchName && _wcsicmp(MatchName, UnicodeName)) {
        LocalFree(UnicodeName);
        return (LogFile.FileList.Flink);
    }

    //
    // Otherwise print it out
    //

    printf("%ws", UnicodeName);
    LocalFree(UnicodeName);

    //
    // Now the file name of this logfile
    //

    UnicodeName = GetUnicodeString(LogFile.LogFileName);
    printf(" : %ws\n", UnicodeName);
    LocalFree(UnicodeName);

    if (LogFile.Notifiees.Flink == LogFile.Notifiees.Blink) {
        printf("\tNo active ChangeNotifies on this log\n");
    }
    else {
        printf("\tActive Change Notify!  Dump of this list not implemented\n");
    }

    printf("\tReference Count: %d\n\tFlags: ", LogFile.RefCount);
    if (LogFile.Flags == 0) {
        printf("No flags set ");
    }
    else {
        if (LogFile.Flags & ELF_LOGFILE_HEADER_DIRTY) {
            printf("Dirty ");
        }
        if (LogFile.Flags & ELF_LOGFILE_HEADER_WRAP) {
            printf("Wrapped ");
        }
        if (LogFile.Flags & ELF_LOGFILE_LOGFULL_WRITTEN) {
             printf("Logfull Written ");
        }
    }
    printf("\n");

    printf("\tMax Files Sizes [Cfg:Curr:Next]  0x%X : 0x%X : 0x%X\n",
        LogFile.ConfigMaxFileSize, LogFile.ActualMaxFileSize,
        LogFile.NextClearMaxFileSize);

    printf("\tRecord Numbers [Oldest:Curr] %d : %d\n",
        LogFile.OldestRecordNumber, LogFile.CurrentRecordNumber);

    printf("\tRetention period in days: %d\n", LogFile.Retention / 86400);

    printf("\tBase Address: 0x%X\n", LogFile.BaseAddress);

    printf("\tView size: 0x%X\n", LogFile.ViewSize);

    printf("\tOffset of beginning record: 0x%X\n", LogFile.BeginRecord);

    printf("\tOffset of ending record: 0x%X\n", LogFile.EndRecord);

    return (LogFile.FileList.Flink);
}

//
// Dump selected, or all, LogFile structures
//

VOID
logfile(
    HANDLE hCurrentProcess,
    HANDLE hCurrentThread,
    DWORD dwCurrentPc,
    PNTSD_EXTENSION_APIS lpExtensionApis,
    LPSTR lpArgumentString
    )
{
    DWORD pLogFile;
    DWORD LogFileAnchor;
    LPWSTR wArgumentString = NULL;
    ANSI_STRING AnsiString;
    UNICODE_STRING UnicodeString;
    BOOL AllocateString = FALSE;

    InitFunctionPointers(hCurrentProcess, lpExtensionApis);
    UnicodeString.Buffer = NULL;

    //
    // Evaluate the argument string to get the address of
    // the logfile to dump.  If no parm, dump them all.
    //

    if (lpArgumentString && *lpArgumentString) {
        if(*lpArgumentString == '.') {
            lpArgumentString++;
            RtlInitAnsiString(&AnsiString, lpArgumentString);
            RtlAnsiStringToUnicodeString(&UnicodeString, &AnsiString, TRUE);
        }
        else {
            pLogFile = (lpGetExpressionRoutine)(lpArgumentString);
            DumpLogFile(hCurrentProcess, pLogFile, NULL);
            return;
        }
    }

    LogFileAnchor = (lpGetExpressionRoutine)("LogFilesHead");

    GET_DATA(LogFileAnchor, &pLogFile, sizeof(DWORD))

    while (pLogFile != LogFileAnchor) {
        pLogFile =
            (DWORD) DumpLogFile(hCurrentProcess, pLogFile,
                UnicodeString.Buffer);
        if (!UnicodeString.Buffer) {
            printf("\n");
        }
    }

    if (UnicodeString.Buffer) {
        RtlFreeUnicodeString(&UnicodeString);
    }

    return;
}

//
// Dump a request packet structure
//

VOID
request(
    HANDLE hCurrentProcess,
    HANDLE hCurrentThread,
    DWORD dwCurrentPc,
    PNTSD_EXTENSION_APIS lpExtensionApis,
    LPSTR lpArgumentString
    )
{
    ELF_REQUEST_RECORD Request;
    DWORD Pointer;
    DWORD RecordSize;
    WRITE_PKT WritePkt;
    READ_PKT ReadPkt;
    CLEAR_PKT ClearPkt;
    BACKUP_PKT BackupPkt;
    LPWSTR FileName;
    CHAR Address[18];

    InitFunctionPointers(hCurrentProcess, lpExtensionApis);

    //
    // Evaluate the argument string to get the address of
    // the request packet to dump.
    //

    if (lpArgumentString && *lpArgumentString) {
        Pointer = (lpGetExpressionRoutine)(lpArgumentString);
    }
    else {
        printf("Must supply a request packet address\n");
        return;
    }

    GET_DATA(Pointer, &Request, sizeof(ELF_REQUEST_RECORD))

    switch (Request.Command ) {
        case ELF_COMMAND_READ:
            printf("\nRead packet\n");
            GET_DATA(Request.Pkt.ReadPkt, &ReadPkt, sizeof(READ_PKT))
            printf("\tLast Seek Position = %d\n", ReadPkt.LastSeekPos);
            printf("\tLast Seek Record = %d\n", ReadPkt.LastSeekRecord);
            printf("\tStart at record number %d\n", ReadPkt.RecordNumber);
            printf("\tRead %d bytes into buffer at 0x%X\n",
                ReadPkt.BufferSize, ReadPkt.Buffer);
            if (ReadPkt.Flags & ELF_IREAD_UNICODE) {
                printf("\tReturn in ANSI\n");
            }
            else {
                printf("\tReturn in UNICODE\n");
            }
            printf("\tRead flags: ");
            if (ReadPkt.ReadFlags & EVENTLOG_SEQUENTIAL_READ) {
                printf("Sequential ");
            }
            if (ReadPkt.ReadFlags & EVENTLOG_SEEK_READ) {
                printf("Seek ");
            }
            if (ReadPkt.ReadFlags & EVENTLOG_FORWARDS_READ) {
                printf("Forward ");
            }
            if (ReadPkt.ReadFlags & EVENTLOG_BACKWARDS_READ) {
                printf("Backwards ");
            }
            printf("\n");
            break;

        case ELF_COMMAND_WRITE:
            printf("\nWrite packet\n");
            if (Request.Flags == ELF_FORCE_OVERWRITE) {
                printf("with ELF_FORCE_OVERWRITE enabled\n");
            }
            else {
                printf("\n");
            }
            GET_DATA(Request.Pkt.WritePkt, &WritePkt, sizeof(WRITE_PKT))
            RecordSize = (WritePkt.Datasize);
            DumpRecord((DWORD)WritePkt.Buffer, 0, 0, 0);
            break;

        case ELF_COMMAND_CLEAR:
            printf("\nClear packet\n");
            GET_DATA(Request.Pkt.ClearPkt, &ClearPkt, sizeof(CLEAR_PKT))
            FileName = GetUnicodeString(ClearPkt.BackupFileName);
            printf("Backup filename = %ws\n", FileName);
            LocalFree(FileName);
            break;

        case ELF_COMMAND_BACKUP:
            printf("\nBackup packet\n");
            GET_DATA(Request.Pkt.BackupPkt, &BackupPkt, sizeof(BACKUP_PKT))
            FileName = GetUnicodeString(BackupPkt.BackupFileName);
            printf("Backup filename = %ws\n", FileName);
            LocalFree(FileName);
            break;

        case ELF_COMMAND_WRITE_QUEUED:
            printf("\nQueued Write packet\n");
            if (Request.Flags == ELF_FORCE_OVERWRITE) {
                printf("with ELF_FORCE_OVERWRITE enabled\n");
            }
            else {
                printf("\n");
            }
            printf("NtStatus = 0x%X\n", Request.Status);
            break;

        default:
            printf("\nInvalid packet\n");
    }

    printf("\nLogFile for this packet:\n\n");
    _itoa((DWORD) Request.LogFile, Address, 16);
    logfile(hCurrentProcess, hCurrentThread, dwCurrentPc, lpExtensionApis,
        Address);

    printf("\nLogModule for this packet:\n\n");
    _itoa((DWORD)Request.Module, Address, 16);
    logmodule(hCurrentProcess, hCurrentThread, dwCurrentPc, lpExtensionApis,
        Address);

    return;
}

//
// Online help
//

VOID
help(
    HANDLE hCurrentProcess,
    HANDLE hCurrentThread,
    DWORD dwCurrentPc,
    PNTSD_EXTENSION_APIS lpExtensionApis,
    LPSTR lpArgumentString
    )
{
    InitFunctionPointers(hCurrentProcess, lpExtensionApis);

    printf("\nEventlog NTSD Extensions\n");

    if (!lpArgumentString || *lpArgumentString == '\0' ||
        *lpArgumentString == '\n' || *lpArgumentString == '\r') {
        printf("\tlogmodule - dump a logmodule structure\n");
        printf("\tlogfile   - dump a logfile structure\n");
        printf("\trequest   - dump a request record\n");
        printf("\trecord    - dump a eventlog record\n");
        printf("\n\tEnter help <cmd> for detailed help on a command\n");
    }
    else {
        if (!_stricmp(lpArgumentString, "logmodule")) {
            printf("\tlogmodule <arg>, where <arg> can be one of:\n");
            printf("\t\tno argument - dump all logmodule structures\n");
            printf("\t\taddress     - dump the logmodule at specified address\n");
            printf("\t\t.string     - dump the logmodule with name string\n");
        }
        else if (!_stricmp(lpArgumentString, "logfile")) {
            printf("\tlogfile <arg>, where <arg> can be one of:\n");
            printf("\t\tno argument - dump all logfile structures\n");
            printf("\t\taddress     - dump the logfile at specified address\n");
            printf("\t\t.string     - dump the logfile with name string\n");
        }
        else if (!_stricmp(lpArgumentString, "record")) {
            printf("\trecord <arg>, where <arg> can be one of:\n");
            printf("\t\tno argument - dump all records in system log\n");
            printf("\t\taddress     - dump records starting at specified address\n");
            printf("\t\t.string     - dump all records in the <string> log\n");
            printf("\t\t#<nnn>      - dumps records starting at nnn in system log\n");
            printf("\t\t#<nnn> .string  - dumps records starting at nnn in <string> log\n");
        }
        else if (!_stricmp(lpArgumentString, "request")) {
            printf("\trequest - dump the request record at specified address\n");
        }
        else {
            printf("\tInvalid command [%s]\n", lpArgumentString);
        }
    }
}
