/*++

Copyright (c) 1992  Microsoft Corporation

Module Name:

    process.c

Abstract:

    This module maintains state about each process/thread created by the application
    setup/install program.

Author:

    Steve Wood (stevewo) 09-Aug-1994

Revision History:

--*/

#include "instaler.h"

BOOLEAN
AddProcess(
    LPDEBUG_EVENT DebugEvent,
    PPROCESS_INFO *ReturnedProcess
    )
{
    NTSTATUS Status;
    PPROCESS_INFO Process;
    RTL_USER_PROCESS_PARAMETERS ProcessParameters;
    PEB Peb;
    PWSTR FreeBuffer, s;

    Process = AllocMem( sizeof( *Process ) );
    if (Process == NULL) {
        return FALSE;
        }

    Process->Id = DebugEvent->dwProcessId;
    Process->Handle = DebugEvent->u.CreateProcessInfo.hProcess;
    InitializeListHead( &Process->ThreadListHead );
    InitializeListHead( &Process->BreakpointListHead );
    InitializeListHead( &Process->OpenHandleListHead );
    InsertTailList( &ProcessListHead, &Process->Entry );
    *ReturnedProcess = Process;

    Status = NtQueryInformationProcess( Process->Handle,
                                        ProcessBasicInformation,
                                        &Process->ProcessInformation,
                                        sizeof( Process->ProcessInformation ),
                                        NULL
                                      );
    FreeBuffer = NULL;
    if (ReadMemory( Process,
                    Process->ProcessInformation.PebBaseAddress,
                    &Peb,
                    sizeof( Peb ),
                    "PEB"
                  ) &&
        Peb.ProcessParameters != NULL &&
        ReadMemory( Process,
                    Peb.ProcessParameters,
                    &ProcessParameters,
                    sizeof( ProcessParameters ),
                    "ProcessParameters"
                  ) &&
        ProcessParameters.ImagePathName.Length != 0 &&
        (FreeBuffer = AllocMem( ProcessParameters.ImagePathName.Length + sizeof( UNICODE_NULL ) )) != NULL &&
        ReadMemory( Process,
                    ProcessParameters.Flags & RTL_USER_PROC_PARAMS_NORMALIZED ?
                        ProcessParameters.ImagePathName.Buffer :
                        (PWSTR)((ULONG)(ProcessParameters.ImagePathName.Buffer) + (PCHAR)(Peb.ProcessParameters)),
                    FreeBuffer,
                    ProcessParameters.ImagePathName.Length,
                    "Image File Name"
                  )
       ) {
        s = (PWSTR)((PCHAR)FreeBuffer + ProcessParameters.ImagePathName.Length);
        while (s > FreeBuffer && s[ -1 ] != OBJ_NAME_PATH_SEPARATOR) {
            s--;
            }

        wcsncpy( Process->ImageFileName,
                 s,
                 (sizeof( Process->ImageFileName ) - sizeof( UNICODE_NULL )) / sizeof( WCHAR )
               );
        //
        // BogdanA 02/28/2002 : make sure we NULL terminate the buffer
        //
        Process->ImageFileName[(sizeof( Process->ImageFileName ) - sizeof( UNICODE_NULL )) / sizeof( WCHAR )] = UNICODE_NULL;
        }
    FreeMem( &FreeBuffer );
    return TRUE;
}

BOOLEAN
DeleteProcess(
    PPROCESS_INFO Process
    )
{
    PLIST_ENTRY Next, Head;
    PTHREAD_INFO Thread;
    PBREAKPOINT_INFO Breakpoint;
    POPENHANDLE_INFO p;

    RemoveEntryList( &Process->Entry );

    Head = &Process->ThreadListHead;
    Next = Head->Flink;
    while (Next != Head) {
        Thread = CONTAINING_RECORD( Next, THREAD_INFO, Entry );
        Next = Next->Flink;
        DeleteThread( Process, Thread );
        }

    Head = &Process->BreakpointListHead;
    Next = Head->Flink;
    while (Next != Head) {
        Breakpoint = CONTAINING_RECORD( Next, BREAKPOINT_INFO, Entry );
        Next = Next->Flink;
        DestroyBreakpoint( Breakpoint->Address, Process, NULL );
        }


    Head = &Process->OpenHandleListHead;
    Next = Head->Flink;
    while (Next != Head) {
        p = CONTAINING_RECORD( Next, OPENHANDLE_INFO, Entry );
        Next = Next->Flink;
        DeleteOpenHandle( Process, p->Handle, p->Type );
        }

    FreeMem( &Process );
    return TRUE;
}


BOOLEAN
AddThread(
    LPDEBUG_EVENT DebugEvent,
    PPROCESS_INFO Process,
    PTHREAD_INFO *ReturnedThread
    )
{
    PTHREAD_INFO Thread;

    Thread = AllocMem( sizeof( *Thread ) );
    if (Thread == NULL) {
        return FALSE;
        }

    Thread->Id = DebugEvent->dwThreadId;
    if (DebugEvent->dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) {
        Thread->Handle = DebugEvent->u.CreateProcessInfo.hThread;
        Thread->StartAddress = DebugEvent->u.CreateProcessInfo.lpStartAddress;
        }
    else {
        Thread->Handle = DebugEvent->u.CreateThread.hThread;
        Thread->StartAddress = DebugEvent->u.CreateThread.lpStartAddress;
        }
    Thread->SingleStepExpected = FALSE;
    InitializeListHead( &Thread->BreakpointListHead );
    InsertTailList( &Process->ThreadListHead, &Thread->Entry );
    *ReturnedThread = Thread;
    return TRUE;
}

BOOLEAN
DeleteThread(
    PPROCESS_INFO Process,
    PTHREAD_INFO Thread
    )
{
    PLIST_ENTRY Next, Head;
    PBREAKPOINT_INFO Breakpoint;

    RemoveEntryList( &Thread->Entry );

    Head = &Thread->BreakpointListHead;
    Next = Head->Flink;
    while (Next != Head) {
        Breakpoint = CONTAINING_RECORD( Next, BREAKPOINT_INFO, Entry );
        Next = Next->Flink;
        DestroyBreakpoint( Breakpoint->Address, Process, Thread );
        }

    FreeMem( &Thread );
    return TRUE;
}


PPROCESS_INFO
FindProcessById(
    ULONG Id
    )
{
    PLIST_ENTRY Next, Head;
    PPROCESS_INFO Process;

    Head = &ProcessListHead;
    Next = Head->Flink;
    while (Next != Head) {
        Process = CONTAINING_RECORD( Next, PROCESS_INFO, Entry );
        if (Process->Id == Id) {
            return Process;
            }

        Next = Next->Flink;
        }

    return NULL;
}

BOOLEAN
FindProcessAndThreadForEvent(
    LPDEBUG_EVENT DebugEvent,
    PPROCESS_INFO *ReturnedProcess,
    PTHREAD_INFO *ReturnedThread
    )
{
    PLIST_ENTRY Next, Head;
    PPROCESS_INFO Process;
    PTHREAD_INFO Thread;

    Head = &ProcessListHead;
    Next = Head->Flink;
    Process = NULL;
    Thread = NULL;
    while (Next != Head) {
        Process = CONTAINING_RECORD( Next, PROCESS_INFO, Entry );
        if (Process->Id == DebugEvent->dwProcessId) {
            Head = &Process->ThreadListHead;
            Next = Head->Flink;
            while (Next != Head) {
                Thread = CONTAINING_RECORD( Next, THREAD_INFO, Entry );
                if (Thread->Id == DebugEvent->dwThreadId) {
                    break;
                    }

                Thread = NULL;
                Next = Next->Flink;
                }

            break;
            }

        Process = NULL;
        Next = Next->Flink;
        }

    *ReturnedProcess = Process;
    *ReturnedThread = Thread;

    if (DebugEvent->dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) {
        if (Process != NULL) {
            DeclareError( INSTALER_DUPLICATE_PROCESS_ID, 0, DebugEvent->dwProcessId );
            return FALSE;
            }
        }
    else
    if (DebugEvent->dwDebugEventCode == CREATE_THREAD_DEBUG_EVENT) {
        if (Thread != NULL) {
            DeclareError( INSTALER_DUPLICATE_THREAD_ID, 0, DebugEvent->dwThreadId, DebugEvent->dwProcessId );
            return FALSE;
            }
        if (Process == NULL) {
            DeclareError( INSTALER_MISSING_PROCESS_ID, 0, DebugEvent->dwProcessId );
            return FALSE;
            }
        }
    else
    if (Process == NULL) {
        DeclareError( INSTALER_MISSING_PROCESS_ID, 0, DebugEvent->dwProcessId );
        return FALSE;
        }
    else
    if (Thread == NULL) {
        DeclareError( INSTALER_MISSING_THREAD_ID, 0, DebugEvent->dwThreadId, DebugEvent->dwProcessId );
        return FALSE;
        }

    return TRUE;
}


VOID
SuspendAllButThisThread(
    PPROCESS_INFO Process,
    PTHREAD_INFO Thread
    )
{
    PTHREAD_INFO Thread1;
    PLIST_ENTRY Next, Head;

    if (Thread != NULL) {
        Head = &Process->ThreadListHead;
        Next = Head->Flink;
        while (Next != Head) {
            Thread1 = CONTAINING_RECORD( Next, THREAD_INFO, Entry );
            if (Thread1 != Thread) {
                NtSuspendThread( Thread1->Handle, NULL );
                }

            Next = Next->Flink;
            }
        }

    return;
}

VOID
ResumeAllButThisThread(
    PPROCESS_INFO Process,
    PTHREAD_INFO Thread
    )
{
    PTHREAD_INFO Thread1;
    PLIST_ENTRY Next, Head;

    if (Thread != NULL) {
        Head = &Process->ThreadListHead;
        Next = Head->Flink;
        while (Next != Head) {
            Thread1 = CONTAINING_RECORD( Next, THREAD_INFO, Entry );
            if (Thread1 != Thread) {
                NtResumeThread( Thread1->Handle, NULL );
                }

            Next = Next->Flink;
            }
        }

    return;
}

PBREAKPOINT_INFO
FindBreakpoint(
    LPVOID Address,
    PPROCESS_INFO Process,
    PTHREAD_INFO Thread
    )
{
    PBREAKPOINT_INFO Breakpoint;
    PLIST_ENTRY Next, Head;

    if (Thread != NULL) {
        Head = &Thread->BreakpointListHead;
        Next = Head->Flink;
        while (Next != Head) {
            Breakpoint = CONTAINING_RECORD( Next, BREAKPOINT_INFO, Entry );
            if (Breakpoint->Address == Address) {
                return Breakpoint;
                }

            Next = Next->Flink;
            }
        }

    Head = &Process->BreakpointListHead;
    Next = Head->Flink;
    while (Next != Head) {
        Breakpoint = CONTAINING_RECORD( Next, BREAKPOINT_INFO, Entry );
        if (Breakpoint->Address == Address) {
            return Breakpoint;
            }

        Next = Next->Flink;
        }

    return NULL;
}

BOOLEAN
CreateBreakpoint(
    LPVOID Address,
    PPROCESS_INFO Process,
    PTHREAD_INFO Thread,
    UCHAR ApiIndex,
    PAPI_SAVED_PARAMETERS SavedParameters,
    PBREAKPOINT_INFO *ReturnedBreakpoint
    )
{
    PBREAKPOINT_INFO Breakpoint;

    Breakpoint = FindBreakpoint( Address, Process, Thread );

    if (ARGUMENT_PRESENT( ReturnedBreakpoint )) {
        *ReturnedBreakpoint = Breakpoint;
        }

    if (Breakpoint != NULL) {
        return (Breakpoint->ApiIndex == ApiIndex);
        }

    Breakpoint = AllocMem( sizeof( *Breakpoint ) );
    if (Breakpoint == NULL) {
        return FALSE;
        }

    Breakpoint->Address = Address;
    Breakpoint->ApiIndex = ApiIndex;
    if (ARGUMENT_PRESENT( SavedParameters )) {
        Breakpoint->SavedParameters = *SavedParameters;
        Breakpoint->SavedParametersValid = TRUE;
        }
    else {
        Breakpoint->SavedParametersValid = FALSE;
        }

    if (Thread != NULL) {
        InsertTailList( &Thread->BreakpointListHead, &Breakpoint->Entry );
        }
    else {
        InsertTailList( &Process->BreakpointListHead, &Breakpoint->Entry );
        }

    InstallBreakpoint( Process, Breakpoint );

    if (ARGUMENT_PRESENT( ReturnedBreakpoint )) {
        *ReturnedBreakpoint = Breakpoint;
        }

    return TRUE;
}


BOOLEAN
DestroyBreakpoint(
    LPVOID Address,
    PPROCESS_INFO Process,
    PTHREAD_INFO Thread
    )
{
    PBREAKPOINT_INFO Breakpoint;

    Breakpoint = FindBreakpoint( Address, Process, Thread );
    if (Breakpoint == NULL) {
        return FALSE;
        }

    RemoveBreakpoint( Process, Breakpoint );

    RemoveEntryList( &Breakpoint->Entry );

    FreeMem( &Breakpoint );
    return TRUE;
}


BOOLEAN
HandleThreadsForSingleStep(
    PPROCESS_INFO Process,
    PTHREAD_INFO ThreadToSingleStep,
    BOOLEAN SuspendThreads
    )
{
    PLIST_ENTRY Next, Head;
    PTHREAD_INFO Thread;

    Head = &Process->ThreadListHead;
    Next = Head->Flink;
    while (Next != Head) {
        Thread = CONTAINING_RECORD( Next, THREAD_INFO, Entry );
        if (Thread != ThreadToSingleStep) {
            if (SuspendThreads) {
                if (Thread->BreakpointToStepOver == NULL) {
                    SuspendThread( Thread->Handle );
                    }
                }
            else {
                ResumeThread( Thread->Handle );
                }

            break;
            }

        Next = Next->Flink;
        }

    return TRUE;
}


BOOLEAN
ReadMemory(
    PPROCESS_INFO Process,
    PVOID Address,
    PVOID DataRead,
    ULONG BytesToRead,
    PCHAR Reason
    )
{
    ULONG BytesRead;

    if (!ReadProcessMemory( Process->Handle,
                            Address,
                            DataRead,
                            BytesToRead,
                            &BytesRead
                          ) ||
        BytesRead != BytesToRead
       ) {
        DbgEvent( MEMORYERROR, ( "Read memory from %x for %x bytes failed (%u) - '%s'\n",
                                 Address,
                                 BytesToRead,
                                 GetLastError(),
                                 Reason
                               )
                );
        return FALSE;
        }
    else {
        return TRUE;
        }
}


BOOLEAN
WriteMemory(
    PPROCESS_INFO Process,
    PVOID Address,
    PVOID DataToWrite,
    ULONG BytesToWrite,
    PCHAR Reason
    )
{
    ULONG BytesWritten;
    ULONG OldProtection;
    BOOLEAN Result;

    if (WriteProcessMemory( Process->Handle,
                            Address,
                            DataToWrite,
                            BytesToWrite,
                            &BytesWritten
                          ) &&
        BytesWritten == BytesToWrite
       ) {
        return TRUE;
        }

    Result = FALSE;
    if (GetLastError() == ERROR_NOACCESS &&
        VirtualProtectEx( Process->Handle,
                          Address,
                          BytesToWrite,
                          PAGE_READWRITE,
                          &OldProtection
                        )
       ) {
        if (WriteProcessMemory( Process->Handle,
                                Address,
                                DataToWrite,
                                BytesToWrite,
                                &BytesWritten
                              ) &&
            BytesWritten == BytesToWrite
           ) {
            Result = TRUE;
            }
        VirtualProtectEx( Process->Handle,
                          Address,
                          BytesToWrite,
                          OldProtection,
                          &OldProtection
                        );
        if (Result) {
            return TRUE;
            }
        }

    DbgEvent( MEMORYERROR, ( "Write memory to %x for %x bytes failed (%u) - '%s'\n",
                             Address,
                             BytesToWrite,
                             GetLastError(),
                             Reason
                           )
            );
    return FALSE;
}


PVOID
AllocMem(
    ULONG Size
    )
{
    PVOID p;

    p = HeapAlloc( AppHeap, HEAP_ZERO_MEMORY, Size );
    if (p == NULL) {
        DbgEvent( INTERNALERROR, ( "HeapAlloc( %0x8 ) failed\n", Size ) );
        }



    return p;
}


VOID
FreeMem(
    PVOID *p
    )
{
    if (*p != NULL) {
        HeapFree( AppHeap, 0, *p );
        *p = NULL;
        }

    return;
}