/*++

Copyright (c) Microsoft Corporation. All rights reserved.

Module Name:

    tracelog.c

Abstract:

    Sample trace control program. Allows user to start, update, query, stop 
    event tracing, etc.


--*/
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <shellapi.h>
#include <tchar.h>
#include <wmistr.h>
#include <initguid.h>
#include <guiddef.h>
#include <evntrace.h>

#define MAXSTR                          1024
// Default trace file name.
#define DEFAULT_LOGFILE_NAME            _T("C:\\LogFile.Etl")
// On Windows 2000, we support up to 32 loggers at once.
// On Windows XP and .NET server, we support up to 64 loggers. 
#define MAXIMUM_LOGGERS                  32

// In this sample, we support the following actions. 
// Additional actions that we do not use in this sample include 
// Flush and Enumerate Guids functionalities. They are supported
// only on XP or higher version.
#define ACTION_QUERY                    0
#define ACTION_START                    1
#define ACTION_STOP                     2
#define ACTION_UPDATE                   3
#define ACTION_LIST                     4
#define ACTION_ENABLE                   5
#define ACTION_HELP                     6

#define ACTION_UNDEFINED               10

void
PrintLoggerStatus(
    IN PEVENT_TRACE_PROPERTIES LoggerInfo,
    IN ULONG Status
    );

ULONG 
ahextoi(
    IN TCHAR *s
    );

void 
StringToGuid(
    IN TCHAR *str,
    OUT LPGUID guid
    );

void 
PrintHelpMessage();


//
//  main function
//
__cdecl main(argc, argv)
    int argc;
    char **argv;
/*++

Routine Description:

    It is the main function.

Arguments:
  
Return Value:

    Error Code defined in winerror.h : If the function succeeds, 
                it returns ERROR_SUCCESS (== 0).


--*/{
    ULONG i, j;
    ULONG Status = ERROR_SUCCESS;
    LPTSTR *targv, *utargv = NULL;
    // Action to be taken
    USHORT Action = ACTION_UNDEFINED;

    LPTSTR LoggerName;
    LPTSTR LogFileName;
    PEVENT_TRACE_PROPERTIES pLoggerInfo;
    TRACEHANDLE LoggerHandle = 0;
    // Target GUID, level and flags for enable/disable
    GUID TargetGuid;
    ULONG bEnable = TRUE;

    ULONG SizeNeeded = 0;

    // We will enable Process, Thread, Disk, and Network events 
    // if the Kernel Logger is requested.
    BOOL bKernelLogger = FALSE;

    // Allocate and initialize EVENT_TRACE_PROPERTIES structure first
    SizeNeeded = sizeof(EVENT_TRACE_PROPERTIES) + 2 * MAXSTR * sizeof(TCHAR);
    pLoggerInfo = (PEVENT_TRACE_PROPERTIES) malloc(SizeNeeded);
    if (pLoggerInfo == NULL) {
        return (ERROR_OUTOFMEMORY);
    }
    
    RtlZeroMemory(pLoggerInfo, SizeNeeded);

    pLoggerInfo->Wnode.BufferSize = SizeNeeded;
    pLoggerInfo->Wnode.Flags = WNODE_FLAG_TRACED_GUID; 
    pLoggerInfo->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
    pLoggerInfo->LogFileNameOffset = pLoggerInfo->LoggerNameOffset + MAXSTR * sizeof(TCHAR);

    LoggerName = (LPTSTR)((char*)pLoggerInfo + pLoggerInfo->LoggerNameOffset);
    LogFileName = (LPTSTR)((char*)pLoggerInfo + pLoggerInfo->LogFileNameOffset);
    // If the logger name is not given, we will assume the kernel logger.
    _tcscpy(LoggerName, KERNEL_LOGGER_NAME);

#ifdef UNICODE
    if ((targv = CommandLineToArgvW(
                      GetCommandLineW(),    // pointer to a command-line string
                      &argc                 // receives the argument count
                      )) == NULL) {
        free(pLoggerInfo);
        return (GetLastError());
    };
    utargv = targv;
#else
    targv = argv;
#endif

    //
    // Parse the command line options to determine actions and parameters.
    //
    while (--argc > 0) {
        ++targv;
        if (**targv == '-' || **targv == '/') {  // argument found
            if (targv[0][0] == '/' ) {
                targv[0][0] = '-';
            }

            // Deterine actions.
            if (!_tcsicmp(targv[0], _T("-start"))) {
                Action = ACTION_START;
                if (argc > 1) {
                    if (targv[1][0] != '-' && targv[1][0] != '/') {
                        ++targv; --argc;
                        _tcscpy(LoggerName, targv[0]);
                    }
                }
            }
            else if (!_tcsicmp(targv[0], _T("-enable"))) {
                Action = ACTION_ENABLE;
                if (argc > 1) {
                    if (targv[1][0] != '-' && targv[1][0] != '/') {
                        ++targv; --argc;
                        _tcscpy(LoggerName, targv[0]);
                    }
                }
            }
            else if (!_tcsicmp(targv[0], _T("-disable"))) {
                Action = ACTION_ENABLE;
                bEnable = FALSE;
                if (argc > 1) {
                    if (targv[1][0] != '-' && targv[1][0] != '/') {
                        ++targv; --argc;
                        _tcscpy(LoggerName, targv[0]);
                    }
                }
            }
            else if (!_tcsicmp(targv[0], _T("-stop"))) {
                Action = ACTION_STOP;
                if (argc > 1) {
                    if (targv[1][0] != '-' && targv[1][0] != '/') {
                        ++targv; --argc;
                        _tcscpy(LoggerName, targv[0]);
                    }
                }
            }
            else if (!_tcsicmp(targv[0], _T("-update"))) {
                Action = ACTION_UPDATE;
                if (argc > 1) {
                    if (targv[1][0] != '-' && targv[1][0] != '/') {
                        ++targv; --argc;
                        _tcscpy(LoggerName, targv[0]);
                    }
                }
            }
            else if (!_tcsicmp(targv[0], _T("-query"))) {
                Action = ACTION_QUERY;
                if (argc > 1) {
                    if (targv[1][0] != '-' && targv[1][0] != '/') {
                        ++targv; --argc;
                        _tcscpy(LoggerName, targv[0]);
                    }
                }
            }
            else if (!_tcsicmp(targv[0], _T("-list"))) {
                Action  = ACTION_LIST;
            }
 
            // Get other parameters.
            // Users can customize logger settings further by adding/changing 
            // values to pLoggerInfo. Refer to EVENT_TRACE_PROPERTIES documentation
            // for available options.
            // In this sample, we allow changing maximum number of buffers and 
            // specifying user mode (private) logger.
            // We also take trace file name and guid for enable/disable.
            else if (!_tcsicmp(targv[0], _T("-f"))) {
                if (argc > 1) {
                    _tfullpath(LogFileName, targv[1], MAXSTR);
                    ++targv; --argc;
                }
            }
            else if (!_tcsicmp(targv[0], _T("-guid"))) {
                if (argc > 1) {
                    // -guid #00000000-0000-0000-0000-000000000000
                    if (targv[1][0] == _T('#')) {
                        StringToGuid(&targv[1][1], &TargetGuid);
                        ++targv; --argc;
                    }
                }
            }
            else if (!_tcsicmp(targv[0], _T("-max"))) {
                if (argc > 1) {
                    pLoggerInfo->MaximumBuffers = _ttoi(targv[1]);
                    ++targv; --argc;
                }
            }
            else if (!_tcsicmp(targv[0], _T("-um"))) {
                    pLoggerInfo->LogFileMode |= EVENT_TRACE_PRIVATE_LOGGER_MODE;
            }
            else if ( targv[0][1] == 'h' || targv[0][1] == 'H' || targv[0][1] == '?'){
                Action = ACTION_HELP;
                PrintHelpMessage();
                if (utargv != NULL) {
                    GlobalFree(utargv);
                }
                free(pLoggerInfo);

                return (ERROR_SUCCESS);
            }
            else Action = ACTION_UNDEFINED;
        }
        else { 
            _tprintf(_T("Invalid option given: %s\n"), targv[0]);
            Status = ERROR_INVALID_PARAMETER;
            SetLastError(Status);
            if (utargv != NULL) {
                GlobalFree(utargv);
            }
            free(pLoggerInfo);

            return (Status);
        }
    }

    // Set the kernel logger parameters.
    if (!_tcscmp(LoggerName, KERNEL_LOGGER_NAME)) {
        // Set enable flags. Users can add options to add additional kernel events 
        // or remove some of these events.
        pLoggerInfo->EnableFlags |= EVENT_TRACE_FLAG_PROCESS;
        pLoggerInfo->EnableFlags |= EVENT_TRACE_FLAG_THREAD;
        pLoggerInfo->EnableFlags |= EVENT_TRACE_FLAG_DISK_IO;
        pLoggerInfo->EnableFlags |= EVENT_TRACE_FLAG_NETWORK_TCPIP;

        pLoggerInfo->Wnode.Guid = SystemTraceControlGuid; 
        bKernelLogger = TRUE;
    }
    else if (pLoggerInfo->LogFileMode & EVENT_TRACE_PRIVATE_LOGGER_MODE) {
        // We must provide a control GUID for a private logger. 
        pLoggerInfo->Wnode.Guid = TargetGuid;
    }

    // Process the request.
    switch (Action) {
        case  ACTION_START:
        {
            // Use default file name if not given
            if (_tcslen(LogFileName) == 0) {
                _tcscpy(LogFileName, DEFAULT_LOGFILE_NAME); 
            }

            Status = StartTrace(&LoggerHandle, LoggerName, pLoggerInfo);

            if (Status != ERROR_SUCCESS) {
                _tprintf(_T("Could not start logger: %s\n") 
                         _T("Operation Status:       %uL\n"),
                         LoggerName,
                         Status);

                break;
            }
            _tprintf(_T("Logger Started...\n"));
        }
        case ACTION_ENABLE:
        {
            // We can allow enabling a GUID during START operation (Note no break in case ACTION_START). 
            // In that case, we do not need to get LoggerHandle separately.
            if (Action == ACTION_ENABLE ){
                
                // Get Logger Handle though Query.
                Status = ControlTrace((TRACEHANDLE) 0, LoggerName, pLoggerInfo, EVENT_TRACE_CONTROL_QUERY);
                if( Status != ERROR_SUCCESS ){
                    _tprintf( _T("ERROR: Logger not started\n")
                              _T("Operation Status:    %uL\n"),
                              Status);
                    break;
                }
                LoggerHandle = pLoggerInfo->Wnode.HistoricalContext;
            }

            // We do not allow EnableTrace on the Kernel Logger in this sample,
            // users can use EnableFlags to enable/disable certain kernel events.
            if (!bKernelLogger) {
                _tprintf(_T("Enabling trace to logger %d\n"), LoggerHandle);
                // In this sample, we use EnableFlag = EnableLebel = 0
                Status = EnableTrace (
                                bEnable,
                                0,
                                0,
                                &TargetGuid, 
                                LoggerHandle);

                if (Status != ERROR_SUCCESS) {
                    _tprintf(_T("ERROR: Failed to enable Guid...\n"));
                    _tprintf(_T("Operation Status:       %uL\n"), Status);
                    break;
                }
            }
            break;
        }
        case ACTION_STOP :
        {
            LoggerHandle = (TRACEHANDLE) 0;
            Status = ControlTrace(LoggerHandle, LoggerName, pLoggerInfo, EVENT_TRACE_CONTROL_STOP);
            break;
        }
        case ACTION_LIST :
        {
            ULONG returnCount;
            PEVENT_TRACE_PROPERTIES pLoggerInfo[MAXIMUM_LOGGERS];
            PEVENT_TRACE_PROPERTIES pStorage, pTempStorage;
            ULONG SizeForOneProperty = sizeof(EVENT_TRACE_PROPERTIES) +
                                       2 * MAXSTR * sizeof(TCHAR);

            // We need to prepare space to receieve the inforamtion on loggers.
            SizeNeeded = MAXIMUM_LOGGERS * SizeForOneProperty;

            pStorage =  (PEVENT_TRACE_PROPERTIES)malloc(SizeNeeded);
            if (pStorage == NULL) {
                Status = ERROR_OUTOFMEMORY;
                break;
            }
            RtlZeroMemory(pStorage, SizeNeeded);
            // Save the pointer for free() later.
            pTempStorage = pStorage;

            for (i = 0; i < MAXIMUM_LOGGERS; i++) {
                pStorage->Wnode.BufferSize = SizeForOneProperty;
                pStorage->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
                pStorage->LogFileNameOffset = sizeof(EVENT_TRACE_PROPERTIES)
                                        + MAXSTR * sizeof(TCHAR);
                pLoggerInfo[i] = pStorage;
                pStorage = (PEVENT_TRACE_PROPERTIES) (
                                 (PUCHAR)pStorage + 
                                  pStorage->Wnode.BufferSize);
            }
        
            Status = QueryAllTraces(pLoggerInfo,
                                MAXIMUM_LOGGERS,
                                &returnCount);
    
            if (Status == ERROR_SUCCESS)
            {
                for (j= 0; j < returnCount; j++)
                {
                    PrintLoggerStatus(pLoggerInfo[j], 
                                        Status);
                    _tprintf(_T("\n"));
                }
            }

            free(pTempStorage);
            break;
        }

        case ACTION_UPDATE :
        {
            // In this sample, users can only update MaximumBuffers and log file name. 
            // User can add more options for other parameters as needed.
            Status = ControlTrace(LoggerHandle, LoggerName, pLoggerInfo, EVENT_TRACE_CONTROL_UPDATE);
            break;
        }
        case ACTION_QUERY  :
        {
            Status = ControlTrace(LoggerHandle, LoggerName, pLoggerInfo, EVENT_TRACE_CONTROL_QUERY);
            break;
        }
        case ACTION_HELP:
        {
            PrintHelpMessage();
            break;
        }
        default :
        {
            _tprintf(_T("Error: no action specified\n"));
            PrintHelpMessage();
            break;
        }
    }
    
    if ((Action != ACTION_HELP) && 
        (Action != ACTION_UNDEFINED) && 
        (Action != ACTION_LIST)) {
        PrintLoggerStatus(pLoggerInfo,
                            Status);
    }

    if (Status != ERROR_SUCCESS) {
        SetLastError(Status);
    }
    if (utargv != NULL) {
        GlobalFree(utargv);
    }
    free(pLoggerInfo);

    return (Status);
}


void
PrintLoggerStatus(
    IN PEVENT_TRACE_PROPERTIES LoggerInfo,
    IN ULONG Status
    )
/*++

Routine Description:

    Prints out the status of the specified logger.

Arguments:

    LoggerInfo - The pointer to the resident EVENT_TRACE_PROPERTIES that has
        the information about the current logger.
    Status - The operation status of the current logger.

Return Value:

    None

--*/
{
    LPTSTR LoggerName, LogFileName;
    
    if ((LoggerInfo->LoggerNameOffset > 0) &&
        (LoggerInfo->LoggerNameOffset  < LoggerInfo->Wnode.BufferSize)) {
        LoggerName = (LPTSTR) ((PUCHAR)LoggerInfo +
                                LoggerInfo->LoggerNameOffset);
    }
    else LoggerName = NULL;

    if ((LoggerInfo->LogFileNameOffset > 0) &&
        (LoggerInfo->LogFileNameOffset  < LoggerInfo->Wnode.BufferSize)) {
        LogFileName = (LPTSTR) ((PUCHAR)LoggerInfo +
                                LoggerInfo->LogFileNameOffset);
    }
    else LogFileName = NULL;

    _tprintf(_T("Operation Status:       %uL\n"), Status);
    
    _tprintf(_T("Logger Name:            %s\n"),
            (LoggerName == NULL) ?
            _T(" ") : LoggerName);
        _tprintf(_T("Logger Id:              %I64x\n"), LoggerInfo->Wnode.HistoricalContext);
        _tprintf(_T("Logger Thread Id:       %d\n"), LoggerInfo->LoggerThreadId);

    if (Status != 0)
        return;

    _tprintf(_T("Buffer Size:            %d Kb"), LoggerInfo->BufferSize);
    if (LoggerInfo->LogFileMode & EVENT_TRACE_USE_PAGED_MEMORY) {
        _tprintf(_T(" using paged memory\n"));
    }
    else {
        _tprintf(_T("\n"));
    }
    _tprintf(_T("Maximum Buffers:        %d\n"), LoggerInfo->MaximumBuffers);
    _tprintf(_T("Minimum Buffers:        %d\n"), LoggerInfo->MinimumBuffers);
    _tprintf(_T("Number of Buffers:      %d\n"), LoggerInfo->NumberOfBuffers);
    _tprintf(_T("Free Buffers:           %d\n"), LoggerInfo->FreeBuffers);
    _tprintf(_T("Buffers Written:        %d\n"), LoggerInfo->BuffersWritten);
    _tprintf(_T("Events Lost:            %d\n"), LoggerInfo->EventsLost);
    _tprintf(_T("Log Buffers Lost:       %d\n"), LoggerInfo->LogBuffersLost);
    _tprintf(_T("Real Time Buffers Lost: %d\n"), LoggerInfo->RealTimeBuffersLost);
    _tprintf(_T("AgeLimit:               %d\n"), LoggerInfo->AgeLimit);

    if (LogFileName == NULL) {
        _tprintf(_T("Buffering Mode:         "));
    }
    else {
        _tprintf(_T("Log File Mode:          "));
    }
    if (LoggerInfo->LogFileMode & EVENT_TRACE_FILE_MODE_APPEND) {
        _tprintf(_T("Append  "));
    }
    if (LoggerInfo->LogFileMode & EVENT_TRACE_FILE_MODE_CIRCULAR) {
        _tprintf(_T("Circular\n"));
    }
    else if (LoggerInfo->LogFileMode & EVENT_TRACE_FILE_MODE_SEQUENTIAL) {
        _tprintf(_T("Sequential\n"));
    }
    else {
        _tprintf(_T("Sequential\n"));
    }
    if (LoggerInfo->LogFileMode & EVENT_TRACE_REAL_TIME_MODE) {
        _tprintf(_T("Real Time mode enabled"));
        _tprintf(_T("\n"));
    }

    if (LoggerInfo->MaximumFileSize > 0)
        _tprintf(_T("Maximum File Size:      %d Mb\n"), LoggerInfo->MaximumFileSize);

    if (LoggerInfo->FlushTimer > 0)
        _tprintf(_T("Buffer Flush Timer:     %d secs\n"), LoggerInfo->FlushTimer);

    if (LoggerInfo->EnableFlags != 0) {
        _tprintf(_T("Enabled tracing:        "));

        if ((LoggerName != NULL) && (!_tcscmp(LoggerName, KERNEL_LOGGER_NAME))) {

            if (LoggerInfo->EnableFlags & EVENT_TRACE_FLAG_PROCESS)
                _tprintf(_T("Process "));
            if (LoggerInfo->EnableFlags & EVENT_TRACE_FLAG_THREAD)
                _tprintf(_T("Thread "));
            if (LoggerInfo->EnableFlags & EVENT_TRACE_FLAG_DISK_IO)
                _tprintf(_T("Disk "));
            if (LoggerInfo->EnableFlags & EVENT_TRACE_FLAG_DISK_FILE_IO)
                _tprintf(_T("File "));
            if (LoggerInfo->EnableFlags & EVENT_TRACE_FLAG_MEMORY_PAGE_FAULTS)
                _tprintf(_T("PageFaults "));
            if (LoggerInfo->EnableFlags & EVENT_TRACE_FLAG_MEMORY_HARD_FAULTS)
                _tprintf(_T("HardFaults "));
            if (LoggerInfo->EnableFlags & EVENT_TRACE_FLAG_IMAGE_LOAD)
                _tprintf(_T("ImageLoad "));
            if (LoggerInfo->EnableFlags & EVENT_TRACE_FLAG_NETWORK_TCPIP)
                _tprintf(_T("TcpIp "));
            if (LoggerInfo->EnableFlags & EVENT_TRACE_FLAG_REGISTRY)
                _tprintf(_T("Registry "));
        }else{
            _tprintf(_T("0x%08x"), LoggerInfo->EnableFlags );
        }
        _tprintf(_T("\n"));
    }
    if (LogFileName != NULL) {
        _tprintf(_T("Log Filename:           %s\n"), LogFileName);
    }

}

ULONG 
ahextoi(
    IN TCHAR *s
    )
/*++

Routine Description:

    Converts a hex string into a number.

Arguments:

    s - A hex string in TCHAR. 

Return Value:

    ULONG - The number in the string.


--*/
{
    int len;
    ULONG num, base, hex;

    len = _tcslen(s);
    hex = 0; base = 1; num = 0;
    while (--len >= 0) {
        if ( (s[len] == 'x' || s[len] == 'X') &&
             (s[len-1] == '0') )
            break;
        if (s[len] >= '0' && s[len] <= '9')
            num = s[len] - '0';
        else if (s[len] >= 'a' && s[len] <= 'f')
            num = (s[len] - 'a') + 10;
        else if (s[len] >= 'A' && s[len] <= 'F')
            num = (s[len] - 'A') + 10;
        else 
            continue;

        hex += num * base;
        base = base * 16;
    }
    return hex;
}

void 
StringToGuid(
    IN TCHAR *str, 
    IN OUT LPGUID guid
)
/*++

Routine Description:

    Converts a string into a GUID.

Arguments:

    str - A string in TCHAR.
    guid - The pointer to a GUID that will have the converted GUID.

Return Value:

    None.


--*/
{
    TCHAR temp[10];
    int i;

    _tcsncpy(temp, str, 8);
    temp[8] = 0;
    guid->Data1 = ahextoi(temp);
    _tcsncpy(temp, &str[9], 4);
    temp[4] = 0;
    guid->Data2 = (USHORT) ahextoi(temp);
    _tcsncpy(temp, &str[14], 4);
    temp[4] = 0;
    guid->Data3 = (USHORT) ahextoi(temp);

    for (i=0; i<2; i++) {
        _tcsncpy(temp, &str[19 + (i*2)], 2);
        temp[2] = 0;
        guid->Data4[i] = (UCHAR) ahextoi(temp);
    }
    for (i=2; i<8; i++) {
        _tcsncpy(temp, &str[20 + (i*2)], 2);
        temp[2] = 0;
        guid->Data4[i] = (UCHAR) ahextoi(temp);
    }
}

void PrintHelpMessage()
/*++

Routine Description:

    prints out a help message.

Arguments:

    None.

Return Value:

    None.


--*/
{
    _tprintf(_T("Usage: tracelog [actions] [options] | [-h | -help | -?]\n"));
    _tprintf(_T("\n    actions:\n"));
    _tprintf(_T("\t-start   [LoggerName] Starts up the [LoggerName] trace session\n"));
    _tprintf(_T("\t-stop    [LoggerName] Stops the [LoggerName] trace session\n"));
    _tprintf(_T("\t-update  [LoggerName] Updates the [LoggerName] trace session\n"));
    _tprintf(_T("\t-enable  [LoggerName] Enables providers for the [LoggerName] session\n"));
    _tprintf(_T("\t-disable [LoggerName] Disables providers for the [LoggerName] session\n"));
    _tprintf(_T("\t-query   [LoggerName] Query status of [LoggerName] trace session\n"));
    _tprintf(_T("\t-list                 List all trace sessions\n"));

    _tprintf(_T("\n    options:\n"));
    _tprintf(_T("\t-um                   Use Process Private tracing\n"));
    _tprintf(_T("\t-max <n>              Sets maximum buffers\n"));
    _tprintf(_T("\t-f <name>             Log to file <name>\n"));
    _tprintf(_T("\t-guid #<guid>         Provider GUID to enable/disable\n"));
    _tprintf(_T("\n"));
    _tprintf(_T("\t-h\n"));
    _tprintf(_T("\t-help\n"));
    _tprintf(_T("\t-?                    Display usage information\n"));
}