/*++

Copyright (C) 1996-1999 Microsoft Corporation

Module Name:

    log_text.c

Abstract:

    <abstract>

--*/

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <mbctype.h>
#include <pdh.h>
#include "pdhidef.h"
#include "log_text.h"
#include "pdhmsg.h"
#include "strings.h"    

#pragma warning ( disable : 4213)

#define TAB_DELIMITER   '\t'
#define COMMA_DELIMITER ','
#define DOUBLE_QUOTE    '\"'
#define VALUE_BUFFER_SIZE   32

LPCSTR  PdhiszFmtTimeStamp = "\"%2.2d/%2.2d/%2.2d %2.2d:%2.2d:%2.2d.%3.3d\"";
LPCSTR  PdhiszFmtStringValue =  "%c\"%s\"";
LPCSTR  PdhiszFmtRealValue =    "%c\"%.20g\"";
TIME_ZONE_INFORMATION TimeZone;
// LPCSTR  PdhiszTimeStampLabel = " Sample Time\"";
// DWORD   PdhidwTimeStampLabelLength  = 13;

extern  LPCSTR  PdhiszRecordTerminator;
extern  DWORD   PdhidwRecordTerminatorLength;

#define TEXTLOG_TYPE_ID_RECORD  1
#define TEXTLOG_HEADER_RECORD   1
#define TEXTLOG_FIRST_DATA_RECORD   2

#define TIME_FIELD_COUNT        7
#define TIME_FIELD_BUFF_SIZE    24
DWORD   dwTimeFieldOffsetList[TIME_FIELD_COUNT] = {2, 5, 10, 13, 16, 19, 23};

#define MAX_TEXT_FILE_SIZE ((LONGLONG)0x0000000077FFFFFF)

PDH_FUNCTION
PdhiBuildFullCounterPath(
    IN  BOOL               bMachine,
    IN  PPDHI_COUNTER_PATH pCounterPath,
    IN  LPWSTR             szObjectName,
    IN  LPWSTR             szCounterName,
    IN  LPWSTR             szFullPath
);

STATIC_BOOL
PdhiDateStringToFileTimeA (
    IN  LPSTR   szDateTimeString,
    IN  LPFILETIME  pFileTime
);

STATIC_DWORD
PdhiGetStringFromDelimitedListA (
    IN  LPSTR   szInputString,
    IN  DWORD   dwItemIndex,
    IN  CHAR    cDelimiter,
    IN  DWORD   dwFlags,
    IN  LPSTR   szOutputString,
    IN  DWORD   cchBufferLength
);

STATIC_PDH_FUNCTION
PdhiReadOneTextLogRecord (
    IN  PPDHI_LOG   pLog,
    IN  DWORD   dwRecordId,
    IN  LPSTR   szRecord,
    IN  DWORD   dwMaxSize
);

STATIC_BOOL
PdhiDateStringToFileTimeA (
    IN  LPSTR   szDateTimeString,
    IN  LPFILETIME  pFileTime
)
{
    CHAR    mszTimeFields[TIME_FIELD_BUFF_SIZE];
    DWORD   dwThisField;
    LONG    lValue;
    SYSTEMTIME  st;

    // make string into msz
    lstrcpynA (mszTimeFields, szDateTimeString, TIME_FIELD_BUFF_SIZE);
    for (dwThisField = 0; dwThisField < TIME_FIELD_COUNT; dwThisField++) {
        mszTimeFields[dwTimeFieldOffsetList[dwThisField]] = 0;
    }

    // read string into system time structure
    dwThisField = 0;
    st.wDayOfWeek = 0;
    lValue = atol(&mszTimeFields[0]);
    st.wMonth = LOWORD(lValue);
    lValue = atol(&mszTimeFields[dwTimeFieldOffsetList[dwThisField++]+1]);
    st.wDay = LOWORD(lValue);
    lValue = atol(&mszTimeFields[dwTimeFieldOffsetList[dwThisField++]+1]);
    st.wYear = LOWORD(lValue);
    lValue = atol(&mszTimeFields[dwTimeFieldOffsetList[dwThisField++]+1]);
    st.wHour = LOWORD(lValue);
    lValue = atol(&mszTimeFields[dwTimeFieldOffsetList[dwThisField++]+1]);
    st.wMinute = LOWORD(lValue);
    lValue = atol(&mszTimeFields[dwTimeFieldOffsetList[dwThisField++]+1]);
    st.wSecond = LOWORD(lValue);
    lValue = atol(&mszTimeFields[dwTimeFieldOffsetList[dwThisField++]+1]);
    st.wMilliseconds = LOWORD(lValue);

    return SystemTimeToFileTime (&st, pFileTime);
}

#define PDHI_GSFDL_REMOVE_QUOTES    0x00000001
#define PDHI_GSFDL_REMOVE_NONPRINT  0x00000002
STATIC_DWORD
PdhiGetStringFromDelimitedListA (
    IN  LPSTR   szInputString,
    IN  DWORD   dwItemIndex,
    IN  CHAR    cDelimiter,
    IN  DWORD   dwFlags,
    IN  LPSTR   szOutputString,
    IN  DWORD   cchBufferLength
)
{
    DWORD   dwCurrentIndex = 0;
    LPSTR   szCurrentItem;
    LPSTR   szSrcPtr, szDestPtr;
    DWORD   dwReturn       = 0;
    BOOL    bInsideQuote   = FALSE;

    // go to desired entry in string, 0 = first entry
    szCurrentItem = szInputString;

    while (dwCurrentIndex < dwItemIndex) {
        // goto next delimiter or terminator
        while (* szCurrentItem != cDelimiter || bInsideQuote) {
            if (* szCurrentItem == 0) break;
            else if (* szCurrentItem == DOUBLEQUOTE_A) {
                bInsideQuote = ! bInsideQuote;
            }
            szCurrentItem ++;
        }
        if (* szCurrentItem != 0) szCurrentItem ++;
        dwCurrentIndex++;
    }
    if (*szCurrentItem != 0) {
        // then copy to the user's buffer, as long as it fits
        szSrcPtr     = szCurrentItem;
        szDestPtr    = szOutputString;
        dwReturn     = 0;
        bInsideQuote = FALSE;

        while (   (dwReturn < cchBufferLength)
               && (* szSrcPtr != 0)
               && (* szSrcPtr != cDelimiter || bInsideQuote)) {
            if (* szSrcPtr == DOUBLEQUOTE_A) {
                bInsideQuote = ! bInsideQuote;
                if (dwFlags & PDHI_GSFDL_REMOVE_QUOTES) {
                    // skip the quote
                    szSrcPtr ++;
                    continue;
                }
            }

            if (dwFlags & PDHI_GSFDL_REMOVE_NONPRINT) {
                if ((UCHAR) * szSrcPtr < (UCHAR) ' ') {
                    // skip the control char
                    szSrcPtr ++;
                    continue;
                }
            }

            // copy character
            * szDestPtr ++ = * szSrcPtr ++;
            dwReturn ++; // increment length
        }
        if (dwReturn > 0) {
            * szDestPtr = 0; // add terminator char
        }
    }
    return dwReturn;
}

STATIC_PDH_FUNCTION 
PdhiReadOneTextLogRecord (
    IN  PPDHI_LOG   pLog,
    IN  DWORD   dwRecordId,
    IN  LPSTR   szRecord,
    IN  DWORD   dwMaxSize
)
// reads the specified record from the log file and returns it as an ANSI
// character string
{
    LPSTR   szTempBuffer;
    LPSTR   szOldBuffer;
    LPSTR   szTempBufferPtr;
    LPSTR   szReturn;
    PDH_STATUS  pdhStatus;
    int     nFileError = 0;
    DWORD   dwRecordLength;
    DWORD   dwBytesRead = 0;

    if (pLog->dwMaxRecordSize == 0) {
        // initialize with a default value
        dwRecordLength = SMALL_BUFFER_SIZE;
    } else {
        // use current maz record size max.
        dwRecordLength = pLog->dwMaxRecordSize;
    }

    szTempBuffer = G_ALLOC (dwRecordLength);
    if (szTempBuffer == NULL) {
        return PDH_MEMORY_ALLOCATION_FAILURE;
    }
    // position file pointer to desired record;

    if (dwRecordId == pLog->dwLastRecordRead) {
        // then return the current record from the cached buffer
        if ((DWORD)lstrlenA((LPSTR)pLog->pLastRecordRead) < dwMaxSize) {
            lstrcpyA(szRecord, (LPSTR)pLog->pLastRecordRead);
            pdhStatus = ERROR_SUCCESS;
        } else {
            pdhStatus = PDH_MORE_DATA;
        }
        // free temp buffer
        if (szTempBuffer != NULL) {
            G_FREE (szTempBuffer);
        }
    } else {
        if ((dwRecordId < pLog->dwLastRecordRead) || (pLog->dwLastRecordRead == 0)){
            // the desired record is before the current position
            // or the counter has been reset so we have to
            // go to the beginning of the file and read up to the specified
            // record.
            pLog->dwLastRecordRead = 0;
            rewind (pLog->StreamFile);
        }

        // free old buffer
        if (pLog->pLastRecordRead != NULL) {
            G_FREE (pLog->pLastRecordRead);
            pLog->pLastRecordRead = NULL;
        }

        // now seek to the desired entry
        do {
            szReturn = fgets (szTempBuffer, dwRecordLength, pLog->StreamFile);
            if (szReturn == NULL) {
                if (!feof(pLog->StreamFile)) {
                    nFileError = ferror (pLog->StreamFile);
                }
                break; // end of file
            } else {
                // see if an entire record was read
                dwBytesRead = lstrlenA(szTempBuffer);
                // see if the last char is a new line
                if ((dwBytesRead > 0) &&
                    (szTempBuffer[dwBytesRead-1] != '\r') &&
                    (szTempBuffer[dwBytesRead-1] != '\n')) {
                    // then if the record size is the same as the buffer
                    // or there's more text in this record...
                    // just to be safe, we'll realloc the buffer and try
                    // reading some more
                    while (dwBytesRead == dwRecordLength-1) {
                        dwRecordLength += SMALL_BUFFER_SIZE;
                        szOldBuffer = szTempBuffer;
                        szTempBuffer = G_REALLOC (szOldBuffer, dwRecordLength);
                        if (szTempBuffer == NULL) {
                            G_FREE(szOldBuffer);
                            pLog->dwLastRecordRead = 0;
                            pdhStatus = PDH_MEMORY_ALLOCATION_FAILURE;
                            goto Cleanup;
                        }
                        // position read pointer at end of bytes already read
                        szTempBufferPtr = szTempBuffer + dwBytesRead;

                        szReturn = fgets (szTempBufferPtr,
                            dwRecordLength - dwBytesRead,
                            pLog->StreamFile);
                        if (szReturn == NULL) {
                            if (!feof(pLog->StreamFile)) {
                                nFileError = ferror (pLog->StreamFile);
                            }
                            break; // end of file
                        } else {
                            // the BytesRead value already includes the NULL
                            dwBytesRead += lstrlenA(szTempBufferPtr);
                        }
                    } // end while finding the end of the record
                    // update the record length
                    // add one byte to the length read to prevent entering the
                    // recalc loop on records of the same size
                    dwRecordLength = dwBytesRead + 1;
                    szOldBuffer = szTempBuffer;
                    szTempBuffer = G_REALLOC (szOldBuffer, dwRecordLength);
                    if (szTempBuffer == NULL) {
                        G_FREE(szOldBuffer);
                        pLog->dwLastRecordRead = 0;
                        pdhStatus = PDH_MEMORY_ALLOCATION_FAILURE;
                        goto Cleanup;
                    }
                    assert (szTempBuffer != NULL);
                } // else the whole record fit
            }

        } while (++pLog->dwLastRecordRead < dwRecordId);

        // update the max record length for the log file.
        if (dwRecordLength> pLog->dwMaxRecordSize) {
            pLog->dwMaxRecordSize = dwRecordLength;
        }

        // if the desired one was found then return it
        if (szReturn != NULL) {
            // then a record was read so update the cached values and return
            // the data
            pLog->pLastRecordRead = (LPVOID)szTempBuffer;

            // copy to the caller's buffer
            if (dwBytesRead < dwMaxSize) {
                lstrcpyA(szRecord, (LPSTR)pLog->pLastRecordRead);
                pdhStatus = ERROR_SUCCESS;
            } else {
                pdhStatus = PDH_MORE_DATA;
            }
        } else {
            // reset the pointers and buffers
            pLog->dwLastRecordRead = 0;
            G_FREE (szTempBuffer);
            pdhStatus = PDH_END_OF_LOG_FILE;
        }
    }

Cleanup:
    return pdhStatus;
}

PDH_FUNCTION
PdhiGetTextLogCounterInfo (
    IN  PPDHI_LOG       pLog,
    IN  PPDHI_COUNTER   pCounter
)
{
    PDH_STATUS  pdhStatus;
    LPSTR       szReturn;
    CHAR        cDelim;
    CHAR        szTemp[4];
    LPSTR       szAnsiCounterPath = NULL;
    LPSTR       szAnsiCounter     = NULL;
    LPWSTR      szUnicodeCounter  = NULL;
    DWORD       dwIndex;
    LPSTR       szThisItem;
    DWORD       dwPathLength;
    DWORD       dwBufferLength;
    DWORD       dwItemLength;
    DWORD       dwInstanceId = 0;
    BOOL        bNoMachine   = FALSE;

    if (lstrcmpiW(pCounter->pCounterPath->szMachineName, L"\\\\.") == 0) {
        bNoMachine = TRUE;
    }

    // allocate extra space for DBCS characters
    //
    dwPathLength = lstrlenW(pCounter->pCounterPath->szMachineName)  + 1
                 + lstrlenW(pCounter->pCounterPath->szObjectName)   + 1
                 + lstrlenW(pCounter->pCounterPath->szParentName)   + 4 
                 + lstrlenW(pCounter->pCounterPath->szInstanceName) + 2
                 + lstrlenW(pCounter->pCounterPath->szCounterName)  + 1;
    if ((lstrlenW(pCounter->szFullName) + 1) > (LONG) dwPathLength) {
        dwPathLength = lstrlenW(pCounter->szFullName) + 1;
    }
    szAnsiCounterPath = G_ALLOC(dwPathLength * 3 * sizeof(CHAR));
    szAnsiCounter     = G_ALLOC(dwPathLength * 3 * sizeof(CHAR));
    szUnicodeCounter  = G_ALLOC(dwPathLength * sizeof(WCHAR));
    if (szAnsiCounterPath == NULL || szUnicodeCounter == NULL
                                  || szAnsiCounter == NULL) {
        pdhStatus = PDH_MEMORY_ALLOCATION_FAILURE;
        goto Cleanup;
    } else {
        PdhiBuildFullCounterPath((bNoMachine ? FALSE : TRUE),
                                 pCounter->pCounterPath,
                                 pCounter->pCounterPath->szObjectName,
                                 pCounter->pCounterPath->szCounterName,
                                 szUnicodeCounter);
        WideCharToMultiByte(_getmbcp(),
                            0,
                            pCounter->szFullName,
                            lstrlenW(pCounter->szFullName),
                            szAnsiCounterPath,
                            dwPathLength,
                            NULL,
                            NULL);
        WideCharToMultiByte(_getmbcp(),
                            0,
                            szUnicodeCounter,
                            lstrlenW(szUnicodeCounter),
                            szAnsiCounter,
                            dwPathLength,
                            NULL,
                            NULL);
    }

    szReturn = &szTemp[0]; // for starters
    // read the log file's header record
    pdhStatus = PdhiReadOneTextLogRecord (
        pLog,
        TEXTLOG_HEADER_RECORD,
        szReturn,
        1); // we're lying to prevent copying the record.
    // what's in szReturn is not important since we'll be reading the
    // data from the "last Record read" buffer

    if (pLog->dwLastRecordRead == TEXTLOG_HEADER_RECORD) {
        cDelim = (CHAR)((LOWORD(pLog->dwLogFormat) == PDH_LOG_TYPE_CSV) ?
            COMMA_DELIMITER : TAB_DELIMITER);

        // then the seek worked and we can use the buffer
        dwBufferLength =
            lstrlenA((LPSTR)pLog->pLastRecordRead) + 1;
        szReturn = G_ALLOC (dwBufferLength * sizeof(CHAR));
        if (szReturn != NULL) {
            lstrcpyA (szReturn, (LPSTR)pLog->pLastRecordRead);
            dwIndex = 0;
            szThisItem = NULL;
            while ((dwItemLength = PdhiGetStringFromDelimitedListA(
                    (LPSTR)pLog->pLastRecordRead,
                    ++dwIndex,
                    cDelim,
                    PDHI_GSFDL_REMOVE_QUOTES | PDHI_GSFDL_REMOVE_NONPRINT,
                    szReturn,
                    dwBufferLength)) != 0) {
                if (lstrcmpiA(szReturn, szAnsiCounterPath) == 0) {
                    szThisItem = szReturn;
                    break;
                }
                else if (lstrcmpiA(szReturn, szAnsiCounter) == 0) {
                    // then this is the desired counter
                    if (dwInstanceId < pCounter->pCounterPath->dwIndex) {
                        dwInstanceId ++;
                    }
                    else {
                        szThisItem = szReturn;
                        break;
                    }
                }
            }
            if (szThisItem != NULL) {
                if (bNoMachine) {
                    pCounter->pCounterPath->szMachineName = NULL;
                }
                // this is a valid counter so update the fields
                // for Text logs, none of this info is used
                pCounter->plCounterInfo.dwObjectId = 0;
                pCounter->plCounterInfo.lInstanceId = dwInstanceId;
                pCounter->plCounterInfo.szInstanceName = NULL;
                pCounter->plCounterInfo.dwParentObjectId = 0;
                pCounter->plCounterInfo.szParentInstanceName = NULL;
                // this data is used by the log file readers
                pCounter->plCounterInfo.dwCounterId = dwIndex;
                pCounter->plCounterInfo.dwCounterType = PERF_DOUBLE_RAW;
                pCounter->plCounterInfo.dwCounterSize = 8;
                pdhStatus = ERROR_SUCCESS;
            } else {
                // counter not found
                pdhStatus = PDH_CSTATUS_NO_COUNTER;
            }
            G_FREE (szReturn);
        } else {
            pdhStatus = PDH_MEMORY_ALLOCATION_FAILURE;
        }
    } else {
        // unable to read header from log file
        pdhStatus = PDH_UNABLE_READ_LOG_HEADER;
    }

Cleanup:
    if (szAnsiCounterPath) G_FREE(szAnsiCounterPath);
    if (szAnsiCounter)     G_FREE(szAnsiCounter);
    if (szUnicodeCounter)  G_FREE(szUnicodeCounter);

    return pdhStatus;
}

PDH_FUNCTION
PdhiOpenInputTextLog (
    IN  PPDHI_LOG   pLog
)
{
    PDH_STATUS  pdhStatus;

        // open a stream handle for easy C RTL I/O
        pLog->StreamFile = _wfopen (pLog->szLogFileName, (LPCWSTR)L"rt");
        if (pLog->StreamFile == NULL ||
                        pLog->StreamFile == (FILE *)((DWORD_PTR)(-1))) {
            pLog->StreamFile = (FILE *)((DWORD_PTR) (-1));
            pdhStatus        = PDH_LOG_FILE_OPEN_ERROR;
        } else {
            pdhStatus = ERROR_SUCCESS;
        }
    return pdhStatus;
}

PDH_FUNCTION
PdhiOpenOutputTextLog (
    IN  PPDHI_LOG   pLog
)
{
    PDH_STATUS  pdhStatus;

    pLog->StreamFile = (FILE *)((DWORD_PTR)(-1));
    pLog->dwRecord1Size = 0;
    pdhStatus = ERROR_SUCCESS;

    return pdhStatus;
}

PDH_FUNCTION
PdhiCloseTextLog (
    IN  PPDHI_LOG   pLog,
    IN  DWORD       dwFlags
)
{
    PDH_STATUS  pdhStatus;

    UNREFERENCED_PARAMETER (dwFlags);

    if (pLog->StreamFile != NULL &&
                    pLog->StreamFile != (FILE *)((DWORD_PTR)(-1))) {
       fclose (pLog->StreamFile);
       pLog->StreamFile = (FILE *)((DWORD_PTR)(-1));
    }
    pdhStatus = ERROR_SUCCESS;
    return pdhStatus;
}

PDH_FUNCTION
PdhiWriteTextLogHeader (
    IN  PPDHI_LOG   pLog,
    IN  LPCWSTR     szUserCaption
)
{
    PDH_STATUS      pdhStatus = ERROR_SUCCESS;
    PPDHI_COUNTER   pThisCounter;
    CHAR            cDelim;
    CHAR            szLeadDelim[4];
    DWORD           dwLeadSize;
    CHAR            szTrailDelim[4];
    DWORD           dwTrailSize;
    DWORD           dwBytesWritten;
    LPSTR           szCounterPath  = NULL;
    LPWSTR          wszCounterPath = NULL;
    LPSTR           szLocalCaption = NULL;
    DWORD           dwCaptionSize = 0;
    BOOL            bDefaultCaption;

    LPSTR           szOutputString = NULL;
    LPSTR           szTmpString;
    DWORD           dwStringBufferSize = 0;
    DWORD           dwStringBufferUsed = 0;
    DWORD           dwNewStringLen;

    szCounterPath  = G_ALLOC(MEDIUM_BUFFER_SIZE * sizeof(CHAR));
    wszCounterPath = G_ALLOC(MEDIUM_BUFFER_SIZE * sizeof(WCHAR));
    szOutputString = G_ALLOC(MEDIUM_BUFFER_SIZE * sizeof(CHAR));
    if (szCounterPath == NULL || wszCounterPath == NULL
                              || szOutputString == NULL) {
        pdhStatus = PDH_MEMORY_ALLOCATION_FAILURE;
        goto Cleanup;
    }
    dwStringBufferSize = MEDIUM_BUFFER_SIZE;

    cDelim = (CHAR)((LOWORD(pLog->dwLogFormat) == PDH_LOG_TYPE_CSV) ? COMMA_DELIMITER :
            TAB_DELIMITER);

    szLeadDelim[0] = cDelim;
    szLeadDelim[1] = DOUBLE_QUOTE;
    szLeadDelim[2] = 0;
    szLeadDelim[3] = 0;
    dwLeadSize = 2 * sizeof(szLeadDelim[0]);

    szTrailDelim[0] = DOUBLE_QUOTE;
    szTrailDelim[1] = 0;
    szTrailDelim[2] = 0;
    szTrailDelim[3] = 0;
    dwTrailSize = 1 * sizeof(szTrailDelim[0]);

    // we'll assume the buffer allocated is large enough to hold the timestamp 
    // and 1st counter name. After that we'll test the size first.

    lstrcpyA(szOutputString, szTrailDelim);
    lstrcatA(szOutputString, 
             (LOWORD(pLog->dwLogFormat) == PDH_LOG_TYPE_CSV ?
                                szCsvLogFileHeader : szTsvLogFileHeader));

    {
        // Add TimeZone information
        //
        DWORD dwReturn = GetTimeZoneInformation(&TimeZone);
        CHAR  strTimeZone[MAX_PATH];

        if (dwReturn != TIME_ZONE_ID_INVALID) {
            if (dwReturn == TIME_ZONE_ID_DAYLIGHT) {
                sprintf(strTimeZone, " (%ws)(%d)",
                        TimeZone.DaylightName,
                        TimeZone.Bias + TimeZone.DaylightBias);
            }
            else {
                sprintf(strTimeZone, " (%ws)(%d)",
                        TimeZone.StandardName,
                        TimeZone.Bias + TimeZone.StandardBias);
            }
            lstrcatA(szOutputString, strTimeZone);
            pLog->dwRecord1Size = 1;
        }
    }

    lstrcatA(szOutputString, szTrailDelim);
    lstrlenA(szOutputString);

    // get buffer size here
    dwStringBufferUsed = lstrlenA(szOutputString);

    // check each counter in the list of counters for this query and
    // write them to the file

    // output the path names
    pThisCounter = pLog->pQuery ? pLog->pQuery->pCounterListHead : NULL;

    if (pThisCounter != NULL) {
        do {
            // get the counter path information from the counter
            ZeroMemory(wszCounterPath, sizeof(WCHAR) * MEDIUM_BUFFER_SIZE);
            ZeroMemory(szCounterPath,  sizeof(CHAR)  * MEDIUM_BUFFER_SIZE);

            PdhiBuildFullCounterPath(TRUE,
                                     pThisCounter->pCounterPath,
                                     pThisCounter->pCounterPath->szObjectName,
                                     pThisCounter->pCounterPath->szCounterName,
                                     wszCounterPath);
            WideCharToMultiByte(_getmbcp(),
                                0,
                                wszCounterPath,
                                lstrlenW(wszCounterPath),
                                (LPSTR) szCounterPath,
                                MEDIUM_BUFFER_SIZE,
                                NULL,
                                NULL);
            dwNewStringLen = lstrlenA(szCounterPath);
            dwNewStringLen += dwLeadSize;
            dwNewStringLen += dwTrailSize;

            if ((dwNewStringLen + dwStringBufferUsed) >= dwStringBufferSize) {
                dwStringBufferSize += SMALL_BUFFER_SIZE;
                szTmpString = szOutputString;
                szOutputString = G_REALLOC (szTmpString, dwStringBufferSize);
                if (szOutputString == NULL) {
                    G_FREE(szTmpString);
                    pdhStatus = PDH_MEMORY_ALLOCATION_FAILURE;
                    break; // out of DO loop
                }
            } else {
                // mem alloc ok, so continue
            }

            lstrcatA (szOutputString, szLeadDelim);
            if (pdhStatus == ERROR_SUCCESS) {
                lstrcatA (szOutputString, szCounterPath);
            } else {
                // just write the delimiters and no string inbetween
            }
            lstrcatA (szOutputString, szTrailDelim);

            dwStringBufferUsed += dwNewStringLen;

            pThisCounter = pThisCounter->next.flink; // go to next in list
        } while (pThisCounter != pLog->pQuery->pCounterListHead);
    }

    // test to see if the caller wants to append user strings to the log

    if (((pLog->dwLogFormat & PDH_LOG_OPT_MASK) == PDH_LOG_OPT_USER_STRING) &&
        (pdhStatus == ERROR_SUCCESS)) { 
        // they want to write user data  so  see if they've passed in a
        // caption string
        if (szUserCaption != NULL) {
            dwCaptionSize = lstrlenW (szUserCaption) + 1;
            // allocate larger buffer to accomodate DBCS characters
            dwCaptionSize = dwCaptionSize * 3 * sizeof (CHAR);

            szLocalCaption = (LPSTR) G_ALLOC (dwCaptionSize);
            if (szLocalCaption != NULL) {
                memset(szLocalCaption, 0, dwCaptionSize);
                dwCaptionSize = WideCharToMultiByte(
                                _getmbcp(),
                                0,
                                szUserCaption,
                                lstrlenW(szUserCaption),
                                szLocalCaption,
                                dwCaptionSize,
                                NULL,
                                NULL);
                bDefaultCaption = FALSE;
            } else {
                bDefaultCaption = TRUE;
            }
        } else {
            bDefaultCaption = TRUE;
        }

        if (bDefaultCaption) {
            szLocalCaption = (LPSTR)caszDefaultLogCaption;
            dwCaptionSize = lstrlenA (szLocalCaption);
        }

        dwNewStringLen = (DWORD)dwCaptionSize;
        dwNewStringLen += dwLeadSize;
        dwNewStringLen += dwTrailSize;

        if ((dwNewStringLen + dwStringBufferUsed) >= dwStringBufferSize) {
            dwStringBufferSize += SMALL_BUFFER_SIZE;
            szTmpString = szOutputString;
            szOutputString = G_REALLOC (szTmpString, dwStringBufferSize);
            if (szOutputString == NULL) {
                G_FREE(szTmpString);
                pdhStatus = PDH_MEMORY_ALLOCATION_FAILURE;
            }
        } else {
            // mem alloc ok, so continue
        }

        if (pdhStatus == ERROR_SUCCESS) {
            lstrcatA (szOutputString, szLeadDelim);
#pragma warning (disable : 4701 )    // szLocalCaption is initialized above
            lstrcatA (szOutputString, szLocalCaption);
#pragma warning (default : 4701)    
            lstrcatA (szOutputString, szTrailDelim);

        }

        dwStringBufferUsed += dwNewStringLen;
        if (!bDefaultCaption) {
            G_FREE (szLocalCaption);
        }

    }

    if (pdhStatus == ERROR_SUCCESS) {

        if ((PdhidwRecordTerminatorLength + dwStringBufferUsed) >= dwStringBufferSize) {
            dwStringBufferSize += PdhidwRecordTerminatorLength;
            szTmpString = szOutputString;
            szOutputString = G_REALLOC (szTmpString, dwStringBufferSize);
            if (szOutputString == NULL) {
                G_FREE(szTmpString);
                pdhStatus = PDH_MEMORY_ALLOCATION_FAILURE;
            }
        } else {
            // mem alloc ok, so continue
        }

        if (pdhStatus == ERROR_SUCCESS) {

            lstrcatA (szOutputString, PdhiszRecordTerminator);
            dwStringBufferUsed += PdhidwRecordTerminatorLength;

            // write  the record
            if (!WriteFile (pLog->hLogFileHandle,
                (LPCVOID)szOutputString,
                dwStringBufferUsed,
                &dwBytesWritten,
                NULL)) {
                pdhStatus = GetLastError();
            } else if (pLog->pQuery->hLog == H_REALTIME_DATASOURCE || pLog->pQuery->hLog == H_WBEM_DATASOURCE) {
                FlushFileBuffers(pLog->hLogFileHandle);
            }

            if (dwStringBufferUsed > pLog->dwMaxRecordSize) {
                // then update the buffer size
                pLog->dwMaxRecordSize = dwStringBufferUsed;
            }
        }
    }

Cleanup:
    if (szCounterPath  != NULL) G_FREE(szCounterPath);
    if (wszCounterPath != NULL) G_FREE(wszCounterPath);
    if (szOutputString != NULL) G_FREE(szOutputString);

    return pdhStatus;
}

PDH_FUNCTION
PdhiWriteTextLogRecord (
    IN  PPDHI_LOG   pLog,
    IN  SYSTEMTIME  *stTimeStamp,
    IN  LPCWSTR     szUserString
)
{
    PDH_STATUS      pdhStatus = ERROR_SUCCESS;
    PPDHI_COUNTER   pThisCounter;
    CHAR            cDelim;
    DWORD           dwBytesWritten;
    DWORD           dwBufferSize;
    CHAR            szValueBuffer[VALUE_BUFFER_SIZE];
    PDH_FMT_COUNTERVALUE    pdhValue;

    DWORD           dwUserStringSize;
    LPSTR           szLocalUserString = NULL;
    BOOL            bDefaultUserString;

    LPSTR           szOutputString = NULL;
    DWORD           dwStringBufferSize = 0;
    DWORD           dwStringBufferUsed = 0;
    DWORD           dwNewStringLen;
    SYSTEMTIME      lstTimeStamp;

    LARGE_INTEGER   liFileSize;

    dwStringBufferSize = (MEDIUM_BUFFER_SIZE > pLog->dwMaxRecordSize ? 
        MEDIUM_BUFFER_SIZE : pLog->dwMaxRecordSize);

    szOutputString = G_ALLOC (dwStringBufferSize);
    if (szOutputString == NULL) {
        return PDH_MEMORY_ALLOCATION_FAILURE;
    }

    cDelim = (CHAR)((LOWORD(pLog->dwLogFormat) == PDH_LOG_TYPE_CSV) ? COMMA_DELIMITER :
            TAB_DELIMITER);

    // format and write the time stamp title
    lstTimeStamp = * stTimeStamp;

    dwStringBufferUsed = sprintf (szOutputString, PdhiszFmtTimeStamp,
            lstTimeStamp.wMonth, lstTimeStamp.wDay, lstTimeStamp.wYear,
            lstTimeStamp.wHour, lstTimeStamp.wMinute, lstTimeStamp.wSecond,
            lstTimeStamp.wMilliseconds);

    // check each counter in the list of counters for this query and
    // write them to the file

    pThisCounter = pLog->pQuery ? pLog->pQuery->pCounterListHead : NULL;

    if (pThisCounter != NULL) {
        // lock the query while we read the data so the values
        // written to the log will all be from the same sample
        pdhStatus = WAIT_FOR_AND_LOCK_MUTEX(pThisCounter->pOwner->hMutex);
        if (pdhStatus == ERROR_SUCCESS) {
            do {
                // get the formatted value from the counter

                // compute and format current value
                pdhStatus = PdhiComputeFormattedValue (
                    pThisCounter->CalcFunc,
                    pThisCounter->plCounterInfo.dwCounterType,
                    pThisCounter->lScale,
                    PDH_FMT_DOUBLE | PDH_FMT_NOCAP100,
                    &pThisCounter->ThisValue,
                    &pThisCounter->LastValue,
                    &pThisCounter->TimeBase,
                    0L,
                    &pdhValue);

                if ((pdhStatus == ERROR_SUCCESS) &&
                    ((pdhValue.CStatus == PDH_CSTATUS_VALID_DATA) ||
                     (pdhValue.CStatus == PDH_CSTATUS_NEW_DATA))) {
                    // then this is a valid data value so print it
                    dwBufferSize = sprintf (szValueBuffer,
                        PdhiszFmtRealValue, cDelim, pdhValue.doubleValue);
                } else {
                    // invalid data so show a blank data value
                    dwBufferSize = sprintf (szValueBuffer, PdhiszFmtStringValue, cDelim, caszSpace);
                    // reset error
                    pdhStatus = ERROR_SUCCESS;
                }

                dwNewStringLen = dwBufferSize;

                if ((dwNewStringLen+dwStringBufferUsed) >= dwStringBufferSize) {
                    LPTSTR szNewString;
                    dwStringBufferSize += SMALL_BUFFER_SIZE;
                    szNewString = G_REALLOC (szOutputString, dwStringBufferSize);
                    if (szNewString == NULL) {
                        pdhStatus = PDH_MEMORY_ALLOCATION_FAILURE;
                        break; // out of DO loop
                    }
                    else {
                        szOutputString = szNewString;
                    }
                }

                if (pdhStatus != PDH_MEMORY_ALLOCATION_FAILURE) {
                    lstrcatA (szOutputString, szValueBuffer);
                    dwStringBufferUsed += dwNewStringLen;
                }

                // goto the next counter in the list
                pThisCounter = pThisCounter->next.flink; // go to next in list
            } while (pThisCounter != pLog->pQuery->pCounterListHead);
            // free (i.e. unlock) the query
            RELEASE_MUTEX(pThisCounter->pOwner->hMutex);
        }
    }

    if (pdhStatus == PDH_MEMORY_ALLOCATION_FAILURE) // cannot go further
        goto  endLogText;

    // test to see if the caller wants to append user strings to the log

    if ((pLog->dwLogFormat & PDH_LOG_OPT_MASK) == PDH_LOG_OPT_USER_STRING) {
        // they want to write user data  so  see if they've passed in a
        // display string
        if (szUserString != NULL) {
            // get size in chars
            dwUserStringSize = lstrlenW (szUserString) + 1;
            // allocate larger buffer to accomodate DBCS characters
            dwUserStringSize = dwUserStringSize * 3 * sizeof(CHAR);
            szLocalUserString = (LPSTR) G_ALLOC (dwUserStringSize);
            if (szLocalUserString != NULL) {
                memset(szLocalUserString, 0, dwUserStringSize);
                dwUserStringSize = WideCharToMultiByte(
                                _getmbcp(),
                                0,
                                szUserString,
                                lstrlenW(szUserString),
                                szLocalUserString,
                                dwUserStringSize,
                                NULL,
                                NULL);
                bDefaultUserString = FALSE;
            } else {
                bDefaultUserString = TRUE;
            }
        } else {
            bDefaultUserString = TRUE;
        }

        if (bDefaultUserString) {
            szLocalUserString = (LPSTR)caszSpace;
            dwUserStringSize = lstrlenA (szLocalUserString);
        }

#pragma warning (disable : 4701) // szLocalUserString is init'd above
        dwBufferSize = sprintf (szValueBuffer,
            PdhiszFmtStringValue, cDelim, szLocalUserString);
#pragma warning (default : 4701)    

        dwNewStringLen = dwBufferSize;

        if ((dwNewStringLen + dwStringBufferUsed) >= dwStringBufferSize) {
            LPTSTR szNewString;

            dwStringBufferSize += SMALL_BUFFER_SIZE;
            szNewString = G_REALLOC (szOutputString, dwStringBufferSize);
            if (szNewString == NULL) {
                pdhStatus = PDH_MEMORY_ALLOCATION_FAILURE;
            }
            else {
                szOutputString = szNewString;
            }
        }

        if (pdhStatus != PDH_MEMORY_ALLOCATION_FAILURE) {
            lstrcatA (szOutputString, szValueBuffer);
            dwStringBufferUsed += dwNewStringLen;
        }

        if (!bDefaultUserString) {
            G_FREE (szLocalUserString);
        }

    }

    if (pdhStatus == PDH_MEMORY_ALLOCATION_FAILURE)
        goto endLogText;

    if ((PdhidwRecordTerminatorLength + dwStringBufferUsed) >= dwStringBufferSize) {
        LPTSTR szNewString;
        dwStringBufferSize += PdhidwRecordTerminatorLength;
        szNewString = G_REALLOC (szOutputString, dwStringBufferSize);
        if (szNewString == NULL) {
            pdhStatus = PDH_MEMORY_ALLOCATION_FAILURE;
        }
        else {
            szOutputString = szNewString;
        }
    }

    if (pdhStatus == ERROR_SUCCESS) {
        lstrcatA (szOutputString, PdhiszRecordTerminator);
        dwStringBufferUsed += PdhidwRecordTerminatorLength;

        liFileSize.LowPart = GetFileSize (
            pLog->hLogFileHandle,
            (LPDWORD)&liFileSize.HighPart);
        // add in new record to see if it will fit.
        liFileSize.QuadPart += dwStringBufferUsed;
        // test for maximum allowable filesize
        if (liFileSize.QuadPart <= MAX_TEXT_FILE_SIZE) {

            // write  the record terminator
            if (!WriteFile (pLog->hLogFileHandle,
                (LPCVOID)szOutputString,
                dwStringBufferUsed,
                &dwBytesWritten,
                NULL)) {
                pdhStatus = GetLastError ();
            } else if (pLog->pQuery->hLog == H_REALTIME_DATASOURCE || pLog->pQuery->hLog == H_WBEM_DATASOURCE) {
                FlushFileBuffers(pLog->hLogFileHandle);
            }
        } else {
            pdhStatus = ERROR_LOG_FILE_FULL;
        } 

        if (dwStringBufferUsed> pLog->dwMaxRecordSize) {
            // then update the buffer size
            pLog->dwMaxRecordSize = dwStringBufferUsed;
        }
    }

    endLogText: 
    G_FREE (szOutputString);

    return pdhStatus;
}

PDH_FUNCTION
PdhiEnumMachinesFromTextLog (
    PPDHI_LOG   pLog,
    LPVOID      pBuffer,
    LPDWORD     lpdwBufferSize,
    BOOL        bUnicodeDest
)
{
    PDH_STATUS  pdhStatus;
    PDH_STATUS  pdhBuffStatus = ERROR_SUCCESS;
    LPSTR       szBuffer;
    CHAR        szTemp[4];
    CHAR        cDelim;
    PPDH_COUNTER_PATH_ELEMENTS_A    pInfo;
    DWORD       dwInfoBufferSize;
    DWORD       dwBufferUsed;
    DWORD       dwNewBuffer;
    DWORD       dwIndex;
    DWORD       dwItemLength;
    DWORD       dwBufferLength;
    DWORD       dwItemCount = 0;
    LPVOID      LocalBuffer = NULL;
    LPVOID      TempBuffer;
    DWORD       LocalBufferSize = 0;

    LocalBuffer = G_ALLOC(MEDIUM_BUFFER_SIZE);
    if (LocalBuffer == NULL) {
        return PDH_MEMORY_ALLOCATION_FAILURE;
    }
    LocalBufferSize = MEDIUM_BUFFER_SIZE;
    memset(LocalBuffer, 0, MEDIUM_BUFFER_SIZE);
    if (pBuffer) {
        memset(pBuffer, 0, * lpdwBufferSize);
    }

    pInfo = (PPDH_COUNTER_PATH_ELEMENTS_A) G_ALLOC (MEDIUM_BUFFER_SIZE);
    if (pInfo == NULL) {
        G_FREE(LocalBuffer);
        return PDH_MEMORY_ALLOCATION_FAILURE;
    }

    szBuffer = &szTemp[0];

    // what's in szReturn is not important since we'll be reading the
    // data from the "last Record read" buffer

    pdhStatus = PdhiReadOneTextLogRecord (
        pLog,
        TEXTLOG_HEADER_RECORD,
        szBuffer,
        1); // we're lying to prevent copying the record.

    if (pLog->dwLastRecordRead == TEXTLOG_HEADER_RECORD) {
        // then the seek worked and we can use the buffer
        cDelim = (CHAR)((LOWORD(pLog->dwLogFormat) == PDH_LOG_TYPE_CSV) ?
            COMMA_DELIMITER : TAB_DELIMITER);

        dwBufferLength = lstrlenA((LPSTR)pLog->pLastRecordRead)+1;
        szBuffer = G_ALLOC (dwBufferLength);
        if (szBuffer != NULL) {
            dwBufferUsed = 0;
            dwIndex = 0;
            while ((dwItemLength = PdhiGetStringFromDelimitedListA(
                (LPSTR)pLog->pLastRecordRead,
                ++dwIndex,
                cDelim,
                PDHI_GSFDL_REMOVE_QUOTES | PDHI_GSFDL_REMOVE_NONPRINT,
                szBuffer,
                dwBufferLength)) != 0) {

                if (dwItemLength > 0) {
                    dwInfoBufferSize = MEDIUM_BUFFER_SIZE;
                    // parse the path, pull the machine name out and
                    memset(pInfo, 0, MEDIUM_BUFFER_SIZE);
                    pdhStatus = PdhParseCounterPathA (
                        szBuffer,
                        pInfo,
                        &dwInfoBufferSize,
                        0);

                    if (pdhStatus == ERROR_SUCCESS) {
                        if (szBuffer[1] != '\\') {
                            pInfo->szMachineName[0] = '.';
                            pInfo->szMachineName[1] = '\0';
                        }
                        // add it to the list if it will fit
                        if (pInfo->szMachineName != NULL) {
                            // include sizeof delimiter char
                            dwNewBuffer = (lstrlenA (pInfo->szMachineName) + 1) *
                                (bUnicodeDest ? sizeof(WCHAR) : sizeof(CHAR));

                            while (  LocalBufferSize
                                   < (dwBufferUsed + dwNewBuffer)) {
                                TempBuffer = LocalBuffer;
                                LocalBuffer = G_REALLOC(TempBuffer,
                                        LocalBufferSize + MEDIUM_BUFFER_SIZE);
                                if (LocalBuffer == NULL) {
                                    G_FREE(szBuffer);
                                    G_FREE(TempBuffer);
                                    G_FREE(pInfo);
                                    return PDH_MEMORY_ALLOCATION_FAILURE;
                                }
                                LocalBufferSize += MEDIUM_BUFFER_SIZE;
                            }
                            dwNewBuffer = AddUniqueStringToMultiSz (
                                    (LPVOID) LocalBuffer,
                                    pInfo->szMachineName,
                                    bUnicodeDest);
                        } else {
                            dwNewBuffer = 0;
                        }

                        if (dwNewBuffer > 0) {
                            // string was added so update size used.
                            dwBufferUsed = dwNewBuffer
                                         * (bUnicodeDest ? sizeof(WCHAR)
                                                         : sizeof(CHAR));
                            dwItemCount++;
                        }
                    }
                }
            }
            if (dwItemCount > 0) {
                // then the routine was successful. Errors that occurred
                // while scanning will be ignored as long as at least
                // one entry was successfully read

                pdhStatus = pdhBuffStatus;
            }

            // update the buffer used or required.
            if (pBuffer && dwBufferUsed <= * lpdwBufferSize) {
                RtlCopyMemory(pBuffer, LocalBuffer, dwBufferUsed);
            }
            else {
                if (pBuffer)
                    RtlCopyMemory(pBuffer, LocalBuffer, * lpdwBufferSize);
                dwBufferUsed += (bUnicodeDest) ? sizeof(WCHAR) : sizeof(CHAR);
                pdhStatus = PDH_MORE_DATA;
            }
            *lpdwBufferSize = dwBufferUsed;

            G_FREE (szBuffer);
        } else {
            pdhStatus = PDH_MEMORY_ALLOCATION_FAILURE;
        }
    } else {
        // unable to read header record
       pdhStatus = PDH_UNABLE_READ_LOG_HEADER;
    }
    G_FREE (pInfo);
    G_FREE(LocalBuffer);
    return pdhStatus;
}

PDH_FUNCTION
PdhiEnumObjectsFromTextLog (
    IN  PPDHI_LOG   pLog,
    IN  LPCWSTR     szMachineName,
    IN  LPVOID      pBuffer,
    IN  LPDWORD     pcchBufferSize,
    IN  DWORD       dwDetailLevel,
    IN  BOOL        bUnicodeDest
)
{
    PDH_STATUS  pdhStatus;
    PDH_STATUS  pdhBuffStatus = ERROR_SUCCESS;
    LPSTR       szBuffer;
    CHAR        szTemp[4];
    CHAR        cDelim;
    PPDH_COUNTER_PATH_ELEMENTS_A    pInfo;
    DWORD       dwInfoBufferSize;
    DWORD       dwBufferUsed;
    DWORD       dwNewBuffer;
    DWORD       dwIndex;
    DWORD       dwItemLength;
    DWORD       dwBufferLength;
    WCHAR       wszMachineName[MAX_PATH];
    DWORD       dwMachineNameLength;
    DWORD       dwItemCount = 0;
    LPVOID      LocalBuffer = NULL;
    LPVOID      TempBuffer;
    DWORD       LocalBufferSize = 0;

    UNREFERENCED_PARAMETER (dwDetailLevel);

    pInfo = (PPDH_COUNTER_PATH_ELEMENTS_A) G_ALLOC (MEDIUM_BUFFER_SIZE);
    if (pInfo == NULL) {
        return PDH_MEMORY_ALLOCATION_FAILURE;
    }
    szBuffer = &szTemp[0];

    LocalBuffer = G_ALLOC(MEDIUM_BUFFER_SIZE);
    if (LocalBuffer == NULL) {
        G_FREE(pInfo);
        return PDH_MEMORY_ALLOCATION_FAILURE;
    }
    memset(LocalBuffer, 0, MEDIUM_BUFFER_SIZE);
    LocalBufferSize = MEDIUM_BUFFER_SIZE;
    if (pBuffer) {
        memset(pBuffer, 0, * pcchBufferSize);
    }

    // what's in szReturn is not important since we'll be reading the
    // data from the "last Record read" buffer

    pdhStatus = PdhiReadOneTextLogRecord (
        pLog,
        TEXTLOG_HEADER_RECORD,
        szBuffer,
        1); // we're lying to prevent copying the record.

    if (pLog->dwLastRecordRead == TEXTLOG_HEADER_RECORD) {
        // then the seek worked and we can use the buffer
        cDelim = (CHAR)((LOWORD(pLog->dwLogFormat) == PDH_LOG_TYPE_CSV) ?
            COMMA_DELIMITER : TAB_DELIMITER);

        dwBufferLength = lstrlenA((LPSTR)pLog->pLastRecordRead)+1;
        szBuffer = G_ALLOC (dwBufferLength);
        if (szBuffer != NULL) {
            dwBufferUsed = 0;
            dwIndex = 0;
            while ((dwItemLength = PdhiGetStringFromDelimitedListA(
                (LPSTR)pLog->pLastRecordRead,
                ++dwIndex,
                cDelim,
                PDHI_GSFDL_REMOVE_QUOTES | PDHI_GSFDL_REMOVE_NONPRINT,
                szBuffer,
                dwBufferLength)) != 0) {

                if (dwItemLength > 0) {
                    dwInfoBufferSize = MEDIUM_BUFFER_SIZE;
                    // parse the path, pull the machine name out and
                    memset(pInfo, 0, MEDIUM_BUFFER_SIZE);
                    pdhStatus = PdhParseCounterPathA (
                        szBuffer,
                        pInfo,
                        &dwInfoBufferSize,
                        0);

                    if (pdhStatus == ERROR_SUCCESS) {
                        if (szBuffer[1] != '\\') {
                            pInfo->szMachineName[0] = '.';
                            pInfo->szMachineName[1] = '\0';
                        }
                        // add it to the list if it will fit and it's from
                        // the desired machine
                        memset(wszMachineName, 0, sizeof(WCHAR) * MAX_PATH);
                        dwMachineNameLength = MAX_PATH;
                        MultiByteToWideChar(_getmbcp(),
                                            0,
                                            pInfo->szMachineName,
                                            lstrlenA(pInfo->szMachineName),
                                            (LPWSTR) wszMachineName,
                                            dwMachineNameLength);
                        if (lstrcmpiW(szMachineName, wszMachineName) == 0) {
                            dwNewBuffer = (lstrlenA (pInfo->szObjectName) + 1) *
                                (bUnicodeDest ? sizeof(WCHAR) : sizeof(CHAR));

                            while (  LocalBufferSize
                                   < (dwBufferUsed + dwNewBuffer)) {
                                TempBuffer = LocalBuffer;
                                LocalBuffer = G_REALLOC(TempBuffer,
                                        LocalBufferSize + MEDIUM_BUFFER_SIZE);
                                if (LocalBuffer == NULL) {
                                    G_FREE(szBuffer);
                                    G_FREE(TempBuffer);
                                    G_FREE(pInfo);
                                    return PDH_MEMORY_ALLOCATION_FAILURE;
                                }
                                LocalBufferSize += MEDIUM_BUFFER_SIZE;
                            }

                            dwNewBuffer = AddUniqueStringToMultiSz (
                                    (LPVOID) LocalBuffer,
                                    pInfo->szObjectName,
                                    bUnicodeDest);

                            if (dwNewBuffer > 0) {
                                // string was added so update size used.
                                dwBufferUsed = dwNewBuffer
                                             * (bUnicodeDest ? sizeof(WCHAR)
                                                             : sizeof(CHAR));
                                dwItemCount++;
                            }
                        }
                    }
                }
            }

            if (dwItemCount > 0) {
                // then the routine was successful. Errors that occurred
                // while scanning will be ignored as long as at least
                // one entry was successfully read
                pdhStatus = pdhBuffStatus;
            }

            // copy buffer size used or required

            if (pBuffer && dwBufferUsed <= * pcchBufferSize) {
                RtlCopyMemory(pBuffer, LocalBuffer, dwBufferUsed);
            }
            else {
                if (pBuffer)
                    RtlCopyMemory(pBuffer, LocalBuffer, * pcchBufferSize);
                dwBufferUsed += (bUnicodeDest) ? sizeof(WCHAR) : sizeof(CHAR);
                pdhStatus = PDH_MORE_DATA;
            }

            * pcchBufferSize = dwBufferUsed;

            G_FREE (szBuffer);
        } else {
            pdhStatus = PDH_MEMORY_ALLOCATION_FAILURE;
        }
    }
    G_FREE (pInfo);
    G_FREE(LocalBuffer);
    return pdhStatus;
}

ULONG HashCounter(
    LPWSTR szCounterName
)
{
    ULONG       h = 0;
    ULONG       a = 31415;  //a, b, k are primes
    const ULONG k = 16381;
    const ULONG b = 27183;
    LPWSTR      szThisChar;

    if (szCounterName) {
        for (szThisChar = szCounterName; * szThisChar; szThisChar ++) {
            h = (a * h + ((ULONG) (* szThisChar))) % k;
            a = a * b % (k - 1);
        }
    }
    return (h % HASH_TABLE_SIZE);
}

void
PdhiInitCounterHashTable(
    IN PDHI_COUNTER_TABLE pTable
)
{
    ZeroMemory(pTable, sizeof(PDHI_COUNTER_TABLE));
}

void
PdhiResetInstanceCount(
    IN PDHI_COUNTER_TABLE pTable
)
{
    PLIST_ENTRY     pHeadInst;
    PLIST_ENTRY     pNextInst;
    PPDHI_INSTANCE  pInstance;
    PPDHI_INST_LIST pInstList;
    DWORD           i;

    for (i = 0; i < HASH_TABLE_SIZE; i ++) {
        pInstList = pTable[i];
        while (pInstList != NULL) {
            if (! IsListEmpty(& pInstList->InstList)) {
                pHeadInst = & pInstList->InstList;
                pNextInst = pHeadInst->Flink;
                while (pNextInst != pHeadInst) {
                    pInstance = CONTAINING_RECORD(
                                    pNextInst, PDHI_INSTANCE, Entry);
                    pInstance->dwCount = 0;
                    pNextInst = pNextInst->Flink;
                }
            }
            pInstList = pInstList->pNext;
        }
    }
}

PDH_FUNCTION
PdhiFindCounterInstList(
    IN  PDHI_COUNTER_TABLE pHeadList,
    IN  LPWSTR             szCounter,
    OUT PPDHI_INST_LIST  * pInstList
)
{
    PDH_STATUS      Status     = ERROR_SUCCESS;
    ULONG           lIndex     = HashCounter(szCounter);
    PPDHI_INST_LIST pLocalList = pHeadList[lIndex];
    PPDHI_INST_LIST pRtnList   = NULL;

    * pInstList = NULL;
    while (pLocalList != NULL) {
        if (lstrcmpiW(pLocalList->szCounter, szCounter) == 0) {
            pRtnList = pLocalList;
            break;
        }
        pLocalList = pLocalList->pNext;
    }

    if (pRtnList == NULL) {
        pRtnList = G_ALLOC(sizeof(PDHI_INST_LIST) +
                   sizeof(WCHAR) * (lstrlenW(szCounter) + 1));
        if (pRtnList == NULL) {
            Status = PDH_MEMORY_ALLOCATION_FAILURE;
            goto Cleanup;
        }
        pRtnList->szCounter = (LPWSTR)
                        (((LPBYTE) pRtnList) + sizeof(PDHI_INST_LIST));
        lstrcpyW(pRtnList->szCounter, szCounter);
        InitializeListHead(& pRtnList->InstList);

        pRtnList->pNext   = pHeadList[lIndex];
        pHeadList[lIndex] = pRtnList;
    }

Cleanup:
    if (Status == ERROR_SUCCESS) {
        * pInstList = pRtnList;
    }
    return Status;
}

PDH_FUNCTION
PdhiFindInstance(
    IN  PLIST_ENTRY      pHeadInst,
    IN  LPWSTR           szInstance,
    IN  BOOLEAN          bUpdateCount,
    OUT PPDHI_INSTANCE * pInstance
)
{
    PDH_STATUS      Status = ERROR_SUCCESS;
    PLIST_ENTRY     pNextInst;
    PPDHI_INSTANCE  pLocalInst;
    PPDHI_INSTANCE  pRtnInst = NULL;

    * pInstance = NULL;

    if (! IsListEmpty(pHeadInst)) {
        pNextInst = pHeadInst->Flink;
        while (pNextInst != pHeadInst) {
            pLocalInst = CONTAINING_RECORD(pNextInst, PDHI_INSTANCE, Entry);
            if (lstrcmpiW(pLocalInst->szInstance, szInstance) == 0) {
                pRtnInst = pLocalInst;
                break;
            }
            pNextInst = pNextInst->Flink;
        }
    }

    if (pRtnInst == NULL) {
        pRtnInst = G_ALLOC(sizeof(PDHI_INSTANCE) +
                           sizeof(WCHAR) * (lstrlenW(szInstance) + 1));
        if (pRtnInst == NULL) {
            Status = PDH_MEMORY_ALLOCATION_FAILURE;
            goto Cleanup;
        }
        pRtnInst->szInstance = (LPWSTR)
                        (((LPBYTE) pRtnInst) + sizeof(PDHI_INSTANCE));
        lstrcpyW(pRtnInst->szInstance, szInstance);
        pRtnInst->dwCount = pRtnInst->dwTotal = 0;
        InsertTailList(pHeadInst, & pRtnInst->Entry);
    }

    if (bUpdateCount) {
        pRtnInst->dwCount ++;
        if (pRtnInst->dwCount > pRtnInst->dwTotal) {
            pRtnInst->dwTotal = pRtnInst->dwCount;
        }
    }
    else if (pRtnInst->dwCount == 0) {
        pRtnInst->dwCount = pRtnInst->dwTotal = 1;
    }

Cleanup:
    if (Status == ERROR_SUCCESS) {
        * pInstance = pRtnInst;
    }
    return Status;
}

DWORD
AddStringToMultiSz(
    IN  LPVOID  mszDest,
    IN  LPWSTR  szSource,
    IN  BOOL    bUnicodeDest
)
{
    LPVOID szDestElem;
    DWORD  dwReturnLength;
    LPSTR  aszSource = NULL;
    DWORD  dwLength;

    if ((mszDest == NULL) || (szSource == NULL)
                          || (* szSource == L'\0')) {
        return 0;
    }

    if (!bUnicodeDest) {
        dwLength = lstrlenW(szSource) + 1;
        aszSource = G_ALLOC(dwLength * 3 * sizeof(CHAR));
        if (aszSource != NULL) {
            WideCharToMultiByte(_getmbcp(),
                                0,
                                szSource,
                                lstrlenW(szSource),
                                aszSource,
                                dwLength,
                                NULL,
                                NULL);
            dwReturnLength = 1;
        }
        else {
            dwReturnLength = 0;
        }
    }
    else {
        dwReturnLength = 1;
    }

    if (dwReturnLength > 0) {
        for (szDestElem = mszDest;
             (bUnicodeDest ? (* (LPWSTR) szDestElem != L'\0')
                           : (* (LPSTR)  szDestElem != '\0'));
                ) {
            if (bUnicodeDest) {
                szDestElem = (LPVOID) ((LPWSTR) szDestElem
                           + (lstrlenW((LPCWSTR) szDestElem) + 1));
            }
            else {
                szDestElem = (LPVOID) ((LPSTR) szDestElem
                           + (lstrlenA((LPCSTR) szDestElem) + 1));
            }
        }

        if (bUnicodeDest) {
            lstrcpyW ((LPWSTR)szDestElem, szSource);
            szDestElem = (LPVOID)((LPWSTR)szDestElem + lstrlenW(szSource) + 1);
            * ((LPWSTR)szDestElem) = L'\0';
            dwReturnLength = (DWORD)((LPWSTR)szDestElem - (LPWSTR)mszDest);
        }
        else {
            lstrcpyA ((LPSTR)szDestElem, aszSource);
            szDestElem = (LPVOID)((LPSTR)szDestElem + lstrlenA(szDestElem) + 1);
            * ((LPSTR)szDestElem) = '\0';
            dwReturnLength = (DWORD)((LPSTR)szDestElem - (LPSTR)mszDest);
        }
    }

    if (aszSource != NULL) {
        G_FREE (aszSource);
    }

    return (DWORD) dwReturnLength;
}

PDH_FUNCTION
PdhiEnumObjectItemsFromTextLog (
    IN  PPDHI_LOG          pLog,
    IN  LPCWSTR            szMachineName,
    IN  LPCWSTR            szObjectName,
    IN  PDHI_COUNTER_TABLE CounterTable,
    IN  DWORD              dwDetailLevel,
    IN  DWORD              dwFlags
)
{
    PDH_STATUS  pdhStatus;
    PDH_STATUS  pdhBuffStatus = ERROR_SUCCESS;
    LPSTR       szBuffer;
    CHAR        szTemp[4];
    CHAR        cDelim;
    PPDH_COUNTER_PATH_ELEMENTS_A    pInfo = NULL;
    DWORD       dwInfoBufferSize;
    DWORD       dwIndex;
    DWORD       dwItemLength;
    DWORD       dwBufferLength;
    WCHAR       wszMachineName[MAX_PATH];
    WCHAR       wszObjectName[MAX_PATH];
    WCHAR       wszCounterName[MAX_PATH];
    WCHAR       wszInstanceName[MAX_PATH];
    CHAR        szFullInstanceName[MAX_PATH];
    DWORD       dwMachineNameLength;
    DWORD       dwObjectNameLength;
    DWORD       dwCounterNameLength;
    DWORD       dwInstanceNameLength;
    DWORD       dwItemCount = 0;
    CHAR        szIndexNumber[20];

    PPDHI_INSTANCE  pInstance;
    PPDHI_INST_LIST pInstList;

    UNREFERENCED_PARAMETER (dwDetailLevel);
    UNREFERENCED_PARAMETER (dwFlags);

    pInfo = (PPDH_COUNTER_PATH_ELEMENTS_A) G_ALLOC (MEDIUM_BUFFER_SIZE);
    if (pInfo == NULL) {
        pdhStatus = PDH_MEMORY_ALLOCATION_FAILURE;
        goto Cleanup;
    }

    szBuffer = & szTemp[0];

    // what's in szReturn is not important since we'll be reading the
    // data from the "last Record read" buffer

    pdhStatus = PdhiReadOneTextLogRecord (
        pLog,
        TEXTLOG_HEADER_RECORD,
        szBuffer,
        1); // we're lying to prevent copying the record.

    if (pLog->dwLastRecordRead == TEXTLOG_HEADER_RECORD) {
        // then the seek worked and we can use the buffer
        cDelim = (CHAR)((LOWORD(pLog->dwLogFormat) == PDH_LOG_TYPE_CSV) ?
            COMMA_DELIMITER : TAB_DELIMITER);

        dwBufferLength = pLog->dwMaxRecordSize+1;
        szBuffer = G_ALLOC (dwBufferLength);
        if (szBuffer != NULL) {
            dwIndex = 0;
            while ((dwItemLength = PdhiGetStringFromDelimitedListA(
                        (LPSTR)pLog->pLastRecordRead,
                        ++dwIndex,
                        cDelim,
                        PDHI_GSFDL_REMOVE_QUOTES | PDHI_GSFDL_REMOVE_NONPRINT,
                        szBuffer,
                        dwBufferLength)) != 0) {
                if (dwItemLength > 0) {
                    dwInfoBufferSize = MEDIUM_BUFFER_SIZE;
                    // parse the path, pull the machine name out and
                    memset(pInfo, 0, MEDIUM_BUFFER_SIZE);
                    pdhStatus = PdhParseCounterPathA (
                                szBuffer,
                                pInfo,
                                &dwInfoBufferSize,
                                0);

                    if (pdhStatus == ERROR_SUCCESS) {
                        if (szBuffer[1] != '\\') {
                            pInfo->szMachineName[0] = '.';
                            pInfo->szMachineName[1] = '\0';
                        }
                        // add it to the list if it will fit and it's from
                        // the desired machine
                        memset(wszMachineName, 0, sizeof(WCHAR) * MAX_PATH);
                        dwMachineNameLength = MAX_PATH;
                        MultiByteToWideChar(_getmbcp(),
                                            0,
                                            pInfo->szMachineName,
                                            lstrlenA(pInfo->szMachineName),
                                            (LPWSTR) wszMachineName,
                                            dwMachineNameLength);
                        if (lstrcmpiW(wszMachineName, szMachineName) == 0) {
                            memset(wszObjectName, 0, sizeof(WCHAR) * MAX_PATH);
                            dwObjectNameLength = MAX_PATH;
                            MultiByteToWideChar(_getmbcp(),
                                                0,
                                                pInfo->szObjectName,
                                                lstrlenA(pInfo->szObjectName),
                                                (LPWSTR) wszObjectName,
                                                dwObjectNameLength);
                            if (lstrcmpiW(wszObjectName, szObjectName) == 0) {
                                memset(wszCounterName, 0, sizeof(WCHAR) * MAX_PATH);
                                dwCounterNameLength = MAX_PATH;
                                MultiByteToWideChar(_getmbcp(),
                                                    0,
                                                    pInfo->szCounterName,
                                                    lstrlenA(pInfo->szCounterName),
                                                    (LPWSTR) wszCounterName,
                                                    dwCounterNameLength);
                                pdhBuffStatus = PdhiFindCounterInstList(
                                            CounterTable,
                                            wszCounterName,
                                            & pInstList);
                                if (pdhBuffStatus != ERROR_SUCCESS) {
                                    continue;
                                }

                                if (   pInfo->szInstanceName != NULL
                                    && pInfo->szInstanceName[0] != '\0') {
                                    if (pInfo->szParentInstance == NULL) {
                                        // then the name is just the instance
                                        szFullInstanceName[0] = 0;
                                    } else {
                                        // we need to build the instance string from
                                        // the parent and the child
                                        lstrcpyA (szFullInstanceName,
                                            pInfo->szParentInstance);
                                        lstrcatA (szFullInstanceName, caszSlash);
                                    }

                                    lstrcatA (szFullInstanceName,
                                        pInfo->szInstanceName);

                                    if ((LONG)pInfo->dwInstanceIndex > 0) {
                                        // append instance index to instance name if it's > 0
                                        szIndexNumber[0] = POUNDSIGN_A;
                                        _ultoa (pInfo->dwInstanceIndex, &szIndexNumber[1], 10);
                                        lstrcatA (szFullInstanceName, szIndexNumber);
                                    }

                                    memset(wszInstanceName, 0, sizeof(WCHAR) * MAX_PATH);
                                    dwInstanceNameLength = MAX_PATH;
                                    MultiByteToWideChar(_getmbcp(),
                                                        0,
                                                        szFullInstanceName,
                                                        lstrlenA(szFullInstanceName),
                                                        (LPWSTR) wszInstanceName,
                                                        dwInstanceNameLength);
                                    pdhBuffStatus = PdhiFindInstance(
                                            & pInstList->InstList,
                                            wszInstanceName,
                                            TRUE,
                                            & pInstance);
                                }

                                if (pdhBuffStatus == ERROR_SUCCESS) {
                                    dwItemCount++;
                                }
                            } // else not this object
                        } // else not this machine
                    }
                }
            }

            if (dwItemCount > 0) {
                // then the routine was successful. Errors that occurred
                // while scanning will be ignored as long as at least
                // one entry was successfully read
                pdhStatus = pdhBuffStatus;
            }

            G_FREE (szBuffer);
        } else {
            pdhStatus = PDH_MEMORY_ALLOCATION_FAILURE;
        }
    }

Cleanup:
    if (pInfo) {
        G_FREE (pInfo);
    }

    return pdhStatus;
}

PDH_FUNCTION
PdhiGetMatchingTextLogRecord (
    IN  PPDHI_LOG   pLog,
    IN  LONGLONG    *pStartTime,
    IN  LPDWORD     pdwIndex
)
{
    PDH_STATUS  pdhStatus = ERROR_SUCCESS;
    CHAR        szSmallBuffer[MAX_PATH];
    DWORD       dwRecordId = TEXTLOG_FIRST_DATA_RECORD;
    CHAR        cDelim;
    FILETIME    RecordTimeStamp;
    LONGLONG    RecordTimeValue;
    LONGLONG    LastTimeValue = 0;

    // read the first data record in the log file
    // note that the record read is not copied to the local buffer
    // rather the internal buffer is used in "read-only" mode
    cDelim = (CHAR)((LOWORD(pLog->dwLogFormat) == PDH_LOG_TYPE_CSV) ?
        COMMA_DELIMITER : TAB_DELIMITER);

    if ((*pStartTime & 0xFFFFFFFF00000000) == 0xFFFFFFFF00000000) {
        dwRecordId = (DWORD)(*pStartTime & 0x00000000FFFFFFFF);
        LastTimeValue = *pStartTime;
        if (dwRecordId == 0) return PDH_ENTRY_NOT_IN_LOG_FILE;
    } else {
        dwRecordId = TEXTLOG_FIRST_DATA_RECORD;
    }

    pdhStatus = PdhiReadOneTextLogRecord (
        pLog,
        dwRecordId,
        szSmallBuffer,
        1); // to prevent copying the record

    while (   pdhStatus == ERROR_SUCCESS
           || pdhStatus == PDH_MORE_DATA
           || pdhStatus == PDH_INSUFFICIENT_BUFFER) {
        if (PdhiGetStringFromDelimitedListA(
                (LPSTR)pLog->pLastRecordRead,
                0,  // timestamp is first entry
                cDelim,
                PDHI_GSFDL_REMOVE_QUOTES | PDHI_GSFDL_REMOVE_NONPRINT,
                szSmallBuffer,
                MAX_PATH) > 0) {
            // convert ASCII timestamp to LONGLONG value for comparison
            PdhiDateStringToFileTimeA (szSmallBuffer, &RecordTimeStamp);
            RecordTimeValue = MAKELONGLONG(RecordTimeStamp.dwLowDateTime,
                                           RecordTimeStamp.dwHighDateTime);
            if ((*pStartTime == RecordTimeValue) || (*pStartTime == 0)) {
                // found the match so bail here
                LastTimeValue = RecordTimeValue;
                break;
            } else if (RecordTimeValue > *pStartTime) {
                // then this is the first record > than the desired time
                // so the desired value is the one before this one
                // unless it's the first data record of the log
                if (dwRecordId > TEXTLOG_FIRST_DATA_RECORD) {
                    dwRecordId--;
                } else {
                    // this hasnt' been initialized yet.
                    LastTimeValue = RecordTimeValue;
                }
                break;
            } else {
                // save value for next trip through loop
                LastTimeValue = RecordTimeValue;
                // advance record counter and try the next entry
                dwRecordId++;
            }
        } else {
            // no timestamp field so ignore this record.
        }
        // read the next record in the file
        pdhStatus = PdhiReadOneTextLogRecord (
            pLog,
            dwRecordId,
            szSmallBuffer,
            1); // to prevent copying the record
    }
    if (   pdhStatus == ERROR_SUCCESS
        || pdhStatus == PDH_MORE_DATA
        || pdhStatus == PDH_INSUFFICIENT_BUFFER) {
        // then dwRecordId is the desired entry
        *pdwIndex = dwRecordId;
        *pStartTime = LastTimeValue;
        pdhStatus = ERROR_SUCCESS;
    } else {
        pdhStatus = PDH_ENTRY_NOT_IN_LOG_FILE;
    }
    return pdhStatus;
}

PDH_FUNCTION
PdhiGetCounterValueFromTextLog (
    IN  PPDHI_LOG           pLog,
    IN  DWORD               dwIndex,
    IN  PERFLIB_COUNTER     *pPath,
    IN  PPDH_RAW_COUNTER    pValue
)
{
    PDH_STATUS  pdhStatus = ERROR_SUCCESS;
    CHAR        szSmallBuffer[MAX_PATH];
    CHAR        cDelim;
    FILETIME    RecordTimeStamp;
    DOUBLE      dValue;


    memset (&RecordTimeStamp, 0, sizeof(RecordTimeStamp));
    // read the first data record in the log file
    // note that the record read is not copied to the local buffer
    // rather the internal buffer is used in "read-only" mode
    cDelim = (CHAR)((LOWORD(pLog->dwLogFormat) == PDH_LOG_TYPE_CSV) ?
        COMMA_DELIMITER : TAB_DELIMITER);

    pdhStatus = PdhiReadOneTextLogRecord (
            pLog,
            dwIndex,
            szSmallBuffer,
            1); // to prevent copying the record

    if (   pdhStatus == ERROR_SUCCESS
        || pdhStatus == PDH_MORE_DATA
        || pdhStatus == PDH_INSUFFICIENT_BUFFER) {
        // the specified entry in the log is the dwCounterId value
        // in the perflib buffer
        if (PdhiGetStringFromDelimitedListA (
                (LPSTR)pLog->pLastRecordRead,
                0,  // timestamp is first entry
                cDelim,
                PDHI_GSFDL_REMOVE_QUOTES | PDHI_GSFDL_REMOVE_NONPRINT,
                szSmallBuffer,
                MAX_PATH) > 0) {
            // convert time stamp string to FileTime value
            PdhiDateStringToFileTimeA (szSmallBuffer, &RecordTimeStamp);
        } else {
            RecordTimeStamp.dwLowDateTime  = 0;
            RecordTimeStamp.dwHighDateTime = 0;
        }

        if (PdhiGetStringFromDelimitedListA(
                (LPSTR) pLog->pLastRecordRead,
                pPath->dwCounterId,
                cDelim,
                PDHI_GSFDL_REMOVE_QUOTES | PDHI_GSFDL_REMOVE_NONPRINT,
                szSmallBuffer,
                MAX_PATH) > 0) {
            // convert ASCII value to Double Float
            if (szSmallBuffer[0] >= '0' && szSmallBuffer[0] <= '9') {
                dValue = atof(szSmallBuffer);
                pValue->CStatus = PDH_CSTATUS_VALID_DATA;
            }
            else {
                dValue = 0.0;
                pValue->CStatus = PDH_CSTATUS_NO_INSTANCE;
            }
            pdhStatus = ERROR_SUCCESS;
            pValue->TimeStamp = RecordTimeStamp;
            (double)pValue->FirstValue = dValue;
            pValue->SecondValue = 0;
            pValue->MultiCount = 1;
        } else {
            pdhStatus = ERROR_SUCCESS;
            // update counter buffer
            pValue->CStatus = PDH_CSTATUS_INVALID_DATA;
            pValue->TimeStamp = RecordTimeStamp;
            (double)pValue->FirstValue = (double)0.0f;
            pValue->SecondValue = 0;
            pValue->MultiCount = 1;
        }
    } else {
        if (pdhStatus == PDH_END_OF_LOG_FILE) {
            pdhStatus = PDH_NO_MORE_DATA;
        }
        // unable to find entry in the log file
        pValue->CStatus = PDH_CSTATUS_INVALID_DATA;
        pValue->TimeStamp = RecordTimeStamp;
        (double)pValue->FirstValue = (double)0.0f;
        pValue->SecondValue = 0;
        pValue->MultiCount = 1;
    }
    return pdhStatus;
}

PDH_FUNCTION
PdhiGetTimeRangeFromTextLog (
    IN  PPDHI_LOG       pLog,
    IN  LPDWORD         pdwNumEntries,
    IN  PPDH_TIME_INFO  pInfo,
    IN  LPDWORD         pdwBufferSize
)
/*++
    the first entry in the buffer returned is the total time range covered
    in the file, if there are multiple time blocks in the log file, then
    subsequent entries will identify each segment in the file.
--*/
{
    PDH_STATUS  pdhStatus;
    LONGLONG    llStartTime = MAX_TIME_VALUE;
    LONGLONG    llEndTime = MIN_TIME_VALUE;
    LONGLONG    llThisTime = 0;
    CHAR        cDelim;

    DWORD       dwThisRecord = TEXTLOG_FIRST_DATA_RECORD;
    DWORD       dwValidEntries = 0;

    CHAR        szSmallBuffer[MAX_PATH];
    
    // read the first data record in the log file
    // note that the record read is not copied to the local buffer
    // rather the internal buffer is used in "read-only" mode
    cDelim = (CHAR)((LOWORD(pLog->dwLogFormat) == PDH_LOG_TYPE_CSV) ?
        COMMA_DELIMITER : TAB_DELIMITER);

    pdhStatus = PdhiReadOneTextLogRecord (
            pLog,
            dwThisRecord,
            szSmallBuffer,
            1); // to prevent copying the record

    while (pdhStatus == ERROR_SUCCESS
           || pdhStatus == PDH_MORE_DATA
           || pdhStatus == PDH_INSUFFICIENT_BUFFER) {
        if (PdhiGetStringFromDelimitedListA (
                (LPSTR)pLog->pLastRecordRead,
                0,  // timestamp is first entry
                cDelim,
                PDHI_GSFDL_REMOVE_QUOTES | PDHI_GSFDL_REMOVE_NONPRINT,
                szSmallBuffer,
                MAX_PATH) > 0) {
            // convert ASCII timestamp to LONGLONG value for comparison
            PdhiDateStringToFileTimeA (szSmallBuffer, (LPFILETIME)&llThisTime);
            if (llThisTime < llStartTime) {
                llStartTime = llThisTime;
            }
            if (llThisTime > llEndTime) {
                llEndTime = llThisTime;
            }
            dwValidEntries++;
        } else {
            // no timestamp field so ignore this record.
        }
        // read the next record in the file
        pdhStatus = PdhiReadOneTextLogRecord (
            pLog,
            ++dwThisRecord,
            szSmallBuffer,
            1); // to prevent copying the record
    }

    if (pdhStatus == PDH_END_OF_LOG_FILE) {
        // then the whole file was read so update the args.
        if (*pdwBufferSize >=  sizeof(PDH_TIME_INFO)) {
            *(LONGLONG *)(&pInfo->StartTime) = llStartTime;
            *(LONGLONG *)(&pInfo->EndTime) = llEndTime;
            pInfo->SampleCount = dwValidEntries;
            *pdwBufferSize = sizeof(PDH_TIME_INFO);
            *pdwNumEntries = 1;
        } else {
            pdhStatus = PDH_MORE_DATA;
        }
        pdhStatus = ERROR_SUCCESS;
    } else {
        pdhStatus = PDH_ENTRY_NOT_IN_LOG_FILE;
    }

    return pdhStatus;
}

PDH_FUNCTION
PdhiReadRawTextLogRecord (
    IN  PPDHI_LOG    pLog,
    IN  FILETIME     *ftRecord,
    IN  PPDH_RAW_LOG_RECORD     pBuffer,
    IN  LPDWORD                 pdwBufferLength
)
{
    PDH_STATUS  pdhStatus = ERROR_SUCCESS;
    LONGLONG    llStartTime;
    DWORD       dwIndex = 0;
    DWORD       dwSizeRequired;
    DWORD       dwLocalRecordLength; // including terminating NULL

    llStartTime = *(LONGLONG *)ftRecord;

    pdhStatus = PdhiGetMatchingTextLogRecord (
        pLog,
        &llStartTime,
        &dwIndex);

    // copy results from internal log buffer if it'll fit.

    if (pdhStatus == ERROR_SUCCESS) {
        dwLocalRecordLength =
            (lstrlenA ((LPSTR)pLog->pLastRecordRead)) * sizeof (CHAR);
        dwSizeRequired =
              sizeof (PDH_RAW_LOG_RECORD) - sizeof (UCHAR)
            + dwLocalRecordLength;

        if (*pdwBufferLength >= dwSizeRequired) {
            pBuffer->dwRecordType = (DWORD)(LOWORD(pLog->dwLogFormat));
            pBuffer->dwItems = dwLocalRecordLength;
            // copy it
            memcpy (&pBuffer->RawBytes[0], pLog->pLastRecordRead,
                dwLocalRecordLength);
            pBuffer->dwStructureSize = dwSizeRequired;

        } else {
            pdhStatus = PDH_MORE_DATA;
        }

        *pdwBufferLength = dwSizeRequired;
    }

    return pdhStatus;
}


PDH_FUNCTION
PdhiListHeaderFromTextLog (
    IN  PPDHI_LOG   pLogFile,
    IN  LPVOID      pBufferArg,
    IN  LPDWORD     pcchBufferSize,
    IN  BOOL        bUnicode
)
{
    LPVOID      pTempBuffer = NULL;
    LPVOID      pOldBuffer;
    DWORD       dwTempBufferSize;
    CHAR        szLocalPathBuffer[MAX_PATH];

    DWORD       dwIndex;
    DWORD       dwBufferRemaining;
    LPVOID      pNextChar;
    DWORD       dwReturnSize;

    CHAR        cDelimiter;

    PDH_STATUS  pdhStatus = ERROR_SUCCESS;
    // read the header record and enum the machine name from the entries

    if (pLogFile->dwMaxRecordSize == 0) {
        // no size is defined so start with 64K
        pLogFile->dwMaxRecordSize = 0x010000;
    }

    dwTempBufferSize = pLogFile->dwMaxRecordSize;
    pTempBuffer = G_ALLOC (dwTempBufferSize);
    assert (pTempBuffer != NULL);
    if (pTempBuffer == NULL) {
        assert (GetLastError() == ERROR_SUCCESS);
        return PDH_MEMORY_ALLOCATION_FAILURE;
    }

    cDelimiter = (CHAR)((LOWORD(pLogFile->dwLogFormat) == PDH_LOG_TYPE_CSV) ?
        COMMA_DELIMITER : TAB_DELIMITER);

    // read in the catalog record

    while ((pdhStatus = PdhiReadOneTextLogRecord (pLogFile, TEXTLOG_HEADER_RECORD,
            pTempBuffer, dwTempBufferSize)) != ERROR_SUCCESS) {
        if ((pdhStatus == PDH_INSUFFICIENT_BUFFER) || (pdhStatus == PDH_MORE_DATA)){ 
            // read the 1st WORD to see if this is a valid record
            pLogFile->dwMaxRecordSize *= 2;
            // realloc a new buffer
            pOldBuffer  = pTempBuffer;
            pTempBuffer = G_REALLOC (pOldBuffer, dwTempBufferSize);
            assert (pTempBuffer != NULL);

            if (pTempBuffer == NULL) {
                // return memory error
                G_FREE(pOldBuffer);
                assert (GetLastError() == ERROR_SUCCESS);
                pdhStatus = PDH_MEMORY_ALLOCATION_FAILURE;
                break;
            } 
        } else {
            // some other error was returned so
            // return error from read function
            break;
        }
    }

    if (pdhStatus == ERROR_SUCCESS) {
        // parse header record into MSZ
        dwIndex = 1;
        dwBufferRemaining = *pcchBufferSize;
        pNextChar = pBufferArg;
        // initialize first character in buffer
        if (bUnicode) {
            *(PWCHAR)pNextChar = 0;
        } else {
            *(LPBYTE)pNextChar = 0;
        }

        do {
            dwReturnSize = PdhiGetStringFromDelimitedListA (
                (LPSTR)pTempBuffer,
                dwIndex,
                cDelimiter,
                PDHI_GSFDL_REMOVE_QUOTES | PDHI_GSFDL_REMOVE_NONPRINT,
                szLocalPathBuffer,
                MAX_PATH);
            if (dwReturnSize > 0) {
                // copy to buffer
                if (dwReturnSize < dwBufferRemaining) {
                    if (bUnicode) {
                        MultiByteToWideChar(_getmbcp(),
                                            0,
                                            szLocalPathBuffer,
                                            lstrlenA(szLocalPathBuffer),
                                            (LPWSTR) pNextChar,
                                            dwReturnSize);
                        pNextChar = (LPVOID)((PWCHAR)pNextChar + dwReturnSize);
                        *(PWCHAR)pNextChar = 0;
                        pNextChar = (LPVOID)((PWCHAR)pNextChar + 1);
                    } else {
                        lstrcpyA ((LPSTR)pNextChar, szLocalPathBuffer);
                        pNextChar = (LPVOID)((LPBYTE)pNextChar + dwReturnSize);
                        *(LPBYTE)pNextChar = 0;
                        pNextChar = (LPVOID)((PCHAR)pNextChar + 1);
                    }
                    dwBufferRemaining -= dwReturnSize;
                } else {
                    pdhStatus = PDH_MORE_DATA;
                }
                dwIndex++;
            }
        } while (dwReturnSize > 0); // end loop
        // add MSZ terminator
        if (1 < dwBufferRemaining) {
            if (bUnicode) {
                *(PWCHAR)pNextChar = 0;
                pNextChar = (LPVOID)((PWCHAR)pNextChar + 1);
            } else {
                *(LPBYTE)pNextChar = 0;
                pNextChar = (LPVOID)((PCHAR)pNextChar + 1);
            }
            dwBufferRemaining -= dwReturnSize;
            pdhStatus = ERROR_SUCCESS;
        } else {
            pdhStatus = PDH_MORE_DATA;
        }
    }
    if (pTempBuffer) G_FREE(pTempBuffer);

    return pdhStatus;
}