/*++

Copyright (c) 1996  Microsoft Corporation

Module Name:

    LogThred.c

Abstract:

    module containing logging thread functions

Author:

    Bob Watson (a-robw) 10 Apr 96

Revision History:

--*/
#ifndef UNICODE
#define UNICODE     1
#endif
#ifndef _UNICODE
#define _UNICODE    1
#endif
//
//  Windows Include files
//
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <pdh.h>
#include <pdhmsg.h>
#include "pdlsvc.h"
//#include "logutils.h"
#include "pdlmsg.h"

static
long
JulianDateFromSystemTime(
    SYSTEMTIME *pST
)
{
    static WORD wDaysInRegularMonth[] = {
    	31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
    };

    static WORD wDaysInLeapYearMonth[] = {
    	31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366
    };

    long JDate = 0;

    // Check for leap year.
    if (pST->wMonth > 1) {
        if( pST->wYear % 100 != 0 && pST->wYear % 4 == 0 ) {
	    // this is a leap year
	    JDate += wDaysInLeapYearMonth[pST->wMonth - 1];
	} else {
	    // this is not a leap year
            JDate += wDaysInRegularMonth[pST->wMonth - 1];
        }
    }
    // Add in days for this month.
    JDate += pST->wDay;

    // Add in year.
    JDate += (pST->wYear % 100) * 10000;

    return JDate;
}

static
BOOL
GetLocalFileTime (
    SYSTEMTIME  *pST,
    LONGLONG    *pFileTime
)
{
    BOOL    bResult;
    GetLocalTime (pST);
    if (pFileTime != NULL) {
        bResult = SystemTimeToFileTime (pST, (LPFILETIME)pFileTime);
    } else {
        bResult = TRUE;
    }
    return bResult;
}

static
DWORD
GetSamplesInRenameInterval(
    IN DWORD    dwSampleInterval,           // in seconds
    IN DWORD    dwRenameIntervalCount,      // in units
    IN DWORD    dwRenameIntervalUnits)      // for "count" arg
{
    DWORD    dwRenameIntervalSeconds;
    // convert rename interval to seconds and account for the
    // first (or zero-th) sample in the log

    dwRenameIntervalSeconds = dwRenameIntervalCount;

    switch (dwRenameIntervalUnits) {
        case OPD_RENAME_HOURS:
            dwRenameIntervalSeconds *= SECONDS_IN_HOUR;
            break;

        case OPD_RENAME_DAYS:
        default:
            dwRenameIntervalSeconds *= SECONDS_IN_DAY;
            break;

        case OPD_RENAME_MONTHS:
            dwRenameIntervalSeconds *= SECONDS_IN_DAY * 30;
            break;

        case OPD_RENAME_KBYTES:
        case OPD_RENAME_MBYTES:
            // these don't use a rename counter
            return (DWORD)0;
            break;
    }

    dwRenameIntervalSeconds /= dwSampleInterval;

    dwRenameIntervalSeconds += 1;   // add in the "zero-th" sample

    return (dwRenameIntervalSeconds);
}

static
LONG
BuildCurrentLogFileName (
    IN  LPCTSTR     szBaseFileName,
    IN  LPCTSTR     szDefaultDir,
    IN  LPTSTR      szOutFileBuffer,
    IN  LPDWORD     lpdwSerialNumber,
    IN  DWORD       dwDateFormat,
    IN  DWORD       dwLogFormat
)
// presumes OutFileBuffer is large enough (i.e. >= MAX_PATH)
{
    SYSTEMTIME  st;
    BOOL        bUseCurrentDir = FALSE;
    TCHAR       szAuto[MAX_PATH];
    LPTSTR      szExt;

    if (szDefaultDir != NULL) {
        if (*szDefaultDir == 0) {
            bUseCurrentDir = TRUE;
        }
    } else {
        bUseCurrentDir = TRUE;
    }

    if (bUseCurrentDir) {
        GetCurrentDirectory (MAX_PATH, szOutFileBuffer);
    } else {
        lstrcpy (szOutFileBuffer, szDefaultDir);
    }

    // add a backslash to the path name if it doesn't have one already

    if (szOutFileBuffer[lstrlen(szOutFileBuffer)-1] != TEXT('\\')) {
        lstrcat (szOutFileBuffer, TEXT("\\"));
    }

    // add the base filename

    lstrcat (szOutFileBuffer, szBaseFileName);

    // add the auto name part

    // get date/time/serial integer format
    GetLocalTime(&st);

    switch (dwDateFormat) {
    case OPD_NAME_NNNNNN:
        _stprintf (szAuto, TEXT("_%6.6d"), *lpdwSerialNumber);
        (*lpdwSerialNumber)++; // increment
        if (*lpdwSerialNumber >= 1000000) {
            // roll over to 0
            *lpdwSerialNumber = 0;
        }
        break;

    case OPD_NAME_YYDDD:
        _stprintf (szAuto, TEXT("_%5.5d"),
            JulianDateFromSystemTime(&st));
        break;

    case OPD_NAME_YYMM:
        _stprintf (szAuto, TEXT("_%2.2d%2.2d"),
            st.wYear % 100, st.wMonth);
        break;

    case OPD_NAME_YYMMDDHH:
        _stprintf (szAuto, TEXT("_%2.2d%2.2d%2.2d%2.2d"),
            (st.wYear % 100), st.wMonth, st.wDay, st.wHour);
        break;

    case OPD_NAME_MMDDHH:
        _stprintf (szAuto, TEXT("_%2.2d%2.2d%2.2d"),
            st.wMonth, st.wDay, st.wHour);
        break;

    case OPD_NAME_YYMMDD:
    default:
        _stprintf (szAuto, TEXT("_%2.2d%2.2d%2.2d"),
            st.wYear % 100, st.wMonth, st.wDay);
        break;
    }

    lstrcat (szOutFileBuffer, szAuto);

    // get file type
    switch (dwLogFormat) {
    case PDH_LOG_TYPE_TSV:
        szExt = TEXT(".tsv");
        break;

    case PDH_LOG_TYPE_BINARY:
        szExt = TEXT(".blg");
        break;

    case PDH_LOG_TYPE_CSV:
    default:
        szExt = TEXT(".csv");
        break;
    }

    lstrcat (szOutFileBuffer, szExt);

    return ERROR_SUCCESS;
}

static
BOOL
LoadDataFromRegistry (
    IN  LPLOG_THREAD_DATA   pArg,
    IN  LPTSTR              szDefaultDir,
    IN  LPTSTR              szBaseName,
    IN  LPTSTR              szCurrentLogFile
)
{
    LONG            lStatus;
    DWORD           dwType;
    DWORD           dwSize;
    DWORD           dwData;
    LPTSTR          szStringArray[2];

    // get size of buffer required by counter list,
    // then allocate the buffer and retrieve the counter list

    dwType = 0;
    dwData = 0;
    dwSize = 0;
    lStatus = RegQueryValueEx (
        pArg->hKeyQuery,
        TEXT("Counter List"),
        NULL,
        &dwType,
        (LPBYTE)NULL,
        &dwSize);

    pArg->mszCounterList = (LPTSTR)G_ALLOC(dwSize);

    if (pArg->mszCounterList != NULL) {
        dwType = 0;
        lStatus = RegQueryValueEx (
            pArg->hKeyQuery,
            TEXT("Counter List"),
            NULL,
            &dwType,
            (LPBYTE)pArg->mszCounterList,
            &dwSize);

        if ((lStatus != ERROR_SUCCESS) || (dwSize == 0)) {
            // no counter list retrieved so there's not much
            // point in continuing
            szStringArray[0] = pArg->szQueryName;
            ReportEvent (hEventLog,
                EVENTLOG_ERROR_TYPE,
                0,
                PERFLOG_UNABLE_READ_COUNTER_LIST,
                NULL,
                1,
                sizeof(DWORD),
                szStringArray,
                (LPVOID)&lStatus);
            return FALSE;
        }
    } else {
        szStringArray[0] = pArg->szQueryName;
        ReportEvent (hEventLog,
            EVENTLOG_ERROR_TYPE,
            0,
            PERFLOG_UNABLE_ALLOC_COUNTER_LIST,
            NULL,
            1,
            sizeof(DWORD),
            szStringArray,
            (LPVOID)&lStatus);
        return FALSE;
    }

    dwType = 0;
    dwData = 0;
    dwSize = sizeof(DWORD);
    lStatus = RegQueryValueEx (
        pArg->hKeyQuery,
        TEXT("Auto Name Interval"),
        NULL,
        &dwType,
        (LPBYTE)&dwData,
        &dwSize);
    if (lStatus != ERROR_SUCCESS) {
        dwData = 0; // default is no autonaming
    } else if (dwType != REG_DWORD) {
        dwData = 0; // default is no autonaming
    } // else assume success

    pArg->dwRenameIntervalCount = dwData;

    dwType = 0;
    dwData = 0;
    dwSize = sizeof(DWORD);
    lStatus = RegQueryValueEx (
        pArg->hKeyQuery,
        TEXT("Auto Rename Units"),
        NULL,
        &dwType,
        (LPBYTE)&dwData,
        &dwSize);
    if (lStatus != ERROR_SUCCESS) {
        dwData = OPD_RENAME_DAYS; // default is days
    } else if (dwType != REG_DWORD) {
        dwData = OPD_RENAME_DAYS; // default is days
    } // else assume success

    pArg->dwRenameIntervalUnits = dwData;

    dwType = 0;
    dwData = 0;
    dwSize = sizeof(DWORD);
    lStatus = RegQueryValueEx (
        pArg->hKeyQuery,
        TEXT("Log File Auto Format"),
        NULL,
        &dwType,
        (LPBYTE)&dwData,
        &dwSize);
    if (lStatus != ERROR_SUCCESS) {
        dwData = OPD_NAME_NNNNNN; // default is a serial number
    } else if (dwType != REG_DWORD) {
        dwData = OPD_NAME_NNNNNN; // default is a serial number
    } // else assume success

    pArg->dwAutoNameFormat = dwData;

    dwType = 0;
    dwData = 0;
    dwSize = sizeof(DWORD);
    lStatus = RegQueryValueEx (
        pArg->hKeyQuery,
        TEXT("Log File Type"),
        NULL,
        &dwType,
        (LPBYTE)&dwData,
        &dwSize);
    if (lStatus != ERROR_SUCCESS) {
        dwData = OPD_CSV_FILE; // default is a CSV file
    } else if (dwType != REG_DWORD) {
        dwData = OPD_CSV_FILE; // default is a CSV file
    } // else assume success

    // convert from OPD to PDH constant
    switch (dwData) {
        case OPD_TSV_FILE:
            pArg->dwLogType = PDH_LOG_TYPE_TSV;
            break;

        case OPD_BIN_FILE:
            pArg->dwLogType = PDH_LOG_TYPE_BINARY;
            break;

        case OPD_CSV_FILE:
        default:
            pArg->dwLogType = PDH_LOG_TYPE_CSV;
            break;
    }

    dwType = 0;
    dwData = 0;
    dwSize = sizeof(DWORD);
    lStatus = RegQueryValueEx (
        pArg->hKeyQuery,
        TEXT("Sample Interval"),
        NULL,
        &dwType,
        (LPBYTE)&dwData,
        &dwSize);
    if (lStatus != ERROR_SUCCESS) {
        dwData = SECONDS_IN_MINUTE; // default is 1 Minute samples
    } else if (dwType != REG_DWORD) {
        dwData = SECONDS_IN_MINUTE; // default is 1 Minute samples
    } // else assume success

    pArg->dwTimeInterval = dwData;

    // get filename or components if auto name

    if (pArg->dwRenameIntervalCount > 0) {
        // this is an autoname file so get components
        dwType = 0;
        *szDefaultDir = 0;
        dwSize = MAX_PATH * sizeof(TCHAR);
        lStatus = RegQueryValueEx (
            pArg->hKeyQuery,
            TEXT("Log Default Directory"),
            NULL,
            &dwType,
            (LPBYTE)&szDefaultDir[0],
            &dwSize);
        if (lStatus != ERROR_SUCCESS) {
            *szDefaultDir = 0;
        } // else assume success

        dwType = 0;
        *szBaseName = 0;
        dwSize = MAX_PATH * sizeof(TCHAR);
        lStatus = RegQueryValueEx (
            pArg->hKeyQuery,
            TEXT("Base Filename"),
            NULL,
            &dwType,
            (LPBYTE)&szBaseName[0],
            &dwSize);
        if (lStatus != ERROR_SUCCESS) {
            // apply default
            lstrcpy (szBaseName, TEXT("perfdata"));
        } // else assume success

        dwType = 0;
        dwSize = 0;
        lStatus = RegQueryValueEx (
            pArg->hKeyQuery,
            TEXT("Command File"),
            NULL,
            &dwType,
            NULL,
            &dwSize);
        if (lStatus != ERROR_SUCCESS) {
            // assume no command filname
            pArg->szCmdFileName = NULL;
        } else {
            // allocate a buffer for this field and collect data
            pArg->szCmdFileName = (LPTSTR)G_ALLOC(dwSize);
            if (pArg->szCmdFileName != NULL) {
                // get command filename
                dwType = 0;
                lStatus = RegQueryValueEx (
                    pArg->hKeyQuery,
                    TEXT("Command File"),
                    NULL,
                    &dwType,
                    (LPBYTE)pArg->szCmdFileName,
                    &dwSize);
                if (lStatus != ERROR_SUCCESS) {
                    // apply default
                    pArg->szCmdFileName = NULL;
                } // else assume success
            }
            if (pArg->szCmdFileName == NULL) {
                // log error message
                // no command file could be read so issue
                // warning and continue
                szStringArray[0] = pArg->szQueryName;
                ReportEvent (hEventLog,
                    EVENTLOG_WARNING_TYPE,
                    0,
                    PERFLOG_ALLOC_CMDFILE_BUFFER,
                    NULL,
                    1,
                    sizeof(DWORD),
                    szStringArray,
                    (LPVOID)&lStatus);
            }
        }

    } else {
        // this is a manual name file so read name
        dwType = 0;
        *szCurrentLogFile = 0;
        dwSize = MAX_PATH * sizeof(TCHAR);
        lStatus = RegQueryValueEx (
            pArg->hKeyQuery,
            TEXT("Log Filename"),
            NULL,
            &dwType,
            (LPBYTE)&szCurrentLogFile[0],
            &dwSize);
        if (lStatus != ERROR_SUCCESS) {
            // apply default
            lstrcpy (szCurrentLogFile, TEXT("c:\\perfdata.log"));
        } // else assume success
    }

    dwType = 0;
    dwData = 0;
    dwSize = sizeof(DWORD);
    lStatus = RegQueryValueEx (
        pArg->hKeyQuery,
        TEXT("Log File Serial Number"),
        NULL,
        &dwType,
        (LPBYTE)&dwData,
        &dwSize);
    if (lStatus != ERROR_SUCCESS) {
        dwData = 1; // default is to start at 1
    } else if (dwType != REG_DWORD) {
        dwData = 1; // default is to start at 1
    } // else assume success

    pArg->dwCurrentSerialNumber = dwData;
    return TRUE;
}

static
LONG
DoCommandFile (
    IN  LPLOG_THREAD_DATA   pArg,
    IN  LPTSTR              szLogFileName,
    IN  BOOL                bStillRunning
)
{
    LONG    lStatus;
    BOOL    bStatus;
    LPTSTR  szCommandString;
    LONG    nErrorMode;
    TCHAR   TempBuffer [ 5 * MAX_PATH] ;
    DWORD   StringLen;
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    DWORD          dwCreationFlags = NORMAL_PRIORITY_CLASS;

    szCommandString = (LPTSTR)G_ALLOC(4096 * sizeof(TCHAR));

    if (szCommandString != NULL) {
        // build command line arguments
        szCommandString[0] = _T('\"');
        lstrcpy (&szCommandString[1], szLogFileName);
        lstrcat (szCommandString, TEXT("\" "));
        lstrcat (szCommandString,
            (bStillRunning ? TEXT("1") : TEXT("0")));

        // initialize Startup Info block
        memset (&si, 0, sizeof(si));
        si.cb = sizeof(si);
        si.dwFlags = STARTF_USESHOWWINDOW ;
        si.wShowWindow = SW_SHOWNOACTIVATE ;

        memset (&pi, 0, sizeof(pi));

        // supress pop-ups inf the detached process
        nErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);

        lstrcpy (TempBuffer, pArg->szCmdFileName) ;

        // see if this is a CMD or a BAT file
        // if it is then create a process with a console window, otherwise
        // assume it's an executable file that will create it's own window
        // or console if necessary
        //
        _tcslwr (TempBuffer);
        if ((_tcsstr(TempBuffer, TEXT(".bat")) != NULL) ||
            (_tcsstr(TempBuffer, TEXT(".cmd")) != NULL)){
                dwCreationFlags |= CREATE_NEW_CONSOLE;
        } else {
                dwCreationFlags |= DETACHED_PROCESS;
        }
        // recopy the image name to the temp buffer since it was modified
        // (i.e. lowercased) for the previous comparison.

        lstrcpy (TempBuffer, pArg->szCmdFileName) ;
        StringLen = lstrlen (TempBuffer) ;

        // now add on the alert text preceded with a space char
        TempBuffer [StringLen] = TEXT(' ') ;
        StringLen++ ;
        lstrcpy (&TempBuffer[StringLen], szCommandString) ;

        bStatus = CreateProcess (
            NULL,
            TempBuffer,
            NULL, NULL, FALSE,
            dwCreationFlags,
            NULL,
            NULL,
            &si,
            &pi);

        SetErrorMode(nErrorMode);

        if (bStatus) {
            lStatus = ERROR_SUCCESS;
        } else {
            lStatus = GetLastError();
        }

    } else {
        lStatus = ERROR_OUTOFMEMORY;
    }

    return lStatus;
}

BOOL
LoggingProc (
    IN    LPLOG_THREAD_DATA pArg
)
{
    HQUERY          hQuery;
    HCOUNTER        hThisCounter;
    HLOG            hLog;
    DWORD           dwDelay;
    DWORD           dwSampleInterval, dwSampleTime=-1;
    PDH_STATUS      pdhStatus;
    DWORD           dwNumCounters;
    LONG            lStatus;
    TCHAR           szDefaultDir[MAX_PATH];
    TCHAR           szBaseName[MAX_PATH];

    LPTSTR          szThisPath;
    DWORD           dwLogType = PDH_LOG_TYPE_CSV;
    BOOL            bRun = FALSE;
    DWORD           dwSamplesUntilNewFile;
    TCHAR           szCurrentLogFile[MAX_PATH];
    LONG            lWaitStatus;
    LPTSTR          szStringArray[4];
    DWORD           dwFileSizeLimit;
    LONGLONG        llFileSizeLimit;
    LONGLONG        llFileSize;
    PLOG_COUNTER_INFO   pCtrInfo;

	SYSTEMTIME	    st;
    LONGLONG        llStartTime = 0;
    LONGLONG        llFinishTime = 0;

    // read registry values

    if (!LoadDataFromRegistry (pArg, szDefaultDir, szBaseName, szCurrentLogFile)) {
        // unable to initialize the query from the registry
        return FALSE;
    }

    // convert to milliseconds for use in timeouts
    dwSampleInterval = pArg->dwTimeInterval * 1000L;

    // open query and add counters from info file

    pdhStatus = PdhOpenQuery (NULL, 0, &hQuery); // from current activity
    if (pdhStatus == ERROR_SUCCESS) {
        dwNumCounters = 0;
        for (szThisPath = pArg->mszCounterList;
            *szThisPath != 0;
            szThisPath += lstrlen(szThisPath) + 1) {
            pdhStatus = PdhAddCounter (hQuery,
                (LPTSTR)szThisPath, dwNumCounters++, &hThisCounter);

            if (pdhStatus == ERROR_SUCCESS) {
                // then add this handle to the list
                pCtrInfo = G_ALLOC (sizeof (LOG_COUNTER_INFO));
                if (pCtrInfo != NULL) {
                    // insert at front of list since the order isn't
                    // important and this is simpler than walking the
                    // list each time.
                    pCtrInfo->hCounter = hThisCounter;
                    pCtrInfo->next = pFirstCounter;
                    pFirstCounter = pCtrInfo;
                }
            }
        }

        // to make sure we get to log the data
        SetThreadPriority (GetCurrentThread(), THREAD_PRIORITY_HIGHEST);

        bRun = TRUE;
        while (bRun) {
            // Get the current Log filename
            if (pArg->dwRenameIntervalCount != 0) {
                // then this is an autonamed file
                // so make current name
                BuildCurrentLogFileName (
                    szBaseName,
                    szDefaultDir,
                    szCurrentLogFile,
                    &pArg->dwCurrentSerialNumber,
                    pArg->dwAutoNameFormat,
                    pArg->dwLogType);
                // reset loop counter
                switch (pArg->dwRenameIntervalUnits) {
                case OPD_RENAME_KBYTES:
                    dwFileSizeLimit = pArg->dwRenameIntervalCount * 1024;
                    dwSamplesUntilNewFile = 0;
                    break;

                case OPD_RENAME_MBYTES:
                    dwFileSizeLimit = pArg->dwRenameIntervalCount * 1024 * 1024;
                    dwSamplesUntilNewFile = 0;
                    break;

                case OPD_RENAME_HOURS:
                case OPD_RENAME_DAYS:
                case OPD_RENAME_MONTHS:
                default:
                    dwSamplesUntilNewFile = GetSamplesInRenameInterval(
                        pArg->dwTimeInterval,
                        pArg->dwRenameIntervalCount,
                        pArg->dwRenameIntervalUnits);
                    dwFileSizeLimit = 0;
                    break;
                }
            } else {
                // filename is left as read from the registry
                dwSamplesUntilNewFile = 0;
                dwFileSizeLimit = 0;
            }
            llFileSizeLimit = dwFileSizeLimit;
            // open log file using this query
            dwLogType = pArg->dwLogType;
            pdhStatus = PdhOpenLog (
                szCurrentLogFile,
                PDH_LOG_WRITE_ACCESS |
                    PDH_LOG_CREATE_ALWAYS,
                &dwLogType,
                hQuery,
                0,
                NULL,
                &hLog);

            if (pdhStatus == ERROR_SUCCESS) {
                szStringArray[0] = pArg->szQueryName;
                szStringArray[1] = szCurrentLogFile;
                ReportEvent (hEventLog,
                    EVENTLOG_INFORMATION_TYPE,
                    0,
                    PERFLOG_LOGGING_QUERY,
                    NULL,
                    2,
                    0,
                    szStringArray,
                    NULL);
                // start sampling immediately
                dwDelay = 0;
                while ((lWaitStatus = WaitForSingleObject (pArg->hExitEvent, dwDelay)) == WAIT_TIMEOUT) {
                    // the event flag will be set when the sampling should exit. if
                    // the wait times out, then that means it's time to collect and
                    // log another sample of data.
                    // time the call to adjust the wait time

                    GetLocalFileTime (&st, &llStartTime);

                    pdhStatus = PdhUpdateLog (hLog, NULL);

                    if (pdhStatus == ERROR_SUCCESS) {
                        // see if it's time to rename the file
                        if (dwSamplesUntilNewFile) {
                            if (!--dwSamplesUntilNewFile) break;
                        } else if (llFileSizeLimit) {
                            // see if the file is too big
                            pdhStatus = PdhGetLogFileSize (hLog, &llFileSize);
                            if (pdhStatus == ERROR_SUCCESS) {
                                if (llFileSizeLimit <= llFileSize) break;
                            }
                        }
                        // compute new timeout value
                        if (dwSampleTime < dwSampleInterval) {
                            dwDelay = dwSampleInterval - dwSampleTime;
                        } else {
                            dwDelay = 0;
                        }
                    } else {
                        // unable to update the log so log event and exit
                        ReportEvent (hEventLog,
                            EVENTLOG_ERROR_TYPE,
                            0,
                            PERFLOG_UNABLE_UPDATE_LOG,
                            NULL,
                            0,
                            sizeof(DWORD),
                            NULL,
                            (LPVOID)&pdhStatus);

                        bRun = FALSE;
                        break;
                    }
                    GetLocalFileTime (&st, &llFinishTime);
                    // compute difference and convert to milliseconds
                    dwSampleTime = (DWORD)((llFinishTime - llStartTime) / 10000L);
                } // end while wait keeps timing out
                if (lWaitStatus == WAIT_OBJECT_0) {
                    // then the loop was terminated by the Exit event
                    // so clear the "run" flag to exit the loop & thread
                    bRun = FALSE;
                }

                // close log file, but keep query open
                PdhCloseLog (hLog, 0);

                if (pArg->szCmdFileName != NULL) {
                    DoCommandFile (pArg, szCurrentLogFile, bRun);
                }
            } else {
                // unable to open log file so log event log message
                szStringArray[0] = szCurrentLogFile;
                ReportEvent (hEventLog,
                    EVENTLOG_ERROR_TYPE,
                    0,
                    PERFLOG_UNABLE_OPEN_LOG_FILE,
                    NULL,
                    1,
                    sizeof(DWORD),
                    szStringArray,
                    (LPVOID)&pdhStatus);

                bRun = FALSE; // exit now
            }
        } // end while (bRun)
        PdhCloseQuery (hQuery);

        // update log serial number if necssary
        if (pArg->dwAutoNameFormat == OPD_NAME_NNNNNN) {
            lStatus = RegSetValueEx (
                pArg->hKeyQuery,
                TEXT("Log File Serial Number"),
                0L,
                REG_DWORD,
                (LPBYTE)&pArg->dwCurrentSerialNumber,
                sizeof(DWORD));
        }
    } else {
        // unable to open query so write event log message
        ReportEvent (hEventLog,
            EVENTLOG_ERROR_TYPE,
            0,
            PERFLOG_UNABLE_OPEN_LOG_QUERY,
            NULL,
            0,
            sizeof(DWORD),
            NULL,
            (LPVOID)&pdhStatus);

    }

    // free allocated buffers
    if (pArg->mszCounterList != NULL) {
        G_FREE(pArg->mszCounterList);
        pArg->mszCounterList = NULL;
    }

    if (pArg->szCmdFileName != NULL) {
        G_FREE(pArg->szCmdFileName);
        pArg->szCmdFileName = NULL;
    }

    return bRun;
}

DWORD
LoggingThreadProc (
    IN    LPVOID    lpThreadArg
)
{
    LPLOG_THREAD_DATA   pThreadData = (LPLOG_THREAD_DATA)lpThreadArg;
    DWORD               dwStatus = ERROR_SUCCESS;
    BOOL                bContinue = TRUE;

    if (pThreadData != NULL) {
        // read config from registry

        do {
            // read config from registry
            // expand counter paths as necessary
            // call Logging function
            bContinue = LoggingProc (pThreadData);
            // see if this thread was paused for reloading
            // or stopped to terminate
            if (pThreadData->bReloadNewConfig) {
                bContinue = TRUE;
            } // else  bContinue is always returned as FALSE
            // so that will terminate this loop
        } while (bContinue);
        dwStatus = ERROR_SUCCESS;
    } else {
        // unable to find data block so return
        dwStatus = ERROR_INVALID_PARAMETER;
    }

    return dwStatus;
}