/*++ 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 #include #include #include #include #include #include #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 // 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