/*++
Copyright (C) 1996-1999 Microsoft Corporation

Module Name:
    perfutil.c

Abstract:
    Performance registry interface functions
--*/

#include <windows.h>
#include "strsafe.h"
#include <pdh.h>
#include "pdhitype.h"
#include "pdhidef.h"
#include "perfdata.h"
#include "pdhmsg.h"
#include "strings.h"

DWORD
PdhiMakePerfLangId(
    LANGID  lID,
    LPWSTR  szBuffer
);

PPERF_MACHINE
PdhiAddNewMachine(
    PPERF_MACHINE   pLastMachine,
    LPWSTR          szMachineName
);

PPERF_MACHINE pFirstMachine = NULL;

PDH_STATUS
ConnectMachine(
    PPERF_MACHINE pThisMachine
)
{
    PDH_STATUS  pdhStatus       = ERROR_SUCCESS;
    LONG        lStatus         = ERROR_SUCCESS;
    FILETIME    CurrentFileTime;
    LONGLONG    llCurrentTime;
    WCHAR       szOsVer[OS_VER_SIZE];
    HKEY        hKeyRemMachine;
    HKEY        hKeyRemCurrentVersion;
    DWORD       dwBufSize;
    DWORD       dwType;
    BOOL        bUpdateRetryTime = FALSE;
    DWORD       dwReconnecting;

    if (pThisMachine == NULL) {
        pdhStatus = PDH_INVALID_ARGUMENT;
    } else {
        pdhStatus = WAIT_FOR_AND_LOCK_MUTEX(pThisMachine->hMutex);
    }

    if (pdhStatus == ERROR_SUCCESS) {
    // connect to system's performance registry
        if (lstrcmpiW(pThisMachine->szName, szStaticLocalMachineName) == 0) {
            // only one thread at a time can try to connect to a machine.

            pThisMachine->dwRefCount++;

            // assign default OS version
            // assume NT4 unless found otherwise
            StringCchCopyW(pThisMachine->szOsVer, OS_VER_SIZE, (LPCWSTR) L"4.0");   
            // this is the local machine so use the local reg key
            pThisMachine->hKeyPerformanceData = HKEY_PERFORMANCE_DATA;

            // look up the OS version and save it
            lStatus = RegOpenKeyExW(HKEY_LOCAL_MACHINE, cszCurrentVersionKey, 0L, KEY_READ, & hKeyRemCurrentVersion);
            if (lStatus == ERROR_SUCCESS) {
                dwType=0;
                dwBufSize = OS_VER_SIZE * sizeof(WCHAR);
                lStatus = RegQueryValueExW(hKeyRemCurrentVersion,
                                           cszCurrentVersionValueName,
                                           0L,
                                           & dwType,
                                           (LPBYTE) szOsVer,
                                           & dwBufSize);
                if ((lStatus == ERROR_SUCCESS) && (dwType == REG_SZ)) {
                    StringCchCopyW(pThisMachine->szOsVer, OS_VER_SIZE, szOsVer);
                }
                RegCloseKey(hKeyRemCurrentVersion);
            }
        }
        else {
            // now try to connect if the retry timeout has elapzed
            GetSystemTimeAsFileTime(& CurrentFileTime);
            llCurrentTime  = MAKELONGLONG(CurrentFileTime.dwLowDateTime, CurrentFileTime.dwHighDateTime);
            dwReconnecting = (DWORD)InterlockedCompareExchange((PLONG) & pThisMachine->dwRetryFlags, TRUE, FALSE);

            if (! dwReconnecting) {
               if ((pThisMachine->llRetryTime == 0) || (pThisMachine->llRetryTime <= llCurrentTime)) {
                    // only one thread at a time can try to connect to a machine.

                    pThisMachine->dwRefCount ++;
                    bUpdateRetryTime = TRUE; // only update after an attempt has been made

                    __try {
                        // close any open keys
                        if (pThisMachine->hKeyPerformanceData != NULL) {
                            RegCloseKey(pThisMachine->hKeyPerformanceData);
                            pThisMachine->hKeyPerformanceData = NULL;
                        }
                    }
                    __except (EXCEPTION_EXECUTE_HANDLER) {
                        lStatus = GetExceptionCode();
                    }

                    if (lStatus != ERROR_SUCCESS) {
                        pThisMachine->hKeyPerformanceData = NULL;
                    }
                    else {
                        // get OS version of remote machine
                        lStatus = RegConnectRegistryW(pThisMachine->szName,
                                                      HKEY_LOCAL_MACHINE,
                                                      & hKeyRemMachine);
                        if (lStatus == ERROR_SUCCESS) {
                            // look up the OS version and save it
                            lStatus = RegOpenKeyExW(hKeyRemMachine,
                                                    cszCurrentVersionKey,
                                                    0L,
                                                    KEY_READ,
                                                    & hKeyRemCurrentVersion);
                            if (lStatus == ERROR_SUCCESS) {
                                dwType=0;
                                dwBufSize = OS_VER_SIZE * sizeof(WCHAR);
                                lStatus = RegQueryValueExW(hKeyRemCurrentVersion,
                                                           cszCurrentVersionValueName,
                                                           0L,
                                                           & dwType,
                                                           (LPBYTE) szOsVer,
                                                           & dwBufSize);
                                if ((lStatus == ERROR_SUCCESS) && (dwType == REG_SZ)) {
                                    StringCchCopyW(pThisMachine->szOsVer, OS_VER_SIZE, szOsVer);
                                }
                                RegCloseKey(hKeyRemCurrentVersion);
                            }
                            RegCloseKey(hKeyRemMachine);
                        }
                    }

                    if (lStatus == ERROR_SUCCESS) {
                        __try {
                            // Connect to remote registry
                            lStatus = RegConnectRegistryW(pThisMachine->szName,
                                                          HKEY_PERFORMANCE_DATA,
                                                          & pThisMachine->hKeyPerformanceData);
                        }
                        __except (EXCEPTION_EXECUTE_HANDLER) {
                            lStatus = GetExceptionCode();
                        }
                    } // else pass error through
                }
                else {
                   // not time to reconnect yet so save the old status and 
                   // clear the registry key
                    pThisMachine->hKeyPerformanceData = NULL;
                    lStatus                           = pThisMachine->dwStatus;
                }
                 // clear the reconnecting flag
                InterlockedExchange((LONG *) & pThisMachine->dwRetryFlags, FALSE);
            }
            else {
                // some other thread is trying to connect
                pdhStatus = PDH_CANNOT_CONNECT_MACHINE;
                goto Cleanup;
            }
        }

        if ((pThisMachine->hKeyPerformanceData != NULL) && (pThisMachine->dwRetryFlags == 0)) {
            // successfully connected to computer's registry, so
            // get the performance names from that computer and cache them
    /*
            the shortcut of mapping local strings cannot be used reliably until
            more synchronization of the mapped file is implemented. Just Mapping
            to the file and not locking it or checking for updates leaves it
            vulnerable to the mapped file being changed by an external program 
            and invalidating the pointer table built by the BuildLocalNameTable
            function.

            Until this synchronization and locking is implemented, the 
            BuildLocalNameTable function should not be used.
    */
            if (pThisMachine->hKeyPerformanceData != HKEY_PERFORMANCE_DATA) {
                if (pThisMachine->szPerfStrings != NULL) {
                    // reload the perf strings, incase new ones have been
                    // installed
                    if (pThisMachine->sz009PerfStrings != NULL
                                    && pThisMachine->sz009PerfStrings != pThisMachine->szPerfStrings) {
                        G_FREE(pThisMachine->sz009PerfStrings);
                    }
                    G_FREE(pThisMachine->typePerfStrings);
                    G_FREE(pThisMachine->szPerfStrings);
                    pThisMachine->sz009PerfStrings = NULL;
                    pThisMachine->typePerfStrings  = NULL;
                    pThisMachine->szPerfStrings    = NULL;
                }
                BuildNameTable(pThisMachine->szName, GetUserDefaultUILanguage(), pThisMachine);
                if (pThisMachine->szPerfStrings == NULL) {
                    BuildNameTable(pThisMachine->szName, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), pThisMachine);
                }
            }
            else {
                if (pThisMachine->szPerfStrings != NULL) {
                    // reload the perf strings, incase new ones have been
                    // installed
                    if (pThisMachine->sz009PerfStrings != NULL
                                    && pThisMachine->sz009PerfStrings != pThisMachine->szPerfStrings) {
                        G_FREE(pThisMachine->sz009PerfStrings);
                    }
                    G_FREE(pThisMachine->typePerfStrings);
                    G_FREE(pThisMachine->szPerfStrings);
                    pThisMachine->sz009PerfStrings = NULL;
                    pThisMachine->typePerfStrings  = NULL;
                    pThisMachine->szPerfStrings    = NULL;
                }
                BuildNameTable(NULL, GetUserDefaultUILanguage(), pThisMachine);
                if (pThisMachine->szPerfStrings == NULL) {
                    BuildNameTable(NULL, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), pThisMachine);
                }
                pThisMachine->pLocalNameInfo = NULL;
            }

            if (pThisMachine->szPerfStrings != NULL) {
                pdhStatus              = ERROR_SUCCESS;
                pThisMachine->dwStatus = ERROR_SUCCESS;
            }
            else {
                // unable to read system counter name strings
                pdhStatus                      = PDH_CANNOT_READ_NAME_STRINGS;
                ZeroMemory(& pThisMachine->LastStringUpdateTime, sizeof(pThisMachine->LastStringUpdateTime));
                pThisMachine->dwLastPerfString = 0;
                pThisMachine->dwStatus         = PDH_CSTATUS_NO_MACHINE;
            }
        }
        else {
            // unable to connect to the specified machine
            pdhStatus = PDH_CANNOT_CONNECT_MACHINE;
            // Set error to either 
            //  "PDH_CSTATUS_NO_MACHINE" if no connection could be made
            // or 
            //  PDH_ACCESS_DENIED if ERROR_ACCESS_DENIED status is returned
            // since if ERROR_ACCESS_DENIED is returned, then reconnection will
            // probably be futile.
            if ((lStatus == ERROR_ACCESS_DENIED) || (lStatus == PDH_ACCESS_DENIED)) {
                pThisMachine->dwStatus = PDH_ACCESS_DENIED;
            }
            else {
                pThisMachine->dwStatus = PDH_CSTATUS_NO_MACHINE;
            }
        }

        if (pdhStatus != ERROR_SUCCESS) {
            if (bUpdateRetryTime) {
                // this attempt didn't work so reset retry counter  to
                // wait some more for the machine to come back up.
                GetSystemTimeAsFileTime(& CurrentFileTime);
                llCurrentTime  = MAKELONGLONG(CurrentFileTime.dwLowDateTime, CurrentFileTime.dwHighDateTime);
                pThisMachine->llRetryTime = llCurrentTime;
                if (pThisMachine->dwStatus != PDH_ACCESS_DENIED) {
                    pThisMachine->llRetryTime += llRemoteRetryTime;
                }
            }
        }
        else {
            // clear the retry counter to allow function calls
            pThisMachine->llRetryTime = 0;
        }
        pThisMachine->dwRefCount --;
        RELEASE_MUTEX(pThisMachine->hMutex);
    }

Cleanup:
    return pdhStatus;
}

PPERF_MACHINE
PdhiAddNewMachine(
    PPERF_MACHINE pLastMachine,
    LPWSTR        szMachineName
)
{
    PPERF_MACHINE   pNewMachine   = NULL;
    LPWSTR          szNameBuffer  = NULL;
    LPWSTR          szIdList      = NULL;
    DWORD           dwNameSize    = 0;
    BOOL            bUseLocalName = TRUE;

    // reset the last error value
    SetLastError(ERROR_SUCCESS);

    if (szMachineName != NULL) {
        if (* szMachineName != L'\0') {
            bUseLocalName = FALSE;
        }
    }

    if (bUseLocalName) {
        dwNameSize = lstrlenW(szStaticLocalMachineName) + 1;
    } else {
        dwNameSize = lstrlenW(szMachineName) + 1;
    }
    pNewMachine = (PPERF_MACHINE) G_ALLOC(sizeof(PERF_MACHINE) + SMALL_BUFFER_SIZE + (sizeof(WCHAR) * dwNameSize));
    if  (pNewMachine != NULL) {
        szIdList     = (LPWSTR) ((LPBYTE) pNewMachine + sizeof(PERF_MACHINE));
        szNameBuffer = (LPWSTR) ((LPBYTE) szIdList + SMALL_BUFFER_SIZE);

        // initialize the new buffer
        pNewMachine->hKeyPerformanceData = NULL;
        pNewMachine->pLocalNameInfo      = NULL;
        pNewMachine->szName              = szNameBuffer;
        if (bUseLocalName) {
            StringCchCopyW(pNewMachine->szName, dwNameSize, szStaticLocalMachineName);
        }
        else {
            StringCchCopyW(pNewMachine->szName, dwNameSize, szMachineName);
        }

        pNewMachine->pSystemPerfData  = NULL;
        pNewMachine->szPerfStrings    = NULL;
        pNewMachine->sz009PerfStrings = NULL;
        pNewMachine->typePerfStrings  = NULL;
        pNewMachine->dwLastPerfString = 0;
        pNewMachine->dwRefCount       = 0;
        pNewMachine->szQueryObjects   = szIdList;
        pNewMachine->dwStatus         = PDH_CSTATUS_NO_MACHINE; // not connected yet
        pNewMachine->llRetryTime      = 1;   // retry connection immediately
        pNewMachine->dwRetryFlags     = 0;   // Not attempting a connection.
        pNewMachine->dwMachineFlags   = 0;
        pNewMachine->hMutex           = CreateMutex(NULL, FALSE, NULL);

        // everything went OK so far so add this entry to the list
        if (pLastMachine != NULL) {
            pNewMachine->pNext        = pLastMachine->pNext;
            pLastMachine->pNext       = pNewMachine;
            pNewMachine->pPrev        = pLastMachine;
            pNewMachine->pNext->pPrev = pNewMachine;
        }
        else {
            // this is the first item in the list so it
            // points to itself
            pNewMachine->pNext = pNewMachine;
            pNewMachine->pPrev = pNewMachine;
        }
    }
    else {
        // unable to allocate machine data memory
        SetLastError(PDH_MEMORY_ALLOCATION_FAILURE);
    }

    return pNewMachine;
}

PPERF_MACHINE
GetMachine(
    LPWSTR  szMachineName,
    DWORD   dwIndex,
    DWORD   dwFlags
)
{
    PPERF_MACHINE   pThisMachine  = NULL;
    PPERF_MACHINE   pLastMachine;
    BOOL            bFound        = FALSE;
    LPWSTR          szFnMachineName;
    BOOL            bNew          = FALSE; // true if this is a new machine to the list
    BOOL            bUseLocalName = TRUE;
    DWORD           dwLocalStatus = ERROR_SUCCESS;
    WCHAR           wszObject[MAX_PATH];

    // reset the last error value
    SetLastError(ERROR_SUCCESS);

    if (WAIT_FOR_AND_LOCK_MUTEX (hPdhDataMutex) == ERROR_SUCCESS) {
        if (szMachineName != NULL) {
            if (* szMachineName != L'\0') {
                bUseLocalName = FALSE;
            }
        }
        if (bUseLocalName) {
            szFnMachineName = szStaticLocalMachineName;
        }
        else {
            szFnMachineName = szMachineName;
        }

        // walk down list to find this machine

        pThisMachine = pFirstMachine;
        pLastMachine = NULL;

        // walk around entire list
        if (pThisMachine != NULL) {
            do {
                // walk down the list and look for a match
                if (lstrcmpiW(szFnMachineName, pThisMachine->szName) != 0) {
                    pLastMachine = pThisMachine;
                    pThisMachine = pThisMachine->pNext;
                }
                else {
                    bFound = TRUE;
                    break;
                }
            }
            while (pThisMachine != pFirstMachine);
        }
        // if thismachine == the first machine, then we couldn't find a match in
        // the list, if this machine is NULL, then there is no list
        if (! bFound) {
            // then this machine was not found so add it.
            pThisMachine = PdhiAddNewMachine(pLastMachine, szFnMachineName);
            if (pThisMachine != NULL) {
                bNew = TRUE;
                if (pFirstMachine == NULL) {
                    // then update the first pointer
                    pFirstMachine = pThisMachine;
                }
            }
            else {
                dwLocalStatus = PDH_MEMORY_ALLOCATION_FAILURE;
            }
        }

        if (dwLocalStatus == ERROR_SUCCESS) {
            dwLocalStatus = WAIT_FOR_AND_LOCK_MUTEX(pThisMachine->hMutex);
        }
        if (dwLocalStatus == ERROR_SUCCESS) {
            if (! (dwFlags & PDH_GM_UPDATE_PERFNAME_ONLY)) {
                if (dwFlags & PDH_GM_UPDATE_PERFDATA) {
                    pThisMachine->dwObjectId = dwIndex;
                    pThisMachine->dwThreadId = GetCurrentThreadId();
                }
                else if (pThisMachine->dwThreadId != GetCurrentThreadId()) {
                    dwFlags                 |= PDH_GM_UPDATE_PERFDATA;
                    pThisMachine->dwThreadId = GetCurrentThreadId();
                    pThisMachine->dwObjectId = dwIndex;
                }
                else if (pThisMachine->pSystemPerfData == NULL) {
                    dwFlags                 |= PDH_GM_UPDATE_PERFDATA;
                    pThisMachine->dwObjectId = dwIndex;
                }
                else if (pThisMachine->dwObjectId != 0 && pThisMachine->dwObjectId != dwIndex) {
                    dwFlags                 |= PDH_GM_UPDATE_PERFDATA;
                    pThisMachine->dwObjectId = dwIndex;
                }
            }

            if ((! bFound) || (dwFlags & PDH_GM_UPDATE_PERFDATA) || (dwFlags & PDH_GM_UPDATE_NAME)
                           || (pThisMachine->dwStatus != ERROR_SUCCESS)) {
                // then this is a new machine or the caller wants the data refreshed or the machine
                // has an entry, but is not yet on line first try to connect to the machine
                // the call to ConnectMachine updates the machine status so there's no need to keep it here.

                BOOL bUpdateName     = TRUE;

                if (! (dwFlags & PDH_GM_UPDATE_NAME)) {
                    if (pThisMachine->szPerfStrings != NULL && pThisMachine->typePerfStrings != NULL) {
                        // No need to update performance name/explain strings, assume that they are OK.
                        //
                        bUpdateName = FALSE;
                    }
                }
                if (bUpdateName || pThisMachine->dwStatus != ERROR_SUCCESS) {
                    G_FREE(pThisMachine->pSystemPerfData);
                    pThisMachine->pSystemPerfData = NULL;
                    dwLocalStatus = ConnectMachine(pThisMachine);
                }
                if (dwLocalStatus != ERROR_SUCCESS) {
                    dwLocalStatus = pThisMachine->dwStatus;
                }
                else if (! (dwFlags & PDH_GM_UPDATE_PERFNAME_ONLY)) {
                    // connected to the machine so
                    // then lock access to it
                    // the caller of this function must release the mutex

                    if (dwFlags & PDH_GM_UPDATE_PERFDATA) {
                        // get the current system counter info
                        ZeroMemory(wszObject, MAX_PATH * sizeof(WCHAR));
                        if (pThisMachine->dwObjectId == 0) {
                            StringCchCopyW(wszObject, MAX_PATH, (LPWSTR) cszGlobal);
                        }
                        else {
                            StringCchPrintfW(wszObject, MAX_PATH, L"%d", pThisMachine->dwObjectId);
                        }
                        pThisMachine->dwStatus = GetSystemPerfData(
                                        pThisMachine->hKeyPerformanceData,
                                        & pThisMachine->pSystemPerfData,
                                        wszObject,
                                        (BOOL) (dwFlags & PDH_GM_READ_COSTLY_DATA)
                            );
                        if ((dwFlags & PDH_GM_READ_COSTLY_DATA) && (pThisMachine->dwStatus == ERROR_SUCCESS)) {
                            pThisMachine->dwMachineFlags |= PDHIPM_FLAGS_HAVE_COSTLY;
                        }
                        else {
                            pThisMachine->dwMachineFlags &= ~PDHIPM_FLAGS_HAVE_COSTLY;
                        }
                        dwLocalStatus = pThisMachine->dwStatus;
                    }
                }
            }
        }
        else {
            pThisMachine  = NULL;
            dwLocalStatus = WAIT_TIMEOUT;
        }

        if (pThisMachine != NULL) {
            // machine found so bump the ref count
            // NOTE!!! the caller must release this!!!
            pThisMachine->dwRefCount ++;
        }

        // at this point if pThisMachine is NULL then it was not found, nor
        // could it be added otherwise it is pointing to the matching machine
        // structure

        RELEASE_MUTEX(hPdhDataMutex);
    }
    else {
        dwLocalStatus = WAIT_TIMEOUT;
    }

    if (dwLocalStatus != ERROR_SUCCESS) {
        SetLastError(dwLocalStatus);
    }
    return pThisMachine;
}

BOOL
FreeMachine(
    PPERF_MACHINE pMachine,
    BOOL          bForceRelease,
    BOOL          bProcessExit
)
{
    PPERF_MACHINE pPrev;
    PPERF_MACHINE pNext;
    HANDLE        hMutex;

    // unlink if this isn't the only one in the list

    if ((!bForceRelease) && (pMachine->dwRefCount)) return FALSE;

    hMutex = pMachine->hMutex;
    if (WAIT_FOR_AND_LOCK_MUTEX (hMutex) != ERROR_SUCCESS) {
        SetLastError(WAIT_TIMEOUT);
        return FALSE;
    }

    pPrev = pMachine->pPrev;
    pNext = pMachine->pNext;

    if ((pPrev != pMachine) && (pNext != pMachine)) {
        // this is not the only entry in the list
        pPrev->pNext = pNext;
        pNext->pPrev = pPrev;
        if (pMachine == pFirstMachine) {
            // then we are deleting the first one in the list so
            // update the list head to point to the next one in line
            pFirstMachine = pNext;
        }
    }
    else {
        // this is the only entry so clear the head pointer
        pFirstMachine = NULL;
    }

    // now free all allocated memory

    G_FREE(pMachine->pSystemPerfData);
    G_FREE(pMachine->typePerfStrings);
    if (pMachine->sz009PerfStrings != NULL && pMachine->sz009PerfStrings != pMachine->szPerfStrings) {
        G_FREE(pMachine->sz009PerfStrings);
    }
    G_FREE(pMachine->szPerfStrings);

    // close key
    if (pMachine->hKeyPerformanceData != NULL) {
        if ((! bProcessExit) || pMachine->hKeyPerformanceData != HKEY_PERFORMANCE_DATA) {
            RegCloseKey(pMachine->hKeyPerformanceData);
        }
        pMachine->hKeyPerformanceData = NULL;
    }

    // free memory block
    G_FREE(pMachine);

    // release and close mutex

    RELEASE_MUTEX(hMutex);

    if (hMutex != NULL) {
        CloseHandle(hMutex);
    }

    return TRUE;
}

BOOL
FreeAllMachines (
    BOOL bProcessExit
)
{
    PPERF_MACHINE pThisMachine;

    // free any machines in the machine list
    if (pFirstMachine != NULL) {
        if (WAIT_FOR_AND_LOCK_MUTEX (hPdhDataMutex) == ERROR_SUCCESS) {
            pThisMachine = pFirstMachine;
            while (pFirstMachine != pFirstMachine->pNext) {
                // delete from list
                // the deletion routine updates the prev pointer as it
                // removes the specified entry.
                FreeMachine(pThisMachine->pPrev, TRUE, bProcessExit);
                if (pFirstMachine == NULL) break;
            }
            // remove last query
            if (pFirstMachine) {
                FreeMachine(pFirstMachine, TRUE, bProcessExit);
            }
            pFirstMachine = NULL;
            RELEASE_MUTEX (hPdhDataMutex);
        }
        else {
            SetLastError (WAIT_TIMEOUT);
            return FALSE;
        }
    }
    return TRUE;

}

DWORD
GetObjectId(
    PPERF_MACHINE   pMachine,
    LPWSTR          szObjectName,
    BOOL          * bInstances
)
{
    PPERF_OBJECT_TYPE pObject    = NULL;
    DWORD             dwIndex    = 2;
    DWORD             dwRtnIndex = (DWORD) -1;
    LPWSTR            szName;
    WCHAR             szIndex[MAX_PATH];
    BOOL              bName      = TRUE;
    BOOL              bCheck;

    if (pMachine->szPerfStrings == NULL || dwIndex > pMachine->dwLastPerfString) return dwRtnIndex;

    while (dwRtnIndex == (DWORD) -1) {
        bCheck = TRUE;
        if (bName) {
            szName = pMachine->szPerfStrings[dwIndex];
            bCheck = (szName != NULL) ? TRUE : FALSE;
            if (bCheck) bCheck = (lstrcmpiW(szName, (LPWSTR) szObjectName) == 0) ? TRUE : FALSE;
        }

        if (bCheck) {
            if (pMachine->dwStatus != ERROR_SUCCESS) {
                bCheck = TRUE;
            }
            else if (pMachine->dwThreadId != GetCurrentThreadId()) {
                bCheck = TRUE;
            }
            else if (pMachine->pSystemPerfData == NULL) {
                bCheck = TRUE;
            }
            else if (pMachine->dwObjectId != 0 && pMachine->dwObjectId != dwIndex) {
                bCheck = TRUE;
            }
            else {
                bCheck = FALSE;
            }

            if (bCheck) {
                ZeroMemory(szIndex, MAX_PATH * sizeof(WCHAR));
                StringCchPrintfW(szIndex, MAX_PATH, L"%d", dwIndex);
                pMachine->dwStatus = GetSystemPerfData(
                                pMachine->hKeyPerformanceData, & pMachine->pSystemPerfData, szIndex, FALSE);
                pMachine->dwThreadId = GetCurrentThreadId();
                pMachine->dwObjectId = dwIndex;
            }
            if (pMachine->dwStatus == ERROR_SUCCESS) {
                pObject = GetObjectDefByTitleIndex(pMachine->pSystemPerfData, dwIndex);
                if (pObject != NULL) {
                    LPCWSTR szTmpObjectName = PdhiLookupPerfNameByIndex(pMachine, pObject->ObjectNameTitleIndex);
                    TRACE((PDH_DBG_TRACE_INFO),
                          (__LINE__,
                           PDH_PERFUTIL,
                           ARG_DEF(ARG_TYPE_WSTR, 1) | ARG_DEF(ARG_TYPE_WSTR, 2),
                           ERROR_SUCCESS,
                           TRACE_WSTR(szObjectName),
                           TRACE_WSTR(szTmpObjectName),
                           TRACE_DWORD(pObject->ObjectNameTitleIndex),
                           TRACE_DWORD(pObject->ObjectHelpTitleIndex),
                           TRACE_DWORD(pObject->NumCounters),
                           TRACE_DWORD(pObject->DefaultCounter),
                           TRACE_DWORD(pObject->NumInstances),
                           NULL));
                    if (bInstances != NULL) {
                        * bInstances = (pObject->NumInstances != PERF_NO_INSTANCES ? TRUE : FALSE);
                    }
                    dwRtnIndex           = dwIndex;
                    break;
                }
            }
        }

        if (! bName) {
            break;
        }
        else if (dwIndex >= pMachine->dwLastPerfString) {
            dwIndex = wcstoul(szObjectName, NULL, 10);
            bName   = FALSE;
            if (dwIndex == 0) break;
        }
        else {
            dwIndex += 2;
            if (dwIndex > pMachine->dwLastPerfString) {
                dwIndex = wcstoul(szObjectName, NULL, 10);
                bName   = FALSE;
                if (dwIndex == 0) break;
            }
        }
    }

    return dwRtnIndex;
}

DWORD
GetCounterId (
    PPERF_MACHINE pMachine,
    DWORD         dwObjectId,
    LPWSTR        szCounterName
)
{
    PPERF_OBJECT_TYPE        pObject;
    PPERF_COUNTER_DEFINITION pCounter;
    DWORD                    dwCounterTitle = (DWORD) -1;

    pObject = GetObjectDefByTitleIndex(pMachine->pSystemPerfData, dwObjectId);
    if (pObject != NULL) {
        pCounter = GetCounterDefByName(pObject, pMachine->dwLastPerfString, pMachine->szPerfStrings, szCounterName);
        if (pCounter != NULL) {
            // update counter name string
            LPCWSTR szTmpCounterName = PdhiLookupPerfNameByIndex(pMachine, pCounter->CounterNameTitleIndex);
            TRACE((PDH_DBG_TRACE_INFO),
                  (__LINE__,
                   PDH_PERFUTIL,
                   ARG_DEF(ARG_TYPE_WSTR, 1) | ARG_DEF(ARG_TYPE_WSTR, 2)
                                             | ARG_DEF(ARG_TYPE_ULONGX, 6),
                   ERROR_SUCCESS,
                   TRACE_WSTR(szCounterName),
                   TRACE_WSTR(szTmpCounterName),
                   TRACE_DWORD(dwObjectId),
                   TRACE_DWORD(pCounter->CounterNameTitleIndex),
                   TRACE_DWORD(pCounter->CounterHelpTitleIndex),
                   TRACE_DWORD(pCounter->CounterType),
                   TRACE_DWORD(pCounter->CounterSize),
                   TRACE_DWORD(pCounter->CounterOffset),
                   NULL));
            dwCounterTitle = pCounter->CounterNameTitleIndex;
        }
        else {
            dwCounterTitle = wcstoul(szCounterName, NULL, 10);
            if (dwCounterTitle == 0) dwCounterTitle = (DWORD) -1;
        }
    }
    return dwCounterTitle;
}

BOOL
InitPerflibCounterInfo(
    PPDHI_COUNTER   pCounter
)
/*++
Routine Description:
    Initializes the perflib related fields of the counter structure

Arguments:
    IN      PPDHI_COUNTER   pCounter
        pointer to the counter structure to initialize

Return Value:
    TRUE
--*/
{
    PPERF_OBJECT_TYPE        pPerfObject    = NULL;
    PPERF_COUNTER_DEFINITION pPerfCounter   = NULL;

    if (pCounter->pQMachine->pMachine == NULL) {
        pCounter->ThisValue.CStatus = PDH_CSTATUS_NO_MACHINE;
        return FALSE;
    } else if (pCounter->pQMachine->pMachine->dwStatus != ERROR_SUCCESS) {
        // machine not initialized
        return FALSE;
    }

    // get perf object definition from system data structure
    pPerfObject = GetObjectDefByTitleIndex (
        pCounter->pQMachine->pMachine->pSystemPerfData,
        pCounter->plCounterInfo.dwObjectId);

    if (pPerfObject != NULL) {
        // object was found now look up counter definition
        pPerfCounter = GetCounterDefByTitleIndex (pPerfObject, 0,
            pCounter->plCounterInfo.dwCounterId);
        if (pPerfCounter != NULL) {
            // get system perf data info
            // (pack into a DWORD)
            pCounter->CVersion = pCounter->pQMachine->pMachine->pSystemPerfData->Version;
            pCounter->CVersion &= 0x0000FFFF;
            pCounter->CVersion <<= 16;
            pCounter->CVersion &= 0xFFFF0000;
            pCounter->CVersion |= (pCounter->pQMachine->pMachine->pSystemPerfData->Revision & 0x0000FFFF);

            // get the counter's time base
            if (pPerfCounter->CounterType & PERF_TIMER_100NS) {
                pCounter->TimeBase = (LONGLONG)10000000;
            } else if (pPerfCounter->CounterType & PERF_OBJECT_TIMER) {
                // then get the time base freq from the object
                pCounter->TimeBase = pPerfObject->PerfFreq.QuadPart;
            } else { // if (pPerfCounter->CounterType & PERF_TIMER_TICK or other)
                pCounter->TimeBase = pCounter->pQMachine->pMachine->pSystemPerfData->PerfFreq.QuadPart;
            }

            // look up info from counter definition
            pCounter->plCounterInfo.dwCounterType =
                pPerfCounter->CounterType;
            pCounter->plCounterInfo.dwCounterSize =
                pPerfCounter->CounterSize;

            pCounter->plCounterInfo.lDefaultScale =
                pPerfCounter->DefaultScale;

            //
            //  get explain text pointer
            pCounter->szExplainText =
                (LPWSTR)PdhiLookupPerfNameByIndex (
                    pCounter->pQMachine->pMachine,
                    pPerfCounter->CounterHelpTitleIndex);

            //
            //  now clear/initialize the raw counter info
            //
            pCounter->ThisValue.TimeStamp.dwLowDateTime = 0;
            pCounter->ThisValue.TimeStamp.dwHighDateTime = 0;
            pCounter->ThisValue.MultiCount = 1;
            pCounter->ThisValue.FirstValue = 0;
            pCounter->ThisValue.SecondValue = 0;
            //
            pCounter->LastValue.TimeStamp.dwLowDateTime = 0;
            pCounter->LastValue.TimeStamp.dwHighDateTime = 0;
            pCounter->LastValue.MultiCount = 1;
            pCounter->LastValue.FirstValue = 0;
            pCounter->LastValue.SecondValue = 0;
            //
            //  clear data array pointers
            //
            pCounter->pThisRawItemList = NULL;
            pCounter->pLastRawItemList = NULL;
            //
            //  lastly update status
            //
            if (pCounter->ThisValue.CStatus == 0)  {
                // don't overwrite any other status values
                pCounter->ThisValue.CStatus = PDH_CSTATUS_VALID_DATA;
            }
            return TRUE;
        } else {
            // unable to find counter
            pCounter->ThisValue.CStatus = PDH_CSTATUS_NO_COUNTER;
            return FALSE;
        }
    } else {
        // unable to find object
        pCounter->ThisValue.CStatus = PDH_CSTATUS_NO_OBJECT;
        return FALSE;
    }
}

#pragma warning ( disable : 4127 )
STATIC_BOOL
IsNumberInUnicodeList(
    DWORD   dwNumber,
    LPWSTR  lpwszUnicodeList
)
/*++
IsNumberInUnicodeList

Arguments:
    IN dwNumber
        DWORD number to find in list
    IN lpwszUnicodeList
        Null terminated, Space delimited list of decimal numbers

Return Value:
    TRUE:
            dwNumber was found in the list of unicode number strings
    FALSE:
            dwNumber was not found in the list.
--*/
{
    DWORD   dwThisNumber;
    LPWSTR  pwcThisChar;
    BOOL    bValidNumber;
    BOOL    bNewItem;
    BOOL    bReturn = FALSE;
    BOOL    bDone   = FALSE;
    WCHAR   wcDelimiter;    // could be an argument to be more flexible

    if (lpwszUnicodeList == 0) return FALSE;    // null pointer, # not founde

    pwcThisChar  = lpwszUnicodeList;
    dwThisNumber = 0;
    wcDelimiter  = SPACE_L;
    bValidNumber = FALSE;
    bNewItem     = TRUE;

    while (! bDone) {
        switch (EvalThisChar(* pwcThisChar, wcDelimiter)) {
        case DIGIT:
            // if this is the first digit after a delimiter, then
            // set flags to start computing the new number
            if (bNewItem) {
                bNewItem     = FALSE;
                bValidNumber = TRUE;
            }
            if (bValidNumber) {
                dwThisNumber *= 10;
                dwThisNumber += (* pwcThisChar - (WCHAR) '0');
            }
            break;

        case DELIMITER:
            // a delimter is either the delimiter character or the
            // end of the string ('\0') if when the delimiter has been
            // reached a valid number was found, then compare it to the
            // number from the argument list. if this is the end of the
            // string and no match was found, then return.
            //
            if (bValidNumber) {
                if (dwThisNumber == dwNumber) {
                    bDone   = TRUE;
                    bReturn = TRUE;
                }
                else {
                    bValidNumber = FALSE;
                }
            }
            if (! bDone) {
                if (* pwcThisChar == L'\0') {
                    bDone   = TRUE;
                    bReturn = FALSE;
                }
                else {
                    bNewItem     = TRUE;
                    dwThisNumber = 0;
                }
            }
            break;

        case INVALID:
            // if an invalid character was encountered, ignore all
            // characters up to the next delimiter and then start fresh.
            // the invalid number is not compared.
            bValidNumber = FALSE;
            break;

        default:
            break;
        }
        pwcThisChar ++;
    }

    return bReturn;
}   // IsNumberInUnicodeList
#pragma warning ( default : 4127 )

BOOL
AppendObjectToValueList(
    DWORD   dwObjectId,
    PWSTR   pwszValueList,
    DWORD   dwValueList
)
/*++
AppendObjectToValueList

Arguments:
    IN dwNumber
        DWORD number to insert in list
    IN PWSTR
        pointer to wide char string that contains buffer that is
        Null terminated, Space delimited list of decimal numbers that
        may have this number appended to.

Return Value:
    TRUE:
            dwNumber was added to list
    FALSE:
            dwNumber was not added. (because it's already there or
                an error occured)
--*/
{
    WCHAR   tempString[16];
    BOOL    bReturn = FALSE;
    LPWSTR  szFormatString;

    if (!pwszValueList) {
        bReturn = FALSE;
    }
    else if (IsNumberInUnicodeList(dwObjectId, pwszValueList)) {
        bReturn = FALSE;   // object already in list
    }
    else {
        __try {
            if (* pwszValueList == 0) {
                // then this is the first string so no delimiter
                szFormatString = (LPWSTR) fmtDecimal;
            }
            else {
                // this is being added to the end so include the delimiter
                szFormatString = (LPWSTR) fmtSpaceDecimal;
            }
            // format number and append the new object id the  value list
            StringCchPrintfW(tempString, 16, szFormatString, dwObjectId);
            StringCchCatW(pwszValueList, dwValueList, tempString);
            bReturn = TRUE;
        }
        __except (EXCEPTION_EXECUTE_HANDLER) {
            bReturn = FALSE;
        }
    }
    return bReturn;
}

BOOL
GetInstanceByNameMatch(
    PPERF_MACHINE pMachine,
    PPDHI_COUNTER pCounter
)
{
    PPERF_INSTANCE_DEFINITION pInstanceDef;
    PPERF_OBJECT_TYPE         pObjectDef;
    LONG                      lInstanceId = PERF_NO_UNIQUE_ID;
    BOOL                      bReturn     = FALSE;

    // get the instances object

    pObjectDef = GetObjectDefByTitleIndex(pMachine->pSystemPerfData, pCounter->plCounterInfo.dwObjectId);
    if (pObjectDef != NULL) {
        pInstanceDef = FirstInstance(pObjectDef);
        if (pInstanceDef != NULL) {
            if (pInstanceDef->UniqueID == PERF_NO_UNIQUE_ID) {
                // get instance in that object by comparing names
                // if there is no parent specified, then just look it up by name
                pInstanceDef = GetInstanceByName(pMachine->pSystemPerfData,
                                                 pObjectDef,
                                                 pCounter->pCounterPath->szInstanceName,
                                                 pCounter->pCounterPath->szParentName,
                                                 pCounter->pCounterPath->dwIndex);
            }
            else {
                // get numeric equivalent of Instance ID
                if (pCounter->pCounterPath->szInstanceName != NULL) {
                    lInstanceId = wcstol(pCounter->pCounterPath->szInstanceName, NULL, 10);
                }
                pInstanceDef = GetInstanceByUniqueId(pObjectDef, lInstanceId);
            }

            // update counter fields
            pCounter->plCounterInfo.lInstanceId = lInstanceId;
            if (lInstanceId == -1) {
                // use instance NAME
                pCounter->plCounterInfo.szInstanceName       = pCounter->pCounterPath->szInstanceName;
                pCounter->plCounterInfo.szParentInstanceName = pCounter->pCounterPath->szParentName;
            }
            else {
                // use instance ID number
                pCounter->plCounterInfo.szInstanceName       = NULL;
                pCounter->plCounterInfo.szParentInstanceName = NULL;
            }
        }
        if (pInstanceDef != NULL) {
            // instance found
            bReturn = TRUE;
        }
    }
    return bReturn;
}

BOOL
GetObjectPerfInfo(
    PPERF_DATA_BLOCK   pPerfData,
    DWORD              dwObjectId,
    LONGLONG         * pPerfTime,
    LONGLONG         * pPerfFreq
)
{
    PPERF_OBJECT_TYPE pObject;
    BOOL              bReturn = FALSE;

    pObject = GetObjectDefByTitleIndex(pPerfData, dwObjectId);
    if (pObject != NULL) {
        __try {
            * pPerfTime = pObject->PerfTime.QuadPart;
            * pPerfFreq = pObject->PerfFreq.QuadPart;
            bReturn     = TRUE;
        }
        __except (EXCEPTION_EXECUTE_HANDLER) {
            bReturn = FALSE;
        }
    }
    return bReturn;
}

PDH_STATUS
ValidateMachineConnection(
    PPERF_MACHINE   pMachine
)
{
    PDH_STATUS  pdhStatus     = ERROR_SUCCESS;
    HANDLE      hThread;
    DWORD       ThreadId;
    DWORD       dwWaitStatus;
    DWORD       dwReconnecting;
    LONGLONG    llCurrentTime;
    FILETIME    CurrentFileTime;

    // if a connection or request has failed, this will be
    // set to an error status
    if (pMachine != NULL) {
        if (pMachine->dwStatus != ERROR_SUCCESS) {
            // get the current time
            GetSystemTimeAsFileTime(& CurrentFileTime);
            llCurrentTime  = MAKELONGLONG(CurrentFileTime.dwLowDateTime, CurrentFileTime.dwHighDateTime);
            if (pMachine->llRetryTime <= llCurrentTime) {
                if (pMachine->llRetryTime != 0) {
                    // see what's up by trying to reconnect
                    dwReconnecting = pMachine->dwRetryFlags;
                    if (!dwReconnecting) {
                        pdhStatus = ConnectMachine(pMachine);
                    }
                    else {
                        // a connection attempt is in process so do nothing here
                        pdhStatus = PDH_CANNOT_CONNECT_MACHINE;
                    }
                }
            }
            else {
                // it's not retry time, yet so machine is off line still
                pdhStatus = PDH_CSTATUS_NO_MACHINE;
            }
        }
    }
    else {
        pdhStatus = PDH_CSTATUS_NO_MACHINE;
    }
    return pdhStatus;
}