/*++

Copyright (c) 1996  Microsoft Corporation

Module Name:

    perfsprc.c

Abstract:


Author:

    Bob Watson (a-robw) Aug 95

Revision History:

--*/

#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <wchar.h>
#include <winperf.h>
#include <ntprfctr.h>
#include <perfutil.h>
#include "perfsprc.h"
#include "procmsg.h"

#include "dataheap.h" 


// bit field definitions for collect function flags
#define POS_READ_SYS_PROCESS_DATA       ((DWORD)0x00010000)
#define POS_READ_PROCESS_VM_DATA        ((DWORD)0x00020000)
#define	POS_READ_JOB_OBJECT_DATA		((DWORD)0x00040000)
#define POS_READ_JOB_DETAIL_DATA		((DWORD)0x00080000)
#define POS_READ_HEAP_DATA		        ((DWORD)0x00100000)

#define POS_COLLECT_PROCESS_DATA        ((DWORD)0x00010001)
#define POS_COLLECT_THREAD_DATA         ((DWORD)0x00010003)
#define POS_COLLECT_EXPROCESS_DATA      ((DWORD)0x00030004)
#define POS_COLLECT_IMAGE_DATA          ((DWORD)0x0003000C)
#define POS_COLLECT_LONG_IMAGE_DATA     ((DWORD)0x00030014)
#define POS_COLLECT_THREAD_DETAILS_DATA ((DWORD)0x00030024)
#define POS_COLLECT_JOB_OBJECT_DATA		((DWORD)0x00050040)
#define POS_COLLECT_JOB_DETAIL_DATA		((DWORD)0x000D00C1)
#define POS_COLLECT_HEAP_DATA           ((DWORD)0x00110101) 

#define POS_COLLECT_FUNCTION_MASK       ((DWORD)0x000001FF)

#define POS_COLLECT_GLOBAL_DATA         ((DWORD)0x001501C3)
#define POS_COLLECT_GLOBAL_NO_HEAP      ((DWORD)0x000500C3)
#define POS_COLLECT_FOREIGN_DATA        ((DWORD)0)
#define POS_COLLECT_COSTLY_DATA         ((DWORD)0x0003003C)

// global variables to this DLL

HANDLE  ThisDLLHandle = NULL;
HANDLE  hEventLog     = NULL;
LPWSTR  wszTotal = NULL;
HANDLE  hLibHeap = NULL;

LPBYTE  pProcessBuffer = NULL;
PPROCESS_VA_INFO     pProcessVaInfo = NULL;
PUNICODE_STRING pusLocalProcessNameBuffer = NULL;

LARGE_INTEGER PerfTime = {0,0};

const WCHAR IDLE_PROCESS[] = L"Idle";
const WCHAR SYSTEM_PROCESS[] = L"System";

const WCHAR szPerflibSubKey[] = L"\\Registry\\Machine\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib";
const WCHAR szPerfProcSubKey[] = L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Services\\PerfProc\\Performance";

const WCHAR szDisplayHeapPerfObject[] = L"DisplayHeapPerfObject";
const WCHAR szProcessNameFormat[] = L"ProcessNameFormat";
const WCHAR szThreadNameFormat[] = L"ThreadNameFormat";

BOOL    PerfSprc_DisplayHeapPerfObject = FALSE;
DWORD   PerfSprc_dwProcessNameFormat = NAME_FORMAT_DEFAULT;
DWORD   PerfSprc_dwThreadNameFormat = NAME_FORMAT_DEFAULT;

extern DWORD bOpenJobErrorLogged;

//
//  Value to decide if process names should be collected from:
//      the SystemProcessInfo structure (fastest)
//          -- or --
//      the process's image file (slower, but shows Unicode filenames)
//
LONG    lProcessNameCollectionMethod = PNCM_NOT_DEFINED;

// variables local to this module

static POS_FUNCTION_INFO    posDataFuncInfo[] = {
    {PROCESS_OBJECT_TITLE_INDEX,    POS_COLLECT_PROCESS_DATA,   0,  CollectProcessObjectData},
    {THREAD_OBJECT_TITLE_INDEX,     POS_COLLECT_THREAD_DATA,    0,  CollectThreadObjectData},
    {EXPROCESS_OBJECT_TITLE_INDEX,  POS_COLLECT_EXPROCESS_DATA, 0,  CollectExProcessObjectData},
    {IMAGE_OBJECT_TITLE_INDEX,      POS_COLLECT_IMAGE_DATA,     0,  CollectImageObjectData},
    {LONG_IMAGE_OBJECT_TITLE_INDEX, POS_COLLECT_LONG_IMAGE_DATA,0,  CollectLongImageObjectData},
    {THREAD_DETAILS_OBJECT_TITLE_INDEX, POS_COLLECT_THREAD_DETAILS_DATA, 0, CollectThreadDetailsObjectData},
	{JOB_OBJECT_TITLE_INDEX,		POS_COLLECT_JOB_OBJECT_DATA, 0,	CollectJobObjectData},
	{JOB_DETAILS_OBJECT_TITLE_INDEX, POS_COLLECT_JOB_DETAIL_DATA, 0, CollectJobDetailData},
    {HEAP_OBJECT_TITLE_INDEX,       POS_COLLECT_HEAP_DATA,      0,  CollectHeapObjectData}
};

#define POS_NUM_FUNCS   (sizeof(posDataFuncInfo) / sizeof(posDataFuncInfo[1]))

static  BOOL    bInitOk  = FALSE;
static  DWORD   dwOpenCount = 0;
static  DWORD   ProcessBufSize = LARGE_BUFFER_SIZE;

PM_OPEN_PROC    OpenSysProcessObject;
PM_COLLECT_PROC CollecSysProcessObjectData;
PM_CLOSE_PROC   CloseSysProcessObject;

__inline
VOID
PerfpQuerySystemTime(
    IN PLARGE_INTEGER SystemTime
    )
{
    do {
        SystemTime->HighPart = USER_SHARED_DATA->SystemTime.High1Time;
        SystemTime->LowPart = USER_SHARED_DATA->SystemTime.LowPart;
    } while (SystemTime->HighPart != USER_SHARED_DATA->SystemTime.High2Time);

}


BOOL
GetProcessExeName(
    HANDLE  hProcessID,
    PUNICODE_STRING pusName
)
{
    HANDLE                  hProcess;
    OBJECT_ATTRIBUTES       obProcess;
    CLIENT_ID               ClientId;
    PROCESS_BASIC_INFORMATION BasicInfo;
    NTSTATUS Status;
    PPEB Peb;
    PPEB_LDR_DATA Ldr;
    PLIST_ENTRY LdrHead;
    PLIST_ENTRY LdrNext;
    PLDR_DATA_TABLE_ENTRY   LdrEntry;
    LDR_DATA_TABLE_ENTRY    LdrEntryData;
    BOOL                    bReturn;
    WCHAR                   wszDllName[MAX_PATH];

    // open process for reading
    // get handle to process

    ClientId.UniqueThread = (HANDLE)NULL;
    ClientId.UniqueProcess = hProcessID;

    InitializeObjectAttributes(
        &obProcess,
        NULL,
        0,
        NULL,
        NULL
        );

    Status = NtOpenProcess(
        &hProcess,
        (ACCESS_MASK)PROCESS_ALL_ACCESS,
        &obProcess,
        &ClientId);

    if (! NT_SUCCESS(Status)){
        // unable to open the process,
        return FALSE;
    }

    // Get the process information

    Status = NtQueryInformationProcess(
                hProcess,
                ProcessBasicInformation,
                &BasicInfo,
                sizeof(BasicInfo),
                NULL
                );

    if ( !NT_SUCCESS(Status) ) {
        SetLastError( RtlNtStatusToDosError( Status ) );
        bReturn = FALSE;
    } else {
        Peb = BasicInfo.PebBaseAddress;

        //
        // get the loader information block
        //
        // Ldr = Peb->Ldr
        //

        if (!ReadProcessMemory(hProcess, &Peb->Ldr, &Ldr, sizeof(Ldr), NULL)) {
            // unable to read loader information
            bReturn = FALSE;
        } else {
            LdrHead = &Ldr->InMemoryOrderModuleList;

            //
            //  get the first memory block listed. this is the .EXE in NT
            //
            if (!ReadProcessMemory(hProcess, &LdrHead->Flink, &LdrNext, sizeof(LdrNext), NULL)) {
                // unable to read memory link
                bReturn = FALSE;
            } else {
                LdrEntry = CONTAINING_RECORD(LdrNext, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);

                if (!ReadProcessMemory(hProcess, LdrEntry, &LdrEntryData, sizeof(LdrEntryData), NULL)) {
                    // unable to read image header
                    bReturn = FALSE;
                } else {
                    if (!ReadProcessMemory(hProcess,
                        LdrEntryData.BaseDllName.Buffer,
                        (LPVOID)&wszDllName[0],
                        sizeof(wszDllName), NULL)) {
                        // unable to read DLL buffer
                        bReturn = FALSE;
                    } else {
                        // copy the short name to the caller's buffer
                        RtlInitUnicodeString (
                            pusName,
                            wszDllName);
                        SetLastError(ERROR_SUCCESS);
                    }
                }
            }
        }
        NtClose (hProcess);
    }

    return TRUE;
}

LONG
GetProcessNameColMeth (
    VOID
)
{
    NTSTATUS            Status;
    HANDLE              hPerflibKey;
    OBJECT_ATTRIBUTES   oaPerflibKey;
    UNICODE_STRING      PerflibSubKeyString;
    UNICODE_STRING      NameInfoValueString;
    LONG                lReturn = PNCM_SYSTEM_INFO;
    PKEY_VALUE_PARTIAL_INFORMATION    pKeyInfo;
    DWORD               dwBufLen;
    DWORD               dwRetBufLen;
    PDWORD              pdwValue;

    RtlInitUnicodeString (
        &PerflibSubKeyString,
        szPerflibSubKey);

    InitializeObjectAttributes(
            &oaPerflibKey,
            &PerflibSubKeyString,
            OBJ_CASE_INSENSITIVE,
            NULL,
            NULL
            );

    Status = NtOpenKey(
                &hPerflibKey,
                MAXIMUM_ALLOWED,
                &oaPerflibKey
                );

    if (NT_SUCCESS (Status)) {
        // registry key opened, now read value.
        // allocate enough room for the structure, - the last
        // UCHAR in the struct, but + the data buffer (a dword)

        dwBufLen = sizeof(KEY_VALUE_PARTIAL_INFORMATION) -
            sizeof(UCHAR) + sizeof (DWORD);

        pKeyInfo = (PKEY_VALUE_PARTIAL_INFORMATION)ALLOCMEM (
            hLibHeap,
            HEAP_ZERO_MEMORY,
            dwBufLen);

        if (pKeyInfo != NULL) {
            // initialize value name string
            RtlInitUnicodeString (
                &NameInfoValueString,
                (LPCWSTR)L"CollectUnicodeProcessNames");

            dwRetBufLen = 0;
            Status = NtQueryValueKey (
                hPerflibKey,
                &NameInfoValueString,
                KeyValuePartialInformation,
                (PVOID)pKeyInfo,
                dwBufLen,
                &dwRetBufLen);

            if (NT_SUCCESS(Status)) {
                // check value of return data buffer
                pdwValue = (PDWORD)&pKeyInfo->Data[0];
                if (*pdwValue == PNCM_MODULE_FILE) {
                    lReturn = PNCM_MODULE_FILE;
                } else {
                    // all other values will cause this routine to return
                    // the default value of PNCM_SYSTEM_INFO;
                }
            }

            FREEMEM (hLibHeap, 0, pKeyInfo);
        }
        // close handle
        NtClose (hPerflibKey);
    }

    return lReturn;
}

VOID
PerfProcGlobalSettings (
    VOID
)
{
    NTSTATUS            Status;
    HANDLE              hPerfProcKey;
    OBJECT_ATTRIBUTES   oaPerfProcKey;
    UNICODE_STRING      PerfProcSubKeyString;
    UNICODE_STRING      NameInfoValueString;
    PKEY_VALUE_PARTIAL_INFORMATION    pKeyInfo;
    DWORD               dwBufLen;
    DWORD               dwRetBufLen;
    PDWORD              pdwValue;

    PerfpQuerySystemTime(&PerfTime);

    RtlInitUnicodeString (
        &PerfProcSubKeyString,
        szPerfProcSubKey);

    InitializeObjectAttributes(
            &oaPerfProcKey,
            &PerfProcSubKeyString,
            OBJ_CASE_INSENSITIVE,
            NULL,
            NULL
            );

    Status = NtOpenKey(
                &hPerfProcKey,
                MAXIMUM_ALLOWED,
                &oaPerfProcKey
                );

    if (NT_SUCCESS (Status)) {
        // registry key opened, now read value.
        // allocate enough room for the structure, - the last
        // UCHAR in the struct, but + the data buffer (a dword)

        dwBufLen = sizeof(KEY_VALUE_PARTIAL_INFORMATION) -
            sizeof(UCHAR) + sizeof (DWORD);

        pKeyInfo = (PKEY_VALUE_PARTIAL_INFORMATION)ALLOCMEM (
            hLibHeap,
            HEAP_ZERO_MEMORY,
            dwBufLen);

        if (pKeyInfo != NULL) {
            // initialize value name string
            RtlInitUnicodeString (
                &NameInfoValueString,
                szDisplayHeapPerfObject);

            dwRetBufLen = 0;
            Status = NtQueryValueKey (
                hPerfProcKey,
                &NameInfoValueString,
                KeyValuePartialInformation,
                (PVOID)pKeyInfo,
                dwBufLen,
                &dwRetBufLen);

            if (NT_SUCCESS(Status)) {
                // check value of return data buffer
                pdwValue = (PDWORD)&pKeyInfo->Data[0];
                if (*pdwValue == 1) {
                    PerfSprc_DisplayHeapPerfObject = TRUE;
                } else {
                    // all other values will cause this routine to return
                    // the default value of FALSE 
                }
            }

            RtlInitUnicodeString(
                &NameInfoValueString,
                szProcessNameFormat);
            dwRetBufLen = 0;
            Status = NtQueryValueKey(
                hPerfProcKey,
                &NameInfoValueString,
                KeyValuePartialInformation,
                (PVOID)pKeyInfo,
                dwBufLen,
                &dwRetBufLen);

            if (NT_SUCCESS(Status)) {
                pdwValue = (PDWORD) &pKeyInfo->Data[0];
                PerfSprc_dwProcessNameFormat = *pdwValue;
            }

            RtlInitUnicodeString(
                &NameInfoValueString,
                szThreadNameFormat);
            dwRetBufLen = 0;
            Status = NtQueryValueKey(
                hPerfProcKey,
                &NameInfoValueString,
                KeyValuePartialInformation,
                (PVOID)pKeyInfo,
                dwBufLen,
                &dwRetBufLen);

            if (NT_SUCCESS(Status)) {
                pdwValue = (PDWORD) &pKeyInfo->Data[0];
                PerfSprc_dwThreadNameFormat = *pdwValue;
            }

            FREEMEM (hLibHeap, 0, pKeyInfo);
        }
        // close handle
        NtClose (hPerfProcKey);
    }
    if ((PerfSprc_dwProcessNameFormat < NAME_FORMAT_BLANK) ||
        (PerfSprc_dwProcessNameFormat > NAME_FORMAT_ID))
        PerfSprc_dwProcessNameFormat = NAME_FORMAT_DEFAULT;
    if ((PerfSprc_dwThreadNameFormat < NAME_FORMAT_BLANK) ||
        (PerfSprc_dwThreadNameFormat > NAME_FORMAT_ID))
        PerfSprc_dwThreadNameFormat = NAME_FORMAT_DEFAULT;
}

BOOL
DllProcessAttach (
    IN  HANDLE DllHandle
)
/*++

Description:

    perform any initialization function that apply to all object
    modules

--*/
{
    BOOL    bReturn = TRUE;
    WCHAR   wszTempBuffer[512];
    LONG    lStatus;
    DWORD   dwBufferSize;

    UNREFERENCED_PARAMETER (DllHandle);

    // open handle to the event log
    if (hEventLog == NULL) {
        hEventLog = MonOpenEventLog((LPWSTR)L"PerfProc");

        // create the local heap
        hLibHeap = HeapCreate (0, 1, 0);

        if (hLibHeap == NULL) {
            return FALSE;
        }

        if (lProcessNameCollectionMethod == PNCM_NOT_DEFINED) {
            // get desired process name collection method as defined in the
            // registry
            lProcessNameCollectionMethod = GetProcessNameColMeth ();
        }
    }

    lStatus = GetPerflibKeyValue (
        szTotalValue,
        REG_SZ,
        sizeof(wszTempBuffer),
        (LPVOID)&wszTempBuffer[0],
        DEFAULT_TOTAL_STRING_LEN,
        (LPVOID)&szDefaultTotalString[0]);

    if (lStatus == ERROR_SUCCESS) {
        // then a string was returned in the temp buffer
        dwBufferSize = lstrlenW (wszTempBuffer) + 1;
        dwBufferSize *= sizeof (WCHAR);
        wszTotal = ALLOCMEM (hLibHeap, HEAP_ZERO_MEMORY, dwBufferSize);
        if (wszTotal == NULL) {
            // unable to allocate buffer so use static buffer
            wszTotal = (LPWSTR)&szDefaultTotalString[0];
        } else {
            memcpy (wszTotal, wszTempBuffer, dwBufferSize);
        }
    } else {
        // unable to get string from registry so just use static buffer
        wszTotal = (LPWSTR)&szDefaultTotalString[0];
    }

    return bReturn;
}

BOOL
DllProcessDetach (
    IN  HANDLE DllHandle
)
{
    UNREFERENCED_PARAMETER (DllHandle);

    if (dwOpenCount > 0) {
        // the Library is being unloaded before it was
        // closed so close it now as this is the last
        // chance to do it before the library is tossed.
        // if the value of dwOpenCount is > 1, set it to
        // one to insure everything will be closed when
        // the close function is called.
        if (dwOpenCount > 1) dwOpenCount = 1;
        CloseSysProcessObject();
    }

    if ((wszTotal != NULL) && (wszTotal != &szDefaultTotalString[0])) {
        FREEMEM (hLibHeap, 0, wszTotal);
        wszTotal = NULL;
    }

    if (HeapDestroy (hLibHeap)) hLibHeap = NULL;

    if (hEventLog != NULL) {
        MonCloseEventLog ();
    }

    return TRUE;
}

BOOL
__stdcall
DllInit(
    IN HANDLE DLLHandle,
    IN DWORD  Reason,
    IN LPVOID ReservedAndUnused
)
{
    ReservedAndUnused;

    // this will prevent the DLL from getting
    // the DLL_THREAD_* messages
    DisableThreadLibraryCalls (DLLHandle);

    switch(Reason) {
        case DLL_PROCESS_ATTACH:
            return DllProcessAttach (DLLHandle);

        case DLL_PROCESS_DETACH:
            return DllProcessDetach (DLLHandle);

        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        default:
            return TRUE;
    }
}

PUNICODE_STRING
GetProcessSlowName (
    PSYSTEM_PROCESS_INFORMATION pProcess
)
/*++

GetProcessSlowName

Inputs:
    PSYSTEM_PROCESS_INFORMATION pProcess

    address of System Process Information data structure.

Outputs:

    None

Returns:

    Pointer to an initialized Unicode string (created by this routine)
    that contains the short name of the process image or a numeric ID
    if no name is found.

    If unable to allocate memory for structure, then NULL is returned.

--*/
{
    PWCHAR  pPeriod;
    PWCHAR  pThisChar;

    WORD    wStringSize;

    WORD    wThisChar;
    WORD    wLength;

    // this routine assumes that the allocated memory has been zero'd

    if (pusLocalProcessNameBuffer == NULL) {
        // allocate Unicode String Structure and adjacent buffer  first
        wLength = MAX_INSTANCE_NAME * sizeof(WCHAR);
        if (pProcess->ImageName.Length > 0) {
            if (wLength < pProcess->ImageName.Length) {
                wLength = pProcess->ImageName.Length;
            }
        }
        wStringSize = sizeof(UNICODE_STRING) + wLength + 64 + (WORD) sizeof(UNICODE_NULL);

        pusLocalProcessNameBuffer =
            ALLOCMEM (hLibHeap,
            HEAP_ZERO_MEMORY, // this will only 0 the buffer the first time!
            (DWORD)wStringSize);
        if (pusLocalProcessNameBuffer == NULL) {
            return NULL;
        } else {
            pusLocalProcessNameBuffer->MaximumLength = (WORD)(wStringSize - (WORD)(sizeof (UNICODE_STRING)));
        }
    }
    else {
        wStringSize = pusLocalProcessNameBuffer->MaximumLength;
    }
    pusLocalProcessNameBuffer->Length = 0;
    pusLocalProcessNameBuffer->Buffer = (PWCHAR)&pusLocalProcessNameBuffer[1];

    memset (     // buffer must be zero'd so we'll have a NULL Term
        pusLocalProcessNameBuffer->Buffer, 0,
        (DWORD)pusLocalProcessNameBuffer->MaximumLength);

    // get the process name from the image file

    GetProcessExeName (pProcess->UniqueProcessId, pusLocalProcessNameBuffer);

    if (pusLocalProcessNameBuffer->Length > 0) {   // some name has been defined

        pPeriod = (PWCHAR)pusLocalProcessNameBuffer->Buffer;
        pThisChar = (PWCHAR)pusLocalProcessNameBuffer->Buffer;
        wThisChar = 0;

        //
        //  go from beginning to end and find last backslash and
        //  last period in name
        //

        while (*pThisChar != 0) { // go until null
            if (*pThisChar == L'.') {
                pPeriod = pThisChar;
            }
            pThisChar++;    // point to next char
            wThisChar += sizeof(WCHAR);
            if (wThisChar >= pusLocalProcessNameBuffer->Length) {
                break;
            }
        }

        // if pPeriod is still pointing to the beginning of the
        // string, then no period was found

        if (pPeriod == (PWCHAR)pusLocalProcessNameBuffer->Buffer) {
            pPeriod = pThisChar; // set to end of string;
        } else {
            // if a period was found, then see if the extension is
            // .EXE, if so leave it, if not, then use end of string
            // (i.e. include extension in name)

            if (lstrcmpiW(pPeriod, (LPCWSTR)L".EXE") != 0) {
                pPeriod = pThisChar;
            }
        }

        // copy characters between period (or end of string) and
        // slash (or start of string) to make image name

        wStringSize = (WORD)((PCHAR)pPeriod - (PCHAR)pusLocalProcessNameBuffer->Buffer);
        wLength = pusLocalProcessNameBuffer->MaximumLength - sizeof(UNICODE_NULL);
        if (wStringSize >= wLength) {
            wStringSize = wLength;
        }
        *pPeriod = 0; // null terminate buffer
        if ((PerfSprc_dwProcessNameFormat == NAME_FORMAT_ID) &&
            (wStringSize < (wLength - 10))) {
            ULONG Length;
            Length = PerfIntegerToWString(
                        HandleToUlong(pProcess->UniqueProcessId),
                        10,
                        (pusLocalProcessNameBuffer->MaximumLength - wStringSize)
                            / sizeof(WCHAR),
                        pPeriod+1);
            if (Length > 0)
                *pPeriod = L'_';
            wStringSize += (WORD) (Length * sizeof(WCHAR));
        }
        pusLocalProcessNameBuffer->Length = wStringSize; // adjust length

    } else {    // no name defined so use Process #

        // check  to see if this is a system process and give it
        // a name

        switch (HandleToUlong(pProcess->UniqueProcessId)) {
            case IDLE_PROCESS_ID:
                RtlAppendUnicodeToString (pusLocalProcessNameBuffer,
                    (LPWSTR)IDLE_PROCESS);
                break;

            case SYSTEM_PROCESS_ID:
                RtlAppendUnicodeToString (pusLocalProcessNameBuffer,
                    (LPWSTR)SYSTEM_PROCESS);
                break;

            // if the id is not a system process, then use the id as the name

            default:
            // try accessing via the "regular" interface
            return (GetProcessShortName (pProcess));

                break;
        }
    }

    return pusLocalProcessNameBuffer;
}

PUNICODE_STRING
GetProcessShortName (
    PSYSTEM_PROCESS_INFORMATION pProcess
)
/*++

GetProcessShortName

Inputs:
    PSYSTEM_PROCESS_INFORMATION pProcess

    address of System Process Information data structure.

Outputs:

    None

Returns:

    Pointer to an initialized Unicode string (created by this routine)
    that contains the short name of the process image or a numeric ID
    if no name is found.

    If unable to allocate memory for structure, then NULL is returned.

--*/
{
    PWCHAR  pSlash;
    PWCHAR  pPeriod;
    PWCHAR  pThisChar;

    WORD    wStringSize;

    WORD    wThisChar;
    ULONG   ProcessId;
    WORD    wLength;

    // this routine assumes that the allocated memory has been zero'd

    if (pusLocalProcessNameBuffer == NULL) {
        // allocate Unicode String Structure and adjacent buffer  first
        wLength = MAX_INSTANCE_NAME * sizeof(WCHAR);
        if (pProcess->ImageName.Length > 0) {
            if (wLength < pProcess->ImageName.Length) {
                wLength = pProcess->ImageName.Length;
            }
        }
        wStringSize = sizeof(UNICODE_STRING) + wLength + 64 + (WORD) sizeof(UNICODE_NULL);

        pusLocalProcessNameBuffer =
            ALLOCMEM (hLibHeap,
            HEAP_ZERO_MEMORY, // this will only 0 the buffer the first time!
            (DWORD)wStringSize);

        if (pusLocalProcessNameBuffer == NULL) {
            return NULL;
        } else {
            pusLocalProcessNameBuffer->MaximumLength = (WORD)(wStringSize - (WORD)sizeof (UNICODE_STRING));
        }
    }
    else {
        wStringSize = pusLocalProcessNameBuffer->MaximumLength;
    }
    pusLocalProcessNameBuffer->Length = 0;
    pusLocalProcessNameBuffer->Buffer = (PWCHAR)&pusLocalProcessNameBuffer[1];

    memset (     // buffer must be zero'd so we'll have a NULL Term
        pusLocalProcessNameBuffer->Buffer, 0,
        (DWORD)pusLocalProcessNameBuffer->MaximumLength);

    ProcessId = HandleToUlong(pProcess->UniqueProcessId);
    if (pProcess->ImageName.Buffer) {   // some name has been defined

        pSlash = (PWCHAR)pProcess->ImageName.Buffer;
        pPeriod = (PWCHAR)pProcess->ImageName.Buffer;
        pThisChar = (PWCHAR)pProcess->ImageName.Buffer;
        wThisChar = 0;

        //
        //  go from beginning to end and find last backslash and
        //  last period in name
        //

        while (*pThisChar != 0) { // go until null
            if (*pThisChar == L'\\') {
                pSlash = pThisChar;
            } else if (*pThisChar == L'.') {
                pPeriod = pThisChar;
            }
            pThisChar++;    // point to next char
            wThisChar += sizeof(WCHAR);
            if (wThisChar >= pProcess->ImageName.Length) {
                break;
            }
        }

        // if pPeriod is still pointing to the beginning of the
        // string, then no period was found

        if (pPeriod == (PWCHAR)pProcess->ImageName.Buffer) {
            pPeriod = pThisChar; // set to end of string;
        } else {
            // if a period was found, then see if the extension is
            // .EXE, if so leave it, if not, then use end of string
            // (i.e. include extension in name)

            if (lstrcmpiW(pPeriod, (LPCWSTR)L".EXE") != 0) {
                pPeriod = pThisChar;
            }
        }

        if (*pSlash == L'\\') { // if pSlash is pointing to a slash, then
            pSlash++;   // point to character next to slash
        }

        // copy characters between period (or end of string) and
        // slash (or start of string) to make image name

        wStringSize = (WORD)((PCHAR)pPeriod - (PCHAR)pSlash); // size in bytes
        wLength = pusLocalProcessNameBuffer->MaximumLength - sizeof(UNICODE_NULL);
        if (wStringSize >= wLength) {
            wStringSize = wLength;
        }

        memcpy (pusLocalProcessNameBuffer->Buffer, pSlash, wStringSize);

        // null terminate is
        // not necessary because allocated memory is zero-init'd
        pPeriod = (PWCHAR) ((PCHAR) pusLocalProcessNameBuffer->Buffer + wStringSize);
        if (PerfSprc_dwProcessNameFormat == NAME_FORMAT_ID) {
            ULONG Length;
            Length = PerfIntegerToWString(
                        ProcessId,
                        10,
                        (pusLocalProcessNameBuffer->MaximumLength - wStringSize)
                            / sizeof(WCHAR),
                        pPeriod+1);
            if (Length > 0)
                *pPeriod = L'_';
            wStringSize += (WORD) (Length * sizeof(WCHAR));
        }
        pusLocalProcessNameBuffer->Length = wStringSize;
    } else {    // no name defined so use Process #

        // check  to see if this is a system process and give it
        // a name

        switch (ProcessId) {
            case IDLE_PROCESS_ID:
                RtlAppendUnicodeToString (pusLocalProcessNameBuffer,
                    (LPWSTR)IDLE_PROCESS);
                break;

            case SYSTEM_PROCESS_ID:
                RtlAppendUnicodeToString (pusLocalProcessNameBuffer,
                    (LPWSTR)SYSTEM_PROCESS);
                break;

            // if the id is not a system process, then use the id as the name

            default:
                RtlIntegerToUnicodeString (ProcessId,
                    10,
                    pusLocalProcessNameBuffer);

                break;
        }


    }

    return pusLocalProcessNameBuffer;
}

#pragma warning (disable : 4706)

DWORD
GetSystemProcessData (
)
{
    DWORD   dwReturnedBufferSize;
    NTSTATUS Status;
    DWORD WinError;
    PVOID pBuffer;

    //
    //  Get process data from system.
    //  if bGotProcessInfo is TRUE, that means we have the process
    //  info. collected earlier when we are checking for costly
    //  object types.
    //
    if (pProcessBuffer == NULL) {
        // allocate a new block
        pProcessBuffer = ALLOCMEM (hLibHeap, HEAP_ZERO_MEMORY,
            ProcessBufSize);
        if (pProcessBuffer == NULL) {
            return ERROR_OUTOFMEMORY;
        }
    }

    PerfpQuerySystemTime(&PerfTime);
    while( (Status = NtQuerySystemInformation(
                            SystemProcessInformation,
                            pProcessBuffer,
                            ProcessBufSize,
                            &dwReturnedBufferSize)) == STATUS_INFO_LENGTH_MISMATCH ) {
        // expand buffer & retry
        ProcessBufSize += INCREMENT_BUFFER_SIZE;

        pBuffer = pProcessBuffer;
        if ( !(pProcessBuffer = REALLOCMEM(hLibHeap, 0,
                                                    pProcessBuffer,
                                                    ProcessBufSize)) ) {
            FREEMEM(hLibHeap, 0, pBuffer);
            return (ERROR_OUTOFMEMORY);
        }
    }

    if ( !NT_SUCCESS(Status) ) {
        // convert to win32 error
        WinError = (DWORD)RtlNtStatusToDosError(Status);
    }
    else {
        WinError = ERROR_SUCCESS;
    }

    return (WinError);

}
#pragma warning (default : 4706)

DWORD APIENTRY
OpenSysProcessObject (
    LPWSTR lpDeviceNames
    )
/*++

Routine Description:

    This routine will initialize the data structures used to pass
    data back to the registry

Arguments:

    Pointer to object ID of each device to be opened (PerfGen)

Return Value:

    None.

--*/
{
    DWORD   status, dwSize;
    HKEY    hKey;
    
    UNREFERENCED_PARAMETER (lpDeviceNames);

    if (dwOpenCount == 0) {
        // clear the job object open error flag
        bOpenJobErrorLogged = FALSE;
        PerfProcGlobalSettings();
    }

    dwOpenCount++;

    bInitOk = TRUE;

    return  ERROR_SUCCESS;
}

DWORD APIENTRY
CollectSysProcessObjectData (
    IN      LPWSTR  lpValueName,
    IN OUT  LPVOID  *lppData,
    IN OUT  LPDWORD lpcbTotalBytes,
    IN OUT  LPDWORD lpNumObjectTypes
)
/*++

Routine Description:

    This routine will return the data for the processor object

Arguments:

   IN       LPWSTR   lpValueName
            pointer to a wide character string passed by registry.

   IN OUT   LPVOID   *lppData
         IN: pointer to the address of the buffer to receive the completed
            PerfDataBlock and subordinate structures. This routine will
            append its data to the buffer starting at the point referenced
            by *lppData.
         OUT: points to the first byte after the data structure added by this
            routine. This routine updated the value at lppdata after appending
            its data.

   IN OUT   LPDWORD  lpcbTotalBytes
         IN: the address of the DWORD that tells the size in bytes of the
            buffer referenced by the lppData argument
         OUT: the number of bytes added by this routine is writted to the
            DWORD pointed to by this argument

   IN OUT   LPDWORD  NumObjectTypes
         IN: the address of the DWORD to receive the number of objects added
            by this routine
         OUT: the number of objects added by this routine is writted to the
            DWORD pointed to by this argument

   Returns:

             0 if successful, else Win 32 error code of failure

--*/
{
    LONG    lReturn = ERROR_SUCCESS;

    NTSTATUS    status;

    // build bit mask of functions to call

    DWORD       dwQueryType;
    DWORD       FunctionCallMask = 0;
    DWORD       FunctionIndex;

    DWORD       dwNumObjectsFromFunction;
    DWORD       dwOrigBuffSize;
    DWORD       dwByteSize;

    if (!bInitOk) {
        ReportEvent (hEventLog,
            EVENTLOG_ERROR_TYPE,
            0,
            PERFPROC_NOT_OPEN,
            NULL,
            0,
            0,
            NULL,
            NULL);
        *lpcbTotalBytes = (DWORD) 0;
        *lpNumObjectTypes = (DWORD) 0;
        lReturn = ERROR_SUCCESS;
        goto COLLECT_BAIL_OUT;
    }

    dwQueryType = GetQueryType (lpValueName);

    switch (dwQueryType) {
        case QUERY_ITEMS:
            for (FunctionIndex = 0; FunctionIndex < POS_NUM_FUNCS; FunctionIndex++) {
                if (IsNumberInUnicodeList (
                    posDataFuncInfo[FunctionIndex].dwObjectId, lpValueName)) {
                    FunctionCallMask |=
                        posDataFuncInfo[FunctionIndex].dwCollectFunctionBit;
                }
            }
            break;

        case QUERY_GLOBAL:
            // only return the HEAP data in a global query if it's enabled
            // if they ask for it specifically, then it's OK
            if (PerfSprc_DisplayHeapPerfObject) {
                FunctionCallMask = POS_COLLECT_GLOBAL_DATA;
            } else {
                // filter out the heap perf object
                FunctionCallMask = POS_COLLECT_GLOBAL_NO_HEAP;
            }
            break;

        case QUERY_FOREIGN:
            FunctionCallMask = POS_COLLECT_FOREIGN_DATA;
            break;

        case QUERY_COSTLY:
            FunctionCallMask = POS_COLLECT_COSTLY_DATA;
            break;

        default:
            FunctionCallMask = POS_COLLECT_COSTLY_DATA;
            break;
    }

    // collect data  from system
    if (FunctionCallMask & POS_READ_SYS_PROCESS_DATA) {
        status = GetSystemProcessData ();

        if (!NT_SUCCESS(status)) {
            ReportEvent (hEventLog,
                EVENTLOG_ERROR_TYPE,
                0,
                PERFPROC_UNABLE_QUERY_PROCESS_INFO,
                NULL,
                0,
                sizeof(DWORD),
                NULL,
                (LPVOID)&status);
        }
    } else {
        status = ERROR_SUCCESS;
    }
    
    // collect data  from system
    if ((status == ERROR_SUCCESS) &&
        (pProcessBuffer != NULL) &&
        (FunctionCallMask & POS_READ_PROCESS_VM_DATA)) {
         pProcessVaInfo = GetSystemVaData (
              (PSYSTEM_PROCESS_INFORMATION)pProcessBuffer);
        // call function

        if (pProcessVaInfo == NULL) {
            ReportEvent (hEventLog,
                EVENTLOG_ERROR_TYPE,
                0,
                PERFPROC_UNABLE_QUERY_VM_INFO,
                NULL,
                0,
                sizeof(DWORD),
                NULL,
                (LPVOID)&status);
            // zero buffer
        }
    } else {
        // zero buffer
    }

    // collect data
    *lpNumObjectTypes = 0;
    dwOrigBuffSize = dwByteSize = *lpcbTotalBytes;
    *lpcbTotalBytes = 0;

    // remove query bits
    FunctionCallMask &= POS_COLLECT_FUNCTION_MASK;

    for (FunctionIndex = 0; FunctionIndex < POS_NUM_FUNCS; FunctionIndex++) {
        if ((posDataFuncInfo[FunctionIndex].dwCollectFunctionBit & FunctionCallMask) == 
            (posDataFuncInfo[FunctionIndex].dwCollectFunctionBit & POS_COLLECT_FUNCTION_MASK)) {
            dwNumObjectsFromFunction = 0;
            lReturn = (*posDataFuncInfo[FunctionIndex].pCollectFunction) (
                lppData,
                &dwByteSize,
                &dwNumObjectsFromFunction);

            if (lReturn == ERROR_SUCCESS) {
                *lpNumObjectTypes += dwNumObjectsFromFunction;
                *lpcbTotalBytes += dwByteSize;
                dwOrigBuffSize -= dwByteSize;
                dwByteSize = dwOrigBuffSize;
            } else {
                break;
            }
        }
    }

    // this list of data must be freed after use
    if (pProcessVaInfo != NULL) {

        FreeSystemVaData (pProcessVaInfo);
        pProcessVaInfo = NULL;

    }


    // *lppData is updated by each function
    // *lpcbTotalBytes is updated after each successful function
    // *lpNumObjects is updated after each successful function

COLLECT_BAIL_OUT:

    return lReturn;
}

DWORD APIENTRY
CloseSysProcessObject (
)
/*++

Routine Description:

    This routine closes the open handles to the Signal Gen counters.

Arguments:

    None.


Return Value:

    ERROR_SUCCESS

--*/

{
    DWORD   status = ERROR_SUCCESS;
    PVOID   buffer;

    if (--dwOpenCount == 0) {
        if (hLibHeap != NULL) {
            // close
            if (pProcessBuffer != NULL) {
                buffer = pProcessBuffer;
                pProcessBuffer = NULL;
                FREEMEM (hLibHeap, 0, buffer);
            }

            if (pusLocalProcessNameBuffer != NULL) {
                buffer = pusLocalProcessNameBuffer;
                pusLocalProcessNameBuffer = NULL;
                FREEMEM (hLibHeap, 0, buffer);
            }
        }
    }
    return  status;

}

const CHAR PerfpIntegerWChars[] = {L'0', L'1', L'2', L'3', L'4', L'5',
                                   L'6', L'7', L'8', L'9', L'A', L'B',
                                   L'C', L'D', L'E', L'F'};

ULONG
PerfIntegerToWString(
    IN ULONG Value,
    IN ULONG Base,
    IN LONG OutputLength,
    OUT LPWSTR String
    )
/*++

Routine Description:

Arguments:

Return Value:

--*/

{
    WCHAR Result[ 33 ], *s;
    ULONG Shift, Mask, Digit, Length;

    Shift = 0;
    switch( Base ) {
        case 16:    Shift = 4;  break;
        case  8:    Shift = 3;  break;
        case  2:    Shift = 1;  break;

        case  0:    Base = 10;
        case 10:    Shift = 0;  break;
        default:    Base = 10; Shift = 0;  // Default to 10
        }

    if (Shift != 0) {
        Mask = 0xF >> (4 - Shift);
        }

    s = &Result[ 32 ];
    *s = L'\0';
    do {
        if (Shift != 0) {
            Digit = Value & Mask;
            Value >>= Shift;
            }
        else {
            Digit = Value % Base;
            Value = Value / Base;
            }

        *--s = PerfpIntegerWChars[ Digit ];
    } while (Value != 0);

    Length = (ULONG) (&Result[ 32 ] - s);
    if (OutputLength < 0) {
        OutputLength = -OutputLength;
        while ((LONG)Length < OutputLength) {
            *--s = L'0';
            Length++;
            }
        }

    if ((LONG)Length > OutputLength) {
        return( 0 );
        }
    else {
        try {
            RtlMoveMemory( String, s, Length*sizeof(WCHAR) );

            if ((LONG)Length < OutputLength) {
                String[ Length ] = L'\0';
                }
            }
        except( EXCEPTION_EXECUTE_HANDLER ) {
            return( 0 );
            }

        return( Length );
        }
}