/*++ BUILD Version: 0001    // Increment this if a change has global effects

Copyright (c) 1994-1997   Microsoft Corporation

Module Name:

    utils.c

Abstract:

        Utility functions used by the performance library functions

Author:

    Russ Blake  11/15/91

Revision History:


--*/
#define UNICODE
//
//  Include files
//
#pragma warning(disable:4306)
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <winperf.h>
#include <prflbmsg.h>
#include <regrpc.h>
#include "ntconreg.h"
#include "perflib.h"
#pragma warning(default:4306)

// test for delimiter, end of line and non-digit characters
// used by IsNumberInUnicodeList routine
//
#define DIGIT       1
#define DELIMITER   2
#define INVALID     3

#define EvalThisChar(c,d) ( \
     (c == d) ? DELIMITER : \
     (c == 0) ? DELIMITER : \
     (c < '0') ? INVALID : \
     (c > '9') ? INVALID : \
     DIGIT)

#define MAX_KEYWORD_LEN   (sizeof (ADDHELP_STRING) / sizeof(WCHAR))
const   WCHAR GLOBAL_STRING[]     = L"GLOBAL";
const   WCHAR FOREIGN_STRING[]    = L"FOREIGN";
const   WCHAR COSTLY_STRING[]     = L"COSTLY";
const   WCHAR COUNTER_STRING[]    = L"COUNTER";
const   WCHAR HELP_STRING[]       = L"EXPLAIN";
const   WCHAR HELP_STRING2[]      = L"HELP";
const   WCHAR ADDCOUNTER_STRING[] = L"ADDCOUNTER";
const   WCHAR ADDHELP_STRING[]    = L"ADDEXPLAIN";
const   WCHAR ONLY_STRING[]       = L"ONLY";
const   WCHAR DisablePerformanceCounters[] = L"Disable Performance Counters";

// minimum length to hold a value name understood by Perflib

const   DWORD VALUE_NAME_LENGTH = ((sizeof(COSTLY_STRING) * sizeof(WCHAR)) + sizeof(UNICODE_NULL));

#define PL_TIMER_START_EVENT    0
#define PL_TIMER_EXIT_EVENT     1
#define PL_TIMER_NUM_OBJECTS    2

static HANDLE   hTimerHandles[PL_TIMER_NUM_OBJECTS] = {NULL,NULL};

static  HANDLE  hTimerDataMutex = NULL;
static  HANDLE  hPerflibTimingThread   = NULL;
static  LPOPEN_PROC_WAIT_INFO   pTimerItemListHead = NULL;
#define PERFLIB_TIMER_INTERVAL  200     // 200 ms Timer
#define PERFLIB_TIMEOUT_COUNT    64

extern HANDLE hEventLog;

#ifdef DBG
#include <stdio.h> // for _vsnprintf
#define DEBUG_BUFFER_LENGTH MAX_PATH*2

ULONG PerfLibDebug = 0;
UCHAR PerfLibDebugBuffer[DEBUG_BUFFER_LENGTH];
#endif


//
//  Perflib functions:
//
NTSTATUS
GetPerflibKeyValue (
    LPCWSTR szItem,
    DWORD   dwRegType,
    DWORD   dwMaxSize,      // ... of pReturnBuffer in bytes
    LPVOID  pReturnBuffer,
    DWORD   dwDefaultSize,  // ... of pDefault in bytes
    LPVOID  pDefault
)
/*++

    read and return the current value of the specified value
    under the Perflib registry key. If unable to read the value
    return the default value from the argument list.

    the value is returned in the pReturnBuffer.

--*/
{

    HKEY                    hPerflibKey;
    OBJECT_ATTRIBUTES       Obja;
    NTSTATUS                Status;
    UNICODE_STRING          PerflibSubKeyString;
    UNICODE_STRING          ValueNameString;
    LONG                    lReturn = STATUS_SUCCESS;
    PKEY_VALUE_PARTIAL_INFORMATION  pValueInformation, pTemp;
    ULONG                   ValueBufferLength;
    ULONG                   ResultLength;
    BOOL                    bUseDefault = TRUE;

    // initialize UNICODE_STRING structures used in this function

    RtlInitUnicodeString (
        &PerflibSubKeyString,
        (LPCWSTR)L"\\Registry\\Machine\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib");

    RtlInitUnicodeString (
        &ValueNameString,
        (LPWSTR)szItem);

    //
    // Initialize the OBJECT_ATTRIBUTES structure and open the key.
    //
    InitializeObjectAttributes(
            &Obja,
            &PerflibSubKeyString,
            OBJ_CASE_INSENSITIVE,
            NULL,
            NULL
            );

    Status = NtOpenKey(
                &hPerflibKey,
                KEY_READ,
                &Obja
                );

    if (NT_SUCCESS( Status )) {
        // read value of desired entry

        ValueBufferLength = ResultLength = 1024;
        pValueInformation = ALLOCMEM(ResultLength);

        if (pValueInformation != NULL) {
            while ( (Status = NtQueryValueKey(hPerflibKey,
                                            &ValueNameString,
                                            KeyValuePartialInformation,
                                            pValueInformation,
                                            ValueBufferLength,
                                            &ResultLength))
                    == STATUS_BUFFER_OVERFLOW ) {

                pTemp = pValueInformation;
                pValueInformation = REALLOCMEM(pValueInformation,
                                                        ResultLength);
                if ( pValueInformation == NULL) {
                    FREEMEM(pTemp);
                    Status = STATUS_NO_MEMORY;
                    break;
                } else {
                    ValueBufferLength = ResultLength;
                }
            }

            if (NT_SUCCESS(Status)) {
                // check to see if it's the desired type
                if (pValueInformation->Type == dwRegType) {
                    // see if it will fit
                    if (pValueInformation->DataLength <= dwMaxSize) {
                        memcpy (pReturnBuffer, &pValueInformation->Data[0],
                            pValueInformation->DataLength);
                        bUseDefault = FALSE;
                        lReturn = STATUS_SUCCESS;
                    }
                }
            } else {
                // return the default value
                lReturn = Status;
            }
            // release temp buffer
            if (pValueInformation) {
                FREEMEM (pValueInformation);
            }
        } else {
            // unable to allocate memory for this operation so
            // just return the default value
        }
        // close the registry key
        NtClose(hPerflibKey);
    } else {
        // return default value
    }

    if (bUseDefault) {
        memcpy (pReturnBuffer, pDefault, dwDefaultSize);
        lReturn = STATUS_SUCCESS;
    }

    return lReturn;
}

BOOL
MatchString (
    IN LPCWSTR lpValueArg,
    IN LPCWSTR lpNameArg
)
/*++

MatchString

    return TRUE if lpName is in lpValue.  Otherwise return FALSE

Arguments

    IN lpValue
        string passed to PerfRegQuery Value for processing

    IN lpName
        string for one of the keyword names

Return TRUE | FALSE

--*/
{
    BOOL    bFound      = TRUE; // assume found until contradicted
    LPWSTR  lpValue     = (LPWSTR)lpValueArg;
    LPWSTR  lpName      = (LPWSTR)lpNameArg;

    // check to the length of the shortest string

    while ((*lpValue != 0) && (*lpName != 0)) {
        if (*lpValue++ != *lpName++) {
            bFound = FALSE; // no match
            break;          // bail out now
        }
    }

    return (bFound);
}

DWORD
GetQueryType (
    IN LPWSTR lpValue
)
/*++

GetQueryType

    returns the type of query described in the lpValue string so that
    the appropriate processing method may be used

Arguments

    IN lpValue
        string passed to PerfRegQuery Value for processing

Return Value

    QUERY_GLOBAL
        if lpValue == 0 (null pointer)
           lpValue == pointer to Null string
           lpValue == pointer to "Global" string

    QUERY_FOREIGN
        if lpValue == pointer to "Foriegn" string

    QUERY_COSTLY
        if lpValue == pointer to "Costly" string

    QUERY_COUNTER
        if lpValue == pointer to "Counter" string

    QUERY_HELP
        if lpValue == pointer to "Explain" string

    QUERY_ADDCOUNTER
        if lpValue == pointer to "Addcounter" string

    QUERY_ADDHELP
        if lpValue == pointer to "Addexplain" string

    otherwise:

    QUERY_ITEMS

--*/
{
    WCHAR   LocalBuff[MAX_KEYWORD_LEN+1];
    WORD    i;

    if (lpValue == 0 || *lpValue == 0)
        return QUERY_GLOBAL;

    // convert the input string to Upper case before matching
    for (i=0; i < MAX_KEYWORD_LEN; i++) {
        if (*lpValue == TEXT(' ') || *lpValue == TEXT('\0')) {
            break;
        }
        LocalBuff[i] = *lpValue ;
        if (*lpValue >= TEXT('a') && *lpValue <= TEXT('z')) {
            LocalBuff[i]  = LocalBuff[i] - TEXT('a') + TEXT('A');
        }
        lpValue++ ;
    }
    LocalBuff[i] = TEXT('\0');

    // check for "Global" request
    if (MatchString (LocalBuff, GLOBAL_STRING))
        return QUERY_GLOBAL ;

    // check for "Foreign" request
    if (MatchString (LocalBuff, FOREIGN_STRING))
        return QUERY_FOREIGN ;

    // check for "Costly" request
    if (MatchString (LocalBuff, COSTLY_STRING))
        return QUERY_COSTLY;

    // check for "Counter" request
    if (MatchString (LocalBuff, COUNTER_STRING))
        return QUERY_COUNTER;

    // check for "Help" request
    if (MatchString (LocalBuff, HELP_STRING))
        return QUERY_HELP;

    if (MatchString (LocalBuff, HELP_STRING2))
        return QUERY_HELP;

    // check for "AddCounter" request
    if (MatchString (LocalBuff, ADDCOUNTER_STRING))
        return QUERY_ADDCOUNTER;

    // check for "AddHelp" request
    if (MatchString (LocalBuff, ADDHELP_STRING))
        return QUERY_ADDHELP;

    // None of the above, then it must be an item list
    return QUERY_ITEMS;

}

DWORD
GetNextNumberFromList (
    IN LPWSTR   szStartChar,
    IN LPWSTR   *szNextChar
)
/*++

 Reads a character string from the szStartChar to the next
 delimiting space character or the end of the string and returns
 the value of the decimal number found. If no valid number is found
 then 0 is returned. The pointer to the next character in the
 string is returned in the szNextChar parameter. If the character
 referenced by this pointer is 0, then the end of the string has
 been reached.

--*/
{
    DWORD   dwThisNumber    = 0;
    WCHAR   *pwcThisChar    = szStartChar;
    WCHAR   wcDelimiter     = L' ';
    BOOL    bValidNumber    = FALSE;

    if (szStartChar != 0) {
        do {
            switch (EvalThisChar (*pwcThisChar, wcDelimiter)) {
                case DIGIT:
                    // if this is the first digit after a delimiter, then
                    // set flags to start computing the new number
                    bValidNumber = TRUE;
                    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 return it
                    //
                    if (bValidNumber || (*pwcThisChar == 0)) {
                        *szNextChar = pwcThisChar;
                        return dwThisNumber;
                    } else {
                        // continue until a non-delimiter char or the
                        // end of the file is found
                    }
                    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++;
        } while (pwcThisChar != NULL);    // always TRUE - avoid W4 warning
        return 0;
    } else {
        *szNextChar = szStartChar;
        return 0;
    }
}

BOOL
IsNumberInUnicodeList (
    IN DWORD   dwNumber,
    IN 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;
    WCHAR   *pwcThisChar;

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

    pwcThisChar = lpwszUnicodeList;
    dwThisNumber = 0;

    while (*pwcThisChar != 0) {
        dwThisNumber = GetNextNumberFromList (
            pwcThisChar, &pwcThisChar);
        if (dwNumber == dwThisNumber) return TRUE;
    }
    // if here, then the number wasn't found
    return FALSE;

}   // IsNumberInUnicodeList

BOOL
MonBuildPerfDataBlock(
    PERF_DATA_BLOCK *pBuffer,
    PVOID *pBufferNext,
    DWORD NumObjectTypes,
    DWORD DefaultObject
)
/*++

    MonBuildPerfDataBlock -     build the PERF_DATA_BLOCK structure

        Inputs:

            pBuffer         -   where the data block should be placed

            pBufferNext     -   where pointer to next byte of data block
                                is to begin; DWORD aligned

            NumObjectTypes  -   number of types of objects being reported

            DefaultObject   -   object to display by default when
                                this system is selected; this is the
                                object type title index
--*/

{
    // Initialize Signature and version ID for this data structure

    pBuffer->Signature[0] = L'P';
    pBuffer->Signature[1] = L'E';
    pBuffer->Signature[2] = L'R';
    pBuffer->Signature[3] = L'F';

    pBuffer->LittleEndian = TRUE;

    pBuffer->Version = PERF_DATA_VERSION;
    pBuffer->Revision = PERF_DATA_REVISION;

    //
    //  The next field will be filled in at the end when the length
    //  of the return data is known
    //

    pBuffer->TotalByteLength = 0;

    pBuffer->NumObjectTypes = NumObjectTypes;
    pBuffer->DefaultObject = DefaultObject;
    GetSystemTime(&pBuffer->SystemTime);
    NtQueryPerformanceCounter(&pBuffer->PerfTime,&pBuffer->PerfFreq);
    GetSystemTimeAsFileTime ((FILETIME *)&pBuffer->PerfTime100nSec.QuadPart);

    if ( ComputerNameLength ) {

        //  There is a Computer name: i.e., the network is installed

        pBuffer->SystemNameLength = ComputerNameLength;
        pBuffer->SystemNameOffset = sizeof(PERF_DATA_BLOCK);
        RtlMoveMemory(&pBuffer[1],
               pComputerName,
               ComputerNameLength);
        *pBufferNext = (PVOID) ((PCHAR) &pBuffer[1] +
                                QWORD_MULTIPLE(ComputerNameLength));
        pBuffer->HeaderLength = (DWORD)((PCHAR) *pBufferNext - (PCHAR) pBuffer);
    } else {

        // Member of Computers Anonymous

        pBuffer->SystemNameLength = 0;
        pBuffer->SystemNameOffset = 0;
        *pBufferNext = &pBuffer[1];
        pBuffer->HeaderLength = sizeof(PERF_DATA_BLOCK);
    }

    return 0;
}

//
// Timer functions
//
DWORD
PerflibTimerFunction (
    LPDWORD dwArg
)
/*++

 PerflibTimerFunction

    Timing thread used to write an event log message if the timer expires.

    This thread runs until the Exit event is set or the wait for the
    Exit event times out.

    While the start event is set, then the timer checks the current events
    to be timed and reports on any that have expired. It then sleeps for
    the duration of the timing interval after which it checks the status
    of the start & exit events to begin the next cycle.

    The timing events are added and deleted from the list only by the
    StartPerflibFunctionTimer and KillPerflibFunctionTimer functions.

 Arguments

    dwArg -- Not Used

--*/
{
    NTSTATUS                NtStatus = STATUS_SUCCESS;
    BOOL                    bKeepTiming = TRUE;
    LPOPEN_PROC_WAIT_INFO   pLocalInfo;
    LPWSTR                  szMessageArray[2];
    LARGE_INTEGER           liWaitTime;

    UNREFERENCED_PARAMETER (dwArg);

//    KdPrint (("\nPERFLIB: Entering Timing Thread: PID: %d, TID: %d", 
//        GetCurrentProcessId(), GetCurrentThreadId()));

    TRACE((WINPERF_DBG_TRACE_INFO),
          (& PerflibGuid,
           __LINE__,
           PERF_TIMERFUNCTION,
           0,
           STATUS_SUCCESS,
           NULL));

    while (bKeepTiming) {
        liWaitTime.QuadPart =
            MakeTimeOutValue((PERFLIB_TIMING_THREAD_TIMEOUT));
        // wait for either the start or exit event flags to be set
        NtStatus = NtWaitForMultipleObjects (
                PL_TIMER_NUM_OBJECTS,
                &hTimerHandles[0],
                WaitAny,          //wait for either one to be set
                FALSE,            // not alertable
                &liWaitTime);

        if ((NtStatus != STATUS_TIMEOUT) &&
            (NtStatus <= STATUS_WAIT_3)) {
            if ((NtStatus - STATUS_WAIT_0) == PL_TIMER_EXIT_EVENT ) {
//              KdPrint (("\nPERFLIB: Timing Thread received Exit Event (1): PID: %d, TID: %d", 
//                    GetCurrentProcessId(), GetCurrentThreadId()));

                // then that's all
                bKeepTiming = FALSE;
                NtStatus = STATUS_SUCCESS;
                break;
            } else if ((NtStatus - STATUS_WAIT_0) == PL_TIMER_START_EVENT) {
//                KdPrint (("\nPERFLIB: Timing Thread received Start Event: PID: %d, TID: %d", 
//                    GetCurrentProcessId(), GetCurrentThreadId()));
                // then the timer is running so wait the interval period
                // wait on exit event here to prevent hanging
                liWaitTime.QuadPart =
                    MakeTimeOutValue((PERFLIB_TIMER_INTERVAL));
                NtStatus = NtWaitForSingleObject (
                        hTimerHandles[PL_TIMER_EXIT_EVENT],
                        FALSE,
                        &liWaitTime);

                if (NtStatus == STATUS_TIMEOUT) {
                    // then the wait time expired without being told
                    // to terminate the thread so
                    // now evaluate the list of timed events
                    // lock the data mutex
                    DWORD dwTimeOut = 0;

//                    KdPrint (("\nPERFLIB: Timing Thread Evaluating Entries: PID: %d, TID: %d", 
//                        GetCurrentProcessId(), GetCurrentThreadId()));

                    liWaitTime.QuadPart =
                        MakeTimeOutValue((PERFLIB_TIMER_INTERVAL * 2));

                    NtStatus = STATUS_TIMEOUT;
                    while (   NtStatus == STATUS_TIMEOUT
                           && dwTimeOut < PERFLIB_TIMEOUT_COUNT) {
                        NtStatus = NtWaitForSingleObject (
                                hTimerDataMutex,
                                FALSE,
                                & liWaitTime);
                        if (NtStatus == STATUS_TIMEOUT) {
                            dwTimeOut ++;
                            DebugPrint((2, "\nPERFLIB:NtWaitForSingleObject(TimerDataMutex,%d) time out for the %dth time. PID: %d, TID: %d",
                                    liWaitTime, dwTimeOut,
                                    GetCurrentProcessId(),
                                    GetCurrentThreadId()));
                            TRACE((WINPERF_DBG_TRACE_WARNING),
                                  (& PerflibGuid,
                                   __LINE__,
                                   PERF_TIMERFUNCTION,
                                   0,
                                   STATUS_TIMEOUT,
                                   & dwTimeOut, sizeof(dwTimeOut),
                                   NULL));
                        }
                    }

                    if (NtStatus != STATUS_WAIT_0) {
                        // cannot grab hTimerDataMutex, there is no guarantee
                        // that this is the exclusive one to work on
                        // pTimerItemListHead list, so just bail out.
                        //
                        bKeepTiming = FALSE;
                        NtStatus    = STATUS_SUCCESS;
                        TRACE((WINPERF_DBG_TRACE_WARNING),
                              (& PerflibGuid,
                               __LINE__,
                               PERF_TIMERFUNCTION,
                               0,
                               NtStatus,
                               NULL));
                        break;
                    }
                    else {
                        for (pLocalInfo = pTimerItemListHead;
                            pLocalInfo != NULL;
                            pLocalInfo = pLocalInfo->pNext) {

//                              KdPrint (("\nPERFLIB: Timing Thread Entry %d. count %d: PID: %d, TID: %d", 
//                              (DWORD)pLocalInfo, pLocalInfo->dwWaitTime,
//                              GetCurrentProcessId(), GetCurrentThreadId()));

                            if (pLocalInfo->dwWaitTime > 0) {
                                if (pLocalInfo->dwWaitTime == 1) {
                                    // then this is the last interval so log error
                                    // if this DLL hasn't already been disabled

                                    szMessageArray[0] = pLocalInfo->szServiceName;
                                    szMessageArray[1] = pLocalInfo->szLibraryName;

                                    ReportEvent (hEventLog,
                                        EVENTLOG_ERROR_TYPE,  // error type
                                        0,                    // category (not used)
                                        (DWORD)pLocalInfo->dwEventMsg, // event,
                                        NULL,                 // SID (not used),
                                        2,                    // number of strings
                                        0,                    // sizeof raw data
                                        szMessageArray,       // message text array
                                        NULL);                // raw data

                                    if (pLocalInfo->pData != NULL) {
                                        if (lPerflibConfigFlags & PLCF_ENABLE_TIMEOUT_DISABLE) {
                                            if (!(((PEXT_OBJECT)pLocalInfo->pData)->dwFlags & PERF_EO_DISABLED)) {
                                                // then pData is an extensible counter data block
                                                // disable the ext. counter
                                                DisablePerfLibrary ((PEXT_OBJECT)pLocalInfo->pData);
                                            } // end if not already disabled
                                        } // end if disable DLL on Timeouts is enabled
                                    } // data is NULL so skip
                                } 
                                pLocalInfo->dwWaitTime--;
                            }
                        }
                        ReleaseMutex (hTimerDataMutex);
                    }
                } else {
//                  KdPrint (("\nPERFLIB: Timing Thread received Exit Event (2): PID: %d, TID: %d", 
//                     GetCurrentProcessId(), GetCurrentThreadId()));

                    // we've been told to exit so
                    NtStatus = STATUS_SUCCESS;
                    bKeepTiming = FALSE;
                    break;
                }
            } else {
                // some unexpected error was returned
                assert (FALSE);
            }
        } else {
//            KdPrint (("\nPERFLIB: Timing Thread Timed out: PID: %d, TID: %d", 
//                GetCurrentProcessId(), GetCurrentThreadId()));
            // the wait timed out so it's time to go
            NtStatus = STATUS_SUCCESS;
            bKeepTiming = FALSE;
            break;
        }
    }

//    KdPrint (("\nPERFLIB: Leaving Timing Thread: PID: %d, TID: %d", 
//        GetCurrentProcessId(), GetCurrentThreadId()));

    return PerfpDosError(NtStatus);
}

HANDLE
StartPerflibFunctionTimer (
    IN  LPOPEN_PROC_WAIT_INFO pInfo
)
/*++

    Starts a timing event by adding it to the list of timing events.
    If the timer thread is not running, then the is started as well.

    If this is the first event in the list then the Start Event is
    set indicating that the timing thread can begin processing timing
    event(s).

--*/
{
    LONG    Status = ERROR_SUCCESS;
    LPOPEN_PROC_WAIT_INFO   pLocalInfo = NULL;
    DWORD   dwLibNameLen = 0;
    DWORD   dwBufferLength = sizeof (OPEN_PROC_WAIT_INFO);
    LARGE_INTEGER   liWaitTime;
    HANDLE  hReturn = NULL;
    HANDLE  hDataMutex;

    if (pInfo == NULL) {
        // no required argument
        Status = ERROR_INVALID_PARAMETER;
    } else {
        // check on or create sync objects

        // allocate timing events for the timing thread
        if (hTimerHandles[PL_TIMER_START_EVENT] == NULL) {
            // create the event as NOT signaled since we're not ready to start
            hTimerHandles[PL_TIMER_START_EVENT] = CreateEvent (NULL, TRUE, FALSE, NULL);
            if (hTimerHandles[PL_TIMER_START_EVENT] == NULL) {
                Status = GetLastError();
            }
        }

        if (hTimerHandles[PL_TIMER_EXIT_EVENT] == NULL) {
            hTimerHandles[PL_TIMER_EXIT_EVENT] = CreateEvent (NULL, TRUE, FALSE, NULL);
            if (hTimerHandles[PL_TIMER_EXIT_EVENT] == NULL) {
            Status = GetLastError();
            }
        }

        // create data sync mutex if it hasn't already been created
        if (hTimerDataMutex  == NULL) {
            hDataMutex = CreateMutex(NULL, FALSE, NULL);
            if (hDataMutex == NULL) {
                Status = GetLastError();
            }
            else {
                if (InterlockedCompareExchangePointer(& hTimerDataMutex,
                        hDataMutex,
                        NULL) != NULL) {
                    CloseHandle(hDataMutex);
                    hDataMutex = NULL;
                }
                else {
                    hTimerDataMutex = hDataMutex;
                }
            }
        }
    }

    if (Status == ERROR_SUCCESS) {
        // continue creating timer entry
        if (hPerflibTimingThread != NULL) {
    	    // see if the handle is valid (i.e the thread is alive)
            Status = WaitForSingleObject (hPerflibTimingThread, 0);
    	    if (Status == WAIT_OBJECT_0) {
                // the thread has terminated so close the handle
                CloseHandle (hPerflibTimingThread);
    	        hPerflibTimingThread = NULL;
    	        Status = ERROR_SUCCESS;
    	    } else if (Status == WAIT_TIMEOUT) {
		// the thread is still running so continue
		Status = ERROR_SUCCESS;
    	    } else {
		// some other, probably serious, error
		// so pass it on through
	    }
        } else {
	        // the thread has never been created yet so continue
        }

        if (hPerflibTimingThread == NULL) {
            // create the timing thread

            assert (pTimerItemListHead == NULL);    // there should be no entries, yet

            // everything is ready for the timer thread

            hPerflibTimingThread = CreateThread (
                NULL, 0,
                (LPTHREAD_START_ROUTINE)PerflibTimerFunction,
                NULL, 0, NULL);

            assert (hPerflibTimingThread != NULL);
            if (hPerflibTimingThread == NULL) {
                Status = GetLastError();
            }
        }

        if (Status == ERROR_SUCCESS) {

            // compute the length of the required buffer;

            dwLibNameLen = (lstrlenW (pInfo->szLibraryName) + 1) * sizeof(WCHAR);
            dwBufferLength += dwLibNameLen;
            dwBufferLength += (lstrlenW (pInfo->szServiceName) + 1) * sizeof(WCHAR);
            dwBufferLength = QWORD_MULTIPLE (dwBufferLength);

            pLocalInfo = ALLOCMEM (dwBufferLength);
            if (pLocalInfo == NULL)
                Status = ERROR_OUTOFMEMORY;
        }
        if ((Status == ERROR_SUCCESS) && (pLocalInfo != NULL)) {

            // copy the arg buffer to the local list

            pLocalInfo->szLibraryName = (LPWSTR)&pLocalInfo[1];
            lstrcpyW (pLocalInfo->szLibraryName, pInfo->szLibraryName);
            pLocalInfo->szServiceName = (LPWSTR)
                ((LPBYTE)pLocalInfo->szLibraryName + dwLibNameLen);
            lstrcpyW (pLocalInfo->szServiceName, pInfo->szServiceName);
            // convert wait time in milliseconds to the number of "loops"
            pLocalInfo->dwWaitTime = pInfo->dwWaitTime / PERFLIB_TIMER_INTERVAL;
            if (pLocalInfo->dwWaitTime  == 0) pLocalInfo->dwWaitTime =1; // have at least 1 loop
            pLocalInfo->dwEventMsg = pInfo->dwEventMsg;
            pLocalInfo->pData = pInfo->pData;

            // wait for access to the data
            if (hTimerDataMutex != NULL) {
                NTSTATUS NtStatus;
                liWaitTime.QuadPart =
                    MakeTimeOutValue((PERFLIB_TIMER_INTERVAL * 2));

                NtStatus = NtWaitForSingleObject (
                    hTimerDataMutex,
                    FALSE,
                    &liWaitTime);
                Status = PerfpDosError(NtStatus);
            } else {
                Status = ERROR_NOT_READY;
            }

            if (Status == WAIT_OBJECT_0) {
//                KdPrint (("\nPERFLIB: Timing Thread Adding Entry: %d (%d) PID: %d, TID: %d", 
//                    (DWORD)pLocalInfo, pLocalInfo->dwWaitTime,
//                    GetCurrentProcessId(), GetCurrentThreadId()));

                // we have access to the data so add this item to the front of the list
                pLocalInfo->pNext = pTimerItemListHead;
                pTimerItemListHead = pLocalInfo;
                ReleaseMutex (hTimerDataMutex);

                if (pLocalInfo->pNext == NULL) {
                    // then the list was empty before this call so start the timer
                    // going
                    SetEvent (hTimerHandles[PL_TIMER_START_EVENT]);
                }

                hReturn = (HANDLE)pLocalInfo;
            } else {
                SetLastError (Status);
            }
        } else {
            // unable to create thread
            SetLastError (Status);
        }
    } else {
        // unable to start timer
        SetLastError (Status);
    }

    return hReturn;
}

DWORD
KillPerflibFunctionTimer (
    IN  HANDLE  hPerflibTimer
)
/*++

    Terminates a timing event by removing it from the list. When the last
    item is removed from the list the Start event is reset so the timing
    thread will wait for either the next start event, exit event or it's
    timeout to expire.

--*/
{
    NTSTATUS Status;
    LPOPEN_PROC_WAIT_INFO   pArg = (LPOPEN_PROC_WAIT_INFO)hPerflibTimer;
    LPOPEN_PROC_WAIT_INFO   pLocalInfo;
    BOOL                    bFound = FALSE;
    LARGE_INTEGER           liWaitTime;
    DWORD   dwReturn = ERROR_SUCCESS;

    if (hTimerDataMutex == NULL) {
        dwReturn = ERROR_NOT_READY;
    } else if (pArg == NULL) {
        dwReturn = ERROR_INVALID_HANDLE;
    } else {
	// so far so good
        // wait for access to the data
        liWaitTime.QuadPart =
            MakeTimeOutValue((PERFLIB_TIMER_INTERVAL * 2));
        Status = NtWaitForSingleObject (
            hTimerDataMutex,
            FALSE,
            &liWaitTime);

        if (Status == STATUS_WAIT_0) {
            // we have access to the list so walk down the list and remove the
            // specified item
            // see if it's the first one in the list

//            KdPrint (("\nPERFLIB: Timing Thread Removing Entry: %d (%d) PID: %d, TID: %d", 
//                (DWORD)pArg, pArg->dwWaitTime,
//                GetCurrentProcessId(), GetCurrentThreadId()));

            if (pArg == pTimerItemListHead) {
                // then remove it
                pTimerItemListHead = pArg->pNext;
                bFound = TRUE;
            } else {
                for (pLocalInfo = pTimerItemListHead;
                    pLocalInfo != NULL;
                    pLocalInfo = pLocalInfo->pNext) {
                    if (pLocalInfo->pNext == pArg) {
                        pLocalInfo->pNext = pArg->pNext;
                        bFound = TRUE;
                        break;
                    }
                }
            }
            assert (bFound);

            if (bFound) {
                // it's out of the list so release the lock
                ReleaseMutex (hTimerDataMutex);

                if (pTimerItemListHead == NULL) {
                    // then the list is empty now so stop timing
                    // going
                    ResetEvent (hTimerHandles[PL_TIMER_START_EVENT]);
                }

                // free memory

                FREEMEM (pArg);
                dwReturn = ERROR_SUCCESS;
            } else {
                dwReturn = ERROR_NOT_FOUND;
            }
        } else {
            dwReturn = ERROR_TIMEOUT;
        }
    }

    return dwReturn;
}

DWORD
DestroyPerflibFunctionTimer (
)
/*++

    Terminates the timing thread and cancels any current timer events.
    NOTE: This routine can be called even if timer thread is not started!

--*/
{
    NTSTATUS    Status   = STATUS_WAIT_0;
    LPOPEN_PROC_WAIT_INFO   pThisItem;
    LPOPEN_PROC_WAIT_INFO   pNextItem;
    LARGE_INTEGER           liWaitTime;
    HANDLE hTemp;

    if (hTimerDataMutex != NULL) {
        DWORD  dwTimeOut = 0;
        LONG   dwStatus  = ERROR_SUCCESS;

        // wait for data mutex
        liWaitTime.QuadPart =
            MakeTimeOutValue((PERFLIB_TIMER_INTERVAL * 5));

        Status = STATUS_TIMEOUT;
        while (Status == STATUS_TIMEOUT && dwTimeOut < PERFLIB_TIMEOUT_COUNT) {
            Status = NtWaitForSingleObject (
                    hTimerDataMutex,
                    FALSE,
                    & liWaitTime);
            if (Status == STATUS_TIMEOUT) {
                if (hPerflibTimingThread != NULL) {
                    // see if the handle is valid (i.e the thread is alive)
                    dwStatus = WaitForSingleObject(hPerflibTimingThread,
                                                   liWaitTime.LowPart);
                    if (dwStatus == WAIT_OBJECT_0) {
                        // the thread has terminated so close the handle
                        Status = STATUS_WAIT_0;
                    }
                }
            }
            if (Status == STATUS_TIMEOUT) {
                dwTimeOut ++;
                DebugPrint((2, "\nPERFLIB:NtWaitForSingleObject(TimerDataMutex,%d) time out for the %dth time in DestroyPErflibFunctionTimer(). PID: %d, TID: %d",
                        liWaitTime, dwTimeOut,
                        GetCurrentProcessId(),
                        GetCurrentThreadId()));
                TRACE((WINPERF_DBG_TRACE_WARNING),
                      (& PerflibGuid,
                       __LINE__,
                       PERF_DESTROYFUNCTIONTIMER,
                       0,
                       STATUS_TIMEOUT,
                       & dwTimeOut, sizeof(dwTimeOut),
                       NULL));
            }
        }

        assert (Status != STATUS_TIMEOUT);
    }

    // free all entries in the list

    if (Status == STATUS_WAIT_0) {
        for (pNextItem = pTimerItemListHead;
            pNextItem != NULL;) {
            pThisItem = pNextItem;
            pNextItem = pThisItem->pNext;
            FREEMEM (pThisItem);
        }
    }
    else {
        TRACE((WINPERF_DBG_TRACE_WARNING),
              (& PerflibGuid,
               __LINE__,
               PERF_DESTROYFUNCTIONTIMER,
               0,
               Status,
               NULL));
    }
    // all items have been freed so clear header
    pTimerItemListHead = NULL;

    // set exit event
    if (hTimerHandles[PL_TIMER_EXIT_EVENT] != NULL) {
        SetEvent (hTimerHandles[PL_TIMER_EXIT_EVENT]);
    }

    if (hPerflibTimingThread != NULL) {
        // wait for thread to terminate
        liWaitTime.QuadPart =
            MakeTimeOutValue((PERFLIB_TIMER_INTERVAL * 5));

        Status = NtWaitForSingleObject (
            hPerflibTimingThread,
            FALSE,
            &liWaitTime);

        assert (Status != STATUS_TIMEOUT);

        hTemp = hPerflibTimingThread;
	    hPerflibTimingThread = NULL;
    	CloseHandle (hTemp);
    }

    if (hTimerDataMutex != NULL) {
        hTemp = hTimerDataMutex;
        hTimerDataMutex = NULL;
        // close handles and leave
    	ReleaseMutex (hTemp);
        CloseHandle (hTemp);
    }

    if (hTimerHandles[PL_TIMER_START_EVENT] != NULL) {
        CloseHandle (hTimerHandles[PL_TIMER_START_EVENT]);
        hTimerHandles[PL_TIMER_START_EVENT] = NULL;
    }

    if (hTimerHandles[PL_TIMER_EXIT_EVENT] != NULL) {
        CloseHandle (hTimerHandles[PL_TIMER_EXIT_EVENT]);
        hTimerHandles[PL_TIMER_EXIT_EVENT] = NULL;
    }

    return ERROR_SUCCESS;
}

LONG
PrivateRegQueryValueExT (
    HKEY    hKey,
    LPVOID  lpValueName,
    LPDWORD lpReserved,
    LPDWORD lpType,
    LPBYTE  lpData,
    LPDWORD lpcbData,
    BOOL    bUnicode
)
/*
    wrapper function to allow RegQueryValues while inside a RegQueryValue

*/
{
    LONG    ReturnStatus;
    NTSTATUS    ntStatus = STATUS_SUCCESS;
	BOOL	bStatus;

    UNICODE_STRING      usLocal = {0,0,NULL};
    PSTR                AnsiValueBuffer;
    ULONG               AnsiValueLength;
    PWSTR               UnicodeValueBuffer;
    ULONG               UnicodeValueLength;
    ULONG               Index;

    PKEY_VALUE_PARTIAL_INFORMATION  pValueInformation;
    LONG                    ValueBufferLength;
    ULONG                   ResultLength;


    UNREFERENCED_PARAMETER (lpReserved);

    if (bUnicode) {
        bStatus = RtlCreateUnicodeString (&usLocal, (LPCWSTR)lpValueName);
    } else {
        bStatus = RtlCreateUnicodeStringFromAsciiz (&usLocal, (LPCSTR)lpValueName);
    }

    if (bStatus) {

        ValueBufferLength =
		ResultLength =
			sizeof(KEY_VALUE_PARTIAL_INFORMATION) + *lpcbData;
        pValueInformation = ALLOCMEM(ResultLength);

        if (pValueInformation != NULL) {
            ntStatus = NtQueryValueKey(
                hKey,
                &usLocal,
                KeyValuePartialInformation,
                pValueInformation,
                ValueBufferLength,
                &ResultLength);

            if ((NT_SUCCESS(ntStatus) || ntStatus == STATUS_BUFFER_OVERFLOW)) {
                // return data
                if (ARGUMENT_PRESENT(lpType)) {
                    *lpType = pValueInformation->Type;
                }

                if (ARGUMENT_PRESENT(lpcbData)) {
                    *lpcbData = pValueInformation->DataLength;
                }

                if (NT_SUCCESS(ntStatus)) {
                    if (ARGUMENT_PRESENT(lpData)) {
                        if (!bUnicode &&
                            (pValueInformation->Type == REG_SZ ||
                            pValueInformation->Type == REG_EXPAND_SZ ||
                            pValueInformation->Type == REG_MULTI_SZ)
                        ) {
                            // then convert the unicode return to an
                            // ANSI string before returning
                            // the local wide buffer used

                            UnicodeValueLength  = ResultLength;
                            UnicodeValueBuffer  = (LPWSTR)&pValueInformation->Data[0];

                            AnsiValueBuffer = (LPSTR)lpData;
                            AnsiValueLength = ARGUMENT_PRESENT( lpcbData )?
                                                     *lpcbData : 0;
                            Index = 0;
                            ntStatus = RtlUnicodeToMultiByteN(
                                AnsiValueBuffer,
                                AnsiValueLength,
                                &Index,
                                UnicodeValueBuffer,
                                UnicodeValueLength);

                            if (NT_SUCCESS( ntStatus ) &&
                                (ARGUMENT_PRESENT( lpcbData ))) {
                                *lpcbData = Index;
                            }
                        } else {
                            if (pValueInformation->DataLength <= *lpcbData) {
                                // copy the buffer to the user's buffer
                                memcpy (lpData, &pValueInformation->Data[0],
                                    pValueInformation->DataLength);
                                ntStatus = STATUS_SUCCESS;
                             } else {
                                 ntStatus = STATUS_BUFFER_OVERFLOW;
                             }
                             *lpcbData = pValueInformation->DataLength;
                        }
                    }
                }
            }

            if (pValueInformation != NULL) {
                // release temp buffer
                FREEMEM (pValueInformation);
            }
        } else {
            // unable to allocate memory for this operation so
            ntStatus = STATUS_NO_MEMORY;
        }

        RtlFreeUnicodeString (&usLocal);
    } else {
		// this is a guess at the most likely cause for the string
		// creation to fail.
		ntStatus = STATUS_NO_MEMORY;
	}

    ReturnStatus = PerfpDosError(ntStatus);

    return ReturnStatus;
}

LONG
GetPerfDllFileInfo (
    LPCWSTR             szFileName,
    PDLL_VALIDATION_DATA  pDllData
)
{
    WCHAR   szFullPath[MAX_PATH*2];
    DWORD   dwStatus = ERROR_FILE_NOT_FOUND;
    DWORD   dwRetValue;
    HANDLE  hFile;
    BOOL    bStatus;
    LARGE_INTEGER   liSize;

    dwRetValue = SearchPathW (
        NULL,
        szFileName,
        NULL,
        sizeof(szFullPath) / sizeof(szFullPath[0]),
        szFullPath,
        NULL);

    if (dwRetValue > 0) {
        //then the file was found so open it.
        hFile = CreateFileW (
            szFullPath,
            GENERIC_READ,
            FILE_SHARE_READ,
            NULL, 
            OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL,
            NULL);

        if (hFile != INVALID_HANDLE_VALUE) {
            // get file creation date/time
            bStatus = GetFileTime (
                hFile,
                &pDllData->CreationDate,
                NULL, NULL);
            if (bStatus)  {
                // get file size
                liSize.LowPart  =  GetFileSize (
                    hFile, (PULONG)&liSize.HighPart);
                if (liSize.LowPart != 0xFFFFFFFF) {
                    pDllData->FileSize = liSize.QuadPart;
                    dwStatus = ERROR_SUCCESS;
                } else {
                    dwStatus = GetLastError();
                }
            } else {
                dwStatus = GetLastError();
            } 

            CloseHandle (hFile);
        } else {
            dwStatus = GetLastError();
        }
    } else {
        dwStatus = GetLastError();
    }

    return dwStatus;
}

DWORD
DisablePerfLibrary (
    PEXT_OBJECT  pObj
)
{
    // continue only if the "Disable" feature is enabled and
    // if this library hasn't already been disabled.
    if ((!(lPerflibConfigFlags & PLCF_NO_DISABLE_DLLS)) &&
        (!(pObj->dwFlags & PERF_EO_DISABLED))) {

        // set the disabled bit in the info
        pObj->dwFlags |= PERF_EO_DISABLED;
        return DisableLibrary(pObj->hPerfKey, pObj->szServiceName);
    }
    return ERROR_SUCCESS;
}


DWORD
DisableLibrary(
    IN HKEY hPerfKey,
    IN LPWSTR szServiceName
    )
{
    //
    // This routine will disable regardless of settings
    //
    DWORD   dwValue, dwSize;
    DWORD   dwFnStatus = ERROR_SUCCESS;
    WORD    wStringIndex = 0;
    LPWSTR  szMessageArray[2];

    // disable perf library entry in the service key
    dwSize = sizeof(dwValue);
    dwValue = 1;
    dwFnStatus = RegSetValueExW (
            hPerfKey,
            DisablePerformanceCounters,
            0L,
            REG_DWORD,
            (LPBYTE)&dwValue,
            dwSize);
        // report error

    if (dwFnStatus == ERROR_SUCCESS) {
        // system disabled
        szMessageArray[wStringIndex++] =
            szServiceName;

        ReportEvent (hEventLog,
            EVENTLOG_ERROR_TYPE,        // error type
            0,                          // category (not used)
            (DWORD)PERFLIB_LIBRARY_DISABLED,              // event,
            NULL,                       // SID (not used),
            wStringIndex,               // number of strings
            0,                          // sizeof raw data
            szMessageArray,             // message text array
            NULL);                      // raw data
    } else {
        // local disable only
        szMessageArray[wStringIndex++] =
            szServiceName;

        ReportEvent (hEventLog,
            EVENTLOG_ERROR_TYPE,        // error type
            0,                          // category (not used)
            (DWORD)PERFLIB_LIBRARY_TEMP_DISABLED,              // event,
            NULL,                       // SID (not used),
            wStringIndex,               // number of strings
            0,                          // sizeof raw data
            szMessageArray,             // message text array
            NULL);                      // raw data
    }
    return ERROR_SUCCESS;
}

DWORD
PerfUpdateErrorCount(
    PEXT_OBJECT pObj,
    DWORD ErrorCount
    )
{
    DWORD Status;
    DWORD dwErrorCount, dwType, dwSize;

    dwErrorCount = 0;
    if (ErrorCount == 0) {  // reset to 0
        RegDeleteValueW(pObj->hPerfKey, cszFailureCount);
        return 0;
    }
    dwSize = sizeof(DWORD);
    dwType = REG_DWORD;
    Status = PrivateRegQueryValueExW(
                pObj->hPerfKey,
                cszFailureCount,
                NULL,
                &dwType,
                (LPBYTE)&dwErrorCount,
                &dwSize);
    if (Status != ERROR_SUCCESS)
        dwErrorCount = 0;

    dwErrorCount += ErrorCount;
    dwSize = sizeof(DWORD);
    Status = RegSetValueExW(
                pObj->hPerfKey,
                cszFailureCount,
                0L,
                REG_DWORD,
                (LPBYTE)&dwErrorCount,
                dwSize);

    if ((dwErrorCount >= pObj->dwErrorLimit) &&
        (pObj->dwErrorLimit != 0)) {
        DisablePerfLibrary(pObj);
    }
    if (dwErrorCount < 100)
        return dwErrorCount;
    ErrorCount = dwErrorCount % 100;
    if (ErrorCount > 10)
        return 0;
    return ErrorCount;
}

DWORD
PerfCheckRegistry(
    IN HKEY hPerfKey,
    IN LPCWSTR szServiceName
    )
{
    DWORD dwType = 0;
    DWORD dwSize = sizeof(DWORD);
    DWORD dwData = 0;
    DWORD status;
    WORD  wStringIndex;
    LPWSTR szMessageArray[2];

    status = PrivateRegQueryValueExA(
                hPerfKey,
                FirstCounter,
                NULL,
                &dwType,
                (LPBYTE)&dwData,
                &dwSize);

    if ((status != ERROR_SUCCESS) || (dwType != REG_DWORD) ||
        (dwData < LAST_BASE_INDEX)) {
        wStringIndex = 0;
        szMessageArray[wStringIndex++] = (LPWSTR) FirstCounter;
        szMessageArray[wStringIndex++] = (LPWSTR) szServiceName;
        ReportEvent(hEventLog,
            EVENTLOG_ERROR_TYPE,
            0,
            (DWORD)PERFLIB_REGVALUE_NOT_FOUND,
            NULL,
            wStringIndex,
            0,
            szMessageArray,
            NULL);
        return FALSE;
    }

    status = PrivateRegQueryValueExA(
                hPerfKey,
                LastCounter,
                NULL,
                &dwType,
                (LPBYTE)&dwData,
                &dwSize);

    if ((status != ERROR_SUCCESS) || (dwType != REG_DWORD) ||
        (dwData <= LAST_BASE_INDEX)) {
        wStringIndex = 0;
        szMessageArray[wStringIndex++] = (LPWSTR) LastCounter;
        szMessageArray[wStringIndex++] = (LPWSTR) szServiceName;
        ReportEvent(hEventLog,
            EVENTLOG_ERROR_TYPE,
            0,
            (DWORD)PERFLIB_REGVALUE_NOT_FOUND,
            NULL,
            wStringIndex,
            0,
            szMessageArray,
            NULL);
        return FALSE;
    }

    status = PrivateRegQueryValueExA(
                hPerfKey,
                FirstHelp,
                NULL,
                &dwType,
                (LPBYTE)&dwData,
                &dwSize);

    if ((status != ERROR_SUCCESS) || (dwType != REG_DWORD) ||
        (dwData < LAST_BASE_INDEX)) {
        wStringIndex = 0;
        szMessageArray[wStringIndex++] = (LPWSTR) FirstHelp;
        szMessageArray[wStringIndex++] = (LPWSTR) szServiceName;
        ReportEvent(hEventLog,
            EVENTLOG_ERROR_TYPE,
            0,
            (DWORD)PERFLIB_REGVALUE_NOT_FOUND,
            NULL,
            wStringIndex,
            0,
            szMessageArray,
            NULL);
        return FALSE;
    }

    status = PrivateRegQueryValueExA(
                hPerfKey,
                LastHelp,
                NULL,
                &dwType,
                (LPBYTE)&dwData,
                &dwSize);

    if ((status != ERROR_SUCCESS) || (dwType != REG_DWORD) ||
        (dwData <= LAST_BASE_INDEX)) {
        wStringIndex = 0;
        szMessageArray[wStringIndex++] = (LPWSTR) LastHelp;
        szMessageArray[wStringIndex++] = (LPWSTR) szServiceName;
        ReportEvent(hEventLog,
            EVENTLOG_ERROR_TYPE,
            0,
            (DWORD)PERFLIB_REGVALUE_NOT_FOUND,
            NULL,
            wStringIndex,
            0,
            szMessageArray,
            NULL);
        return FALSE;
    }

    return TRUE;
}

DWORD
PerfpDosError(
    IN NTSTATUS Status
    )
// Need to convert NtStatus that we generate to DosError
{
    if (Status == STATUS_SUCCESS)
        return ERROR_SUCCESS;
    if (Status == STATUS_BUFFER_OVERFLOW)
        return ERROR_MORE_DATA;
    if (Status == STATUS_TIMEOUT)
        return WAIT_TIMEOUT;
    if (Status <= STATUS_WAIT_63)
        return (DWORD) Status;
    return RtlNtStatusToDosError(Status);
}

#ifdef DBG
VOID
PerfpDebug(
    ULONG DebugPrintLevel,
    PCCHAR DebugMessage,
    ...
    )

/*++

Routine Description:

    Debug print for all Perflib

Arguments:

    Debug print level between 0 and 3, with 3 being the most verbose.

Return Value:

    None

--*/

{
    va_list ap;

    if ((DebugPrintLevel <= (PerfLibDebug & 0x0000ffff)) ||
        ((1 << (DebugPrintLevel + 15)) & PerfLibDebug)) {
        DbgPrint("%d:Perflib:", GetCurrentThreadId());
    }
    else
        return;

    va_start(ap, DebugMessage);


    if ((DebugPrintLevel <= (PerfLibDebug & 0x0000ffff)) ||
        ((1 << (DebugPrintLevel + 15)) & PerfLibDebug)) {

        _vsnprintf(
            (LPSTR)PerfLibDebugBuffer, DEBUG_BUFFER_LENGTH, DebugMessage, ap);

        DbgPrint((LPSTR)PerfLibDebugBuffer);
    }

    va_end(ap);

}
#endif // DBG
