/*++

Copyright (c) Microsoft Corporation. All rights reserved. 

You may only use this code if you agree to the terms of the Windows Research Kernel Source Code License agreement (see License.txt).
If you do not agree to the terms, do not use the code.


Module Name:

    psdelete.c

Abstract:

    This module implements process and thread object termination and
    deletion.

--*/

#include "psp.h"

extern PEPROCESS ExpDefaultErrorPortProcess;


NTSTATUS
PspFreezeProcessWorker (
    PEPROCESS Process,
    PVOID Context
    );

VOID
PspCatchCriticalBreak(
    IN PCHAR Msg,
    IN PVOID  Object,
    IN PUCHAR ImageFileName
    );

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, PsSetBBTNotifyRoutine)
#pragma alloc_text(PAGE, PspTerminateThreadByPointer)
#pragma alloc_text(PAGE, NtTerminateProcess)
#pragma alloc_text(PAGE, PsTerminateProcess)
#pragma alloc_text(PAGE, PspWaitForUsermodeExit)
#pragma alloc_text(PAGE, NtTerminateThread)
#pragma alloc_text(PAGE, PsTerminateSystemThread)
#pragma alloc_text(PAGE, PspNullSpecialApc)
#pragma alloc_text(PAGE, PsExitSpecialApc)
#pragma alloc_text(PAGE, PspExitApcRundown)
#pragma alloc_text(PAGE, PspExitNormalApc)
#pragma alloc_text(PAGE, PspCatchCriticalBreak)
#pragma alloc_text(PAGE, PspExitThread)
#pragma alloc_text(PAGE, PspExitProcess)
#pragma alloc_text(PAGE, PspProcessDelete)
#pragma alloc_text(PAGE, PspThreadDelete)
#pragma alloc_text(PAGE, NtRegisterThreadTerminatePort)
#pragma alloc_text(PAGE, PsGetProcessExitTime)
#pragma alloc_text(PAGE, PsShutdownSystem)
#pragma alloc_text(PAGE, PsWaitForAllProcesses)
#pragma alloc_text(PAGE, PspFreezeProcessWorker)
#pragma alloc_text(PAGE, PspTerminateProcess)
#endif


LARGE_INTEGER ShortTime = {(ULONG)(-10 * 1000 * 100), -1}; // 100 milliseconds


#ifdef ALLOC_DATA_PRAGMA
#pragma data_seg("PAGEDATA")
#endif
PBBT_NOTIFY_ROUTINE PspBBTNotifyRoutine = NULL;

ULONG
PsSetBBTNotifyRoutine(
    __in PBBT_NOTIFY_ROUTINE BBTNotifyRoutine
    )
{
    PAGED_CODE();

    PspBBTNotifyRoutine = BBTNotifyRoutine;

    return FIELD_OFFSET(KTHREAD,BBTData);
}

VOID
PspReaper(
    IN PVOID Context
    )

/*++

Routine Description:

    This routine implements the thread reaper. The reaper is responsible
    for processing terminated threads. This includes:

        - deallocating their kernel stacks

        - releasing their process' CreateDelete lock

        - dereferencing their process

        - dereferencing themselves

Arguments:

    Context - NOT USED

Return Value:

    None.

--*/

{
    PSINGLE_LIST_ENTRY NextEntry;
    PETHREAD Thread;

    UNREFERENCED_PARAMETER (Context);

    //
    // Remove the current list of threads from the reaper list and place a
    // marker in the reaper list head. This marker will prevent another
    // worker thread from being queued until all threads in the reaper list
    // have been processed.
    //

    do {

        NextEntry = InterlockedExchangePointer (&PsReaperListHead.Next,
                                                (PVOID)1);

        ASSERT ((NextEntry != NULL) && (NextEntry != (PVOID)1));
    
        //
        // Delete the respective kernel stack and dereference each thread
        // in the reaper list.
        //
    
        do {
    
            //
            // Wait until context is swapped for this thread.
            //
    
            Thread = CONTAINING_RECORD (NextEntry, ETHREAD, ReaperLink);
            KeWaitForContextSwap (&Thread->Tcb);
            MmDeleteKernelStack (Thread->Tcb.StackBase,
                                 (BOOLEAN)Thread->Tcb.LargeStack);
    
            Thread->Tcb.InitialStack = NULL;
            NextEntry = NextEntry->Next;
            ObDereferenceObject (Thread);
    
        } while ((NextEntry != NULL) && (NextEntry != (PVOID)1));

    } while (InterlockedCompareExchangePointer (&PsReaperListHead.Next,
                                                NULL,
                                                (PVOID)1) != (PVOID)1);

    return;
}

NTSTATUS
PspTerminateThreadByPointer(
    IN PETHREAD Thread,
    IN NTSTATUS ExitStatus,
    IN BOOLEAN DirectTerminate
    )

/*++

Routine Description:

    This function causes the specified thread to terminate.

Arguments:

    ThreadHandle - Supplies a referenced pointer to the thread to terminate.

    ExitStatus - Supplies the exit status associated with the thread.

    DirectTerminate - TRUE is its ok to exit without queing an APC, FALSE otherwise

--*/

{
    NTSTATUS Status;
    PKAPC    ExitApc=NULL;
    ULONG    OldMask;

    PAGED_CODE();

    if (Thread->CrossThreadFlags
    & PS_CROSS_THREAD_FLAGS_BREAK_ON_TERMINATION) {
      PspCatchCriticalBreak("Terminating critical thread 0x%p (in %s)\n",
                Thread,
                THREAD_TO_PROCESS(Thread)->ImageFileName);
    }

    if (DirectTerminate && Thread == PsGetCurrentThread()) {

        ASSERT (KeGetCurrentIrql() < APC_LEVEL);

        PS_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_TERMINATED);

        PspExitThread (ExitStatus);

        //
        // Never Returns
        //

    } else {
        //
        // Cross thread deletion of system threads won't work.
        //
        if (IS_SYSTEM_THREAD (Thread)) {
            return STATUS_ACCESS_DENIED;
        }

        Status = STATUS_SUCCESS;

        while (1) {
            ExitApc = (PKAPC) ExAllocatePoolWithTag (NonPagedPool,
                                                     sizeof(KAPC),
                                                     'xEsP');
            if (ExitApc != NULL) {
                break;
            }
            KeDelayExecutionThread(KernelMode, FALSE, &ShortTime);
        }

        //
        // Mark the thread as terminating and call the exit function.
        //
        OldMask = PS_TEST_SET_BITS (&Thread->CrossThreadFlags, PS_CROSS_THREAD_FLAGS_TERMINATED);

        //
        // If we are the first to set the terminating flag then queue the APC
        //

        if ((OldMask & PS_CROSS_THREAD_FLAGS_TERMINATED) == 0) {

            KeInitializeApc (ExitApc,
                             PsGetKernelThread (Thread),
                             OriginalApcEnvironment,
                             PsExitSpecialApc,
                             PspExitApcRundown,
                             PspExitNormalApc,
                             KernelMode,
                             ULongToPtr (ExitStatus));

            if (!KeInsertQueueApc (ExitApc, ExitApc, NULL, 2)) {
                //
                // If APC queuing is disabled then the thread is exiting anyway
                //
                ExFreePool (ExitApc);
                Status = STATUS_UNSUCCESSFUL;
            } else {
                //
                // We queued the APC to the thread. Wake up the thread if it was suspended.
                //
                KeForceResumeThread (&Thread->Tcb);

            }
        } else {
            ExFreePool (ExitApc);
        }
    }

    return Status;
}

NTSTATUS
NtTerminateProcess(
    __in_opt HANDLE ProcessHandle,
    __in NTSTATUS ExitStatus
    )

/*++

Routine Description:

    This function causes the specified process and all of
    its threads to terminate.

Arguments:

    ProcessHandle - Supplies a handle to the process to terminate.

    ExitStatus - Supplies the exit status associated with the process.

Return Value:

    NTSTATUS - Status of operation

--*/

{

    PETHREAD Thread, Self;
    PEPROCESS Process;
    PEPROCESS CurrentProcess;
    NTSTATUS st;
    BOOLEAN ProcessHandleSpecified;
    PAGED_CODE();

    Self = PsGetCurrentThread();
    CurrentProcess = PsGetCurrentProcessByThread (Self);

    if (ARGUMENT_PRESENT (ProcessHandle)) {
        ProcessHandleSpecified = TRUE;
    } else {
        ProcessHandleSpecified = FALSE;
        ProcessHandle = NtCurrentProcess();
    }

    st = ObReferenceObjectByHandle (ProcessHandle,
                                    PROCESS_TERMINATE,
                                    PsProcessType,
                                    KeGetPreviousModeByThread(&Self->Tcb),
                                    &Process,
                                    NULL);

    if (!NT_SUCCESS (st)) {
        return(st);
    }

    if (Process->Flags & PS_PROCESS_FLAGS_BREAK_ON_TERMINATION) {
        PspCatchCriticalBreak ("Terminating critical process 0x%p (%s)\n",
                               Process,
                               Process->ImageFileName);
    }

    //
    // Acquire rundown protection just so we can give the right errors
    //

    if (!ExAcquireRundownProtection (&Process->RundownProtect)) {
        ObDereferenceObject (Process);
        return STATUS_PROCESS_IS_TERMINATING;
    }

    //
    // Mark process as deleting except for the obscure delete self case.
    //
    if (ProcessHandleSpecified) {
        PS_SET_BITS (&Process->Flags, PS_PROCESS_FLAGS_PROCESS_DELETE);
    }

    st = STATUS_NOTHING_TO_TERMINATE;

    for (Thread = PsGetNextProcessThread (Process, NULL);
         Thread != NULL;
         Thread = PsGetNextProcessThread (Process, Thread)) {

        st = STATUS_SUCCESS;
        if (Thread != Self) {
            PspTerminateThreadByPointer (Thread, ExitStatus, FALSE);
        }
    }

    ExReleaseRundownProtection (&Process->RundownProtect);


    if (Process == CurrentProcess) {
        if (ProcessHandleSpecified) {

            ObDereferenceObject (Process);

            //
            // Never Returns
            //

            PspTerminateThreadByPointer (Self, ExitStatus, TRUE);
        }
    } else if (ExitStatus == DBG_TERMINATE_PROCESS) {
        DbgkClearProcessDebugObject (Process, NULL);
    }

    //
    // If there are no threads in this process then clear out its handle table.
    // Do the same for processes being debugged. This is so a process can never lock itself into the system
    // by debugging itself or have a handle open to itself.
    //
    if (st == STATUS_NOTHING_TO_TERMINATE || (Process->DebugPort != NULL && ProcessHandleSpecified)) {
        ObClearProcessHandleTable (Process);
        st = STATUS_SUCCESS;
    }

    ObDereferenceObject(Process);

    return st;
}

NTSTATUS
PsTerminateProcess(
    PEPROCESS Process,
    NTSTATUS Status
    )
{
    return PspTerminateProcess (Process, Status);
}

NTSTATUS
PspTerminateProcess(
    PEPROCESS Process,
    NTSTATUS ExitStatus
    )

/*++

Routine Description:

    This function causes the specified process and all of
    its threads to terminate.

Arguments:

    ProcessHandle - Supplies a handle to the process to terminate.

    ExitStatus - Supplies the exit status associated with the process.

--*/

{

    PETHREAD Thread;
    NTSTATUS st;

    PAGED_CODE();


    if (Process->Flags
    & PS_PROCESS_FLAGS_BREAK_ON_TERMINATION) {
      PspCatchCriticalBreak("Terminating critical process 0x%p (%s)\n",
                Process,
                Process->ImageFileName);
    }

    //
    // Mark process as deleting
    //
    PS_SET_BITS (&Process->Flags, PS_PROCESS_FLAGS_PROCESS_DELETE);

    st = STATUS_NOTHING_TO_TERMINATE;

    for (Thread = PsGetNextProcessThread (Process, NULL);
         Thread != NULL;
         Thread = PsGetNextProcessThread (Process, Thread)) {

        st = STATUS_SUCCESS;

        PspTerminateThreadByPointer (Thread, ExitStatus, FALSE);

    }

    //
    // If there are no threads in this process then clear out its handle table.
    // Do the same for processes being debugged. This is so a process can never lock itself into the system
    // by debugging itself or have a handle open to itself.
    //
    if (st == STATUS_NOTHING_TO_TERMINATE || Process->DebugPort != NULL) {
        ObClearProcessHandleTable (Process);
        st = STATUS_SUCCESS;
    }
    return st;
}


NTSTATUS
PspWaitForUsermodeExit(
    IN PEPROCESS         Process
    )

/*++

Routine Description:

    This function waits for a process's usermode threads to terminate.

Arguments:

    Process - Supplies a pointer to the process to wait for

    WaitMode - Supplies the mode to wait in

    LockMode - Supplies the way to wait for the process lock

Return Value:

    NTSTATUS - Status of call

--*/
{
    BOOLEAN     GotAThread;
    PETHREAD    Thread;

    do {
        GotAThread = FALSE;

        for (Thread = PsGetNextProcessThread (Process, NULL);
             Thread != NULL;
             Thread = PsGetNextProcessThread (Process, Thread)) {

            if (!IS_SYSTEM_THREAD (Thread) && !KeReadStateThread (&Thread->Tcb)) {
                ObReferenceObject (Thread);
                PsQuitNextProcessThread (Thread);
                GotAThread = TRUE;
                break;
            }
        }


        if (GotAThread) {
            KeWaitForSingleObject (Thread,
                                   Executive,
                                   KernelMode,
                                   FALSE,
                                   NULL);
            ObDereferenceObject (Thread);
        }
    } while (GotAThread);

    return STATUS_SUCCESS;
}

NTSTATUS
NtTerminateThread(
    __in_opt HANDLE ThreadHandle,
    __in NTSTATUS ExitStatus
    )

/*++

Routine Description:

    This function causes the specified thread to terminate.

Arguments:

    ThreadHandle - Supplies a handle to the thread to terminate.

    ExitStatus - Supplies the exit status associated with the thread.

--*/

{

    PETHREAD Thread=NULL, ThisThread;
    PEPROCESS ThisProcess;
    NTSTATUS Status = STATUS_SUCCESS;
    BOOLEAN Self = TRUE;

    PAGED_CODE();

    ThisThread = PsGetCurrentThread ();

    if (!ARGUMENT_PRESENT (ThreadHandle)) {
        //
        // This is part of the strange linkage between base\win32 and the kernel.
        // This routine gets called this way first and if it returns the base
        // code does an exit process call.
        //
        ThisProcess = PsGetCurrentProcessByThread (ThisThread);

        if (ThisProcess->ActiveThreads == 1) {
            return STATUS_CANT_TERMINATE_SELF;
        }
        Self = TRUE;
    } else {
        if (ThreadHandle != NtCurrentThread ()) {
            Status = ObReferenceObjectByHandle (ThreadHandle,
                                                THREAD_TERMINATE,
                                                PsThreadType,
                                                KeGetPreviousModeByThread (&ThisThread->Tcb),
                                                &Thread,
                                                NULL);
            if (!NT_SUCCESS (Status)) {
                return Status;
            }

            if (Thread == ThisThread) {
                ObDereferenceObject (Thread);
            } else {
                Self = FALSE;
            }
        }

    }

    if (Self) {
        PspTerminateThreadByPointer (ThisThread, ExitStatus, TRUE);
    } else {
        Status = PspTerminateThreadByPointer (Thread, ExitStatus, FALSE);
        ObDereferenceObject (Thread);
    }

    return Status;
}

NTSTATUS
PsTerminateSystemThread(
    __in NTSTATUS ExitStatus
    )

/*++

Routine Description:

    This function causes the current thread, which must be a system
    thread, to terminate.

Arguments:

    ExitStatus - Supplies the exit status associated with the thread.

Return Value:

    NTSTATUS - Status of call

--*/

{
    PETHREAD Thread = PsGetCurrentThread();

    if (!IS_SYSTEM_THREAD (Thread)) {
        return STATUS_INVALID_PARAMETER;
    }

    return PspTerminateThreadByPointer (Thread, ExitStatus, TRUE);
}


VOID
PspNullSpecialApc(
    IN PKAPC Apc,
    IN PKNORMAL_ROUTINE *NormalRoutine,
    IN PVOID *NormalContext,
    IN PVOID *SystemArgument1,
    IN PVOID *SystemArgument2
    )

{

    PAGED_CODE();

    UNREFERENCED_PARAMETER(NormalRoutine);
    UNREFERENCED_PARAMETER(NormalContext);
    UNREFERENCED_PARAMETER(SystemArgument1);
    UNREFERENCED_PARAMETER(SystemArgument2);

    ExFreePool (Apc);
}

VOID
PsExitSpecialApc(
    IN PKAPC Apc,
    IN PKNORMAL_ROUTINE *NormalRoutine,
    IN PVOID *NormalContext,
    IN PVOID *SystemArgument1,
    IN PVOID *SystemArgument2
    )

{
    NTSTATUS ExitStatus;
    PETHREAD Thread;

    PAGED_CODE();

    UNREFERENCED_PARAMETER(NormalRoutine);
    UNREFERENCED_PARAMETER(NormalContext);
    UNREFERENCED_PARAMETER(SystemArgument1);
    UNREFERENCED_PARAMETER(SystemArgument2);

    Thread = PsGetCurrentThread();

    if (((ULONG_PTR)Apc->SystemArgument2) & 1) {
        ExitStatus = (NTSTATUS)((LONG_PTR)Apc->NormalContext);
        PspExitApcRundown (Apc);
        PspExitThread (ExitStatus);
    }

}

VOID
PspExitApcRundown(
    IN PKAPC Apc
    )
{
    PAGED_CODE();

    ExFreePool(Apc);
}

VOID
PspExitNormalApc(
    IN PVOID NormalContext,
    IN PVOID SystemArgument1,
    IN PVOID SystemArgument2
    )

{
    PETHREAD Thread;
    PKAPC ExitApc;

    PAGED_CODE();

    ASSERT (!(((ULONG_PTR)SystemArgument2) & 1));

    Thread = PsGetCurrentThread();

    ExitApc = (PKAPC) SystemArgument1;

    KeInitializeApc (ExitApc,
                     PsGetKernelThread(Thread),
                     OriginalApcEnvironment,
                     PsExitSpecialApc,
                     PspExitApcRundown,
                     PspExitNormalApc,
                     UserMode,
                     NormalContext);

    if (!KeInsertQueueApc (ExitApc, ExitApc,
                           (PVOID)((ULONG_PTR)SystemArgument2 | 1),
                           2)) {
        // Note that we'll get here if APC queueing has been
        // disabled -- on the other hand, in that case, the thread
        // is exiting anyway.
        PspExitApcRundown (ExitApc);
    }
    //
    // We just queued a user APC to this thread. User APC won't fire until we do an
    // alertable wait so we need to set this flag here.
    //
    Thread->Tcb.ApcState.UserApcPending = TRUE;
}

VOID
PspCatchCriticalBreak(
    IN PCHAR Msg,
    IN PVOID Object,
    IN PUCHAR ImageFileName
    )
{
    // The object is critical to the OS -- ask to break in, or bugcheck.
    char    Response[2];
    BOOLEAN Handled;

    PAGED_CODE();

    Handled = FALSE;

    if (KdDebuggerEnabled) {
        DbgPrint(Msg,
                 Object,
                 ImageFileName);

        while (! Handled
               && ! KdDebuggerNotPresent) {
            DbgPrompt("Break, or Ignore (bi)? ",
                      Response,
                      sizeof(Response));

            switch (Response[0]) {
            case 'b':
            case 'B':
                DbgBreakPoint();
                // Fall through
            case 'i':
            case 'I':
                Handled = TRUE;
                break;

            default:
                break;
            }
        }
    }

    if (!Handled) {
        //
        // No debugger -- bugcheck immediately
        //
        KeBugCheckEx(CRITICAL_OBJECT_TERMINATION,
                     (ULONG_PTR) ((DISPATCHER_HEADER *)Object)->Type,
                     (ULONG_PTR) Object,
                     (ULONG_PTR) ImageFileName,
                     (ULONG_PTR) Msg);
    }
}

DECLSPEC_NORETURN
VOID
PspExitThread(
    IN NTSTATUS ExitStatus
    )

/*++

Routine Description:

    This function causes the currently executing thread to terminate.  This
    function is only called from within the process structure.  It is called
    either from mainline exit code to exit the current thread, or from
    PsExitSpecialApc (as a piggyback to user-mode PspExitNormalApc).

Arguments:

    ExitStatus - Supplies the exit status associated with the current thread.

Return Value:

    None.

--*/


{

    PETHREAD Thread;
    PETHREAD WaitThread;
    PETHREAD DerefThread;
    PEPROCESS Process;
    PKAPC Apc;
    PLIST_ENTRY Entry, FirstEntry;
    PTERMINATION_PORT TerminationPort, NextPort;
    LPC_CLIENT_DIED_MSG CdMsg;
    BOOLEAN LastThread;
    PTEB Teb;
    PPEB Peb;
    PACCESS_TOKEN ProcessToken;
    NTSTATUS Status;

    PAGED_CODE();

    Thread = PsGetCurrentThread();
    Process = THREAD_TO_PROCESS(Thread);

    if (Process != PsGetCurrentProcessByThread (Thread)) {
        KeBugCheckEx (INVALID_PROCESS_ATTACH_ATTEMPT,
                      (ULONG_PTR)Process,
                      (ULONG_PTR)Thread->Tcb.ApcState.Process,
                      (ULONG)Thread->Tcb.ApcStateIndex,
                      (ULONG_PTR)Thread);
    }

    KeLowerIrql(PASSIVE_LEVEL);

    if (Thread->ActiveExWorker) {
        KeBugCheckEx (ACTIVE_EX_WORKER_THREAD_TERMINATION,
                      (ULONG_PTR)Thread,
                      0,
                      0,
                      0);
    }

    if (Thread->Tcb.CombinedApcDisable != 0) {
        KeBugCheckEx (KERNEL_APC_PENDING_DURING_EXIT,
                      (ULONG_PTR)0,
                      (ULONG_PTR)Thread->Tcb.CombinedApcDisable,
                      (ULONG_PTR)0,
                      1);
    }


    //
    // Its time to start turning off various cross thread references.
    // Mark the thread as rundown and wait for accessors to exit.
    //
    ExWaitForRundownProtectionRelease (&Thread->RundownProtect);

    //
    // Clear any execution state associated with the thread
    //

    PoRundownThread(Thread);

    //
    // Notify registered callout routines of thread deletion.
    //

    PERFINFO_THREAD_DELETE(Thread);

    if (PspCreateThreadNotifyRoutineCount != 0) {
        ULONG i;
        PEX_CALLBACK_ROUTINE_BLOCK CallBack;
        PCREATE_THREAD_NOTIFY_ROUTINE Rtn;

        for (i=0; i < PSP_MAX_CREATE_THREAD_NOTIFY; i++) {
            CallBack = ExReferenceCallBackBlock (&PspCreateThreadNotifyRoutine[i]);
            if (CallBack != NULL) {
                Rtn = (PCREATE_THREAD_NOTIFY_ROUTINE) ExGetCallBackBlockRoutine (CallBack);
                Rtn (Process->UniqueProcessId,
                     Thread->Cid.UniqueThread,
                     FALSE);
                ExDereferenceCallBackBlock (&PspCreateThreadNotifyRoutine[i],
                                            CallBack);
            }
        }
    }

    LastThread = FALSE;
    DerefThread = NULL;

    PspLockProcessExclusive (Process, Thread);

    //
    // Say one less active thread. If we are the last then block creates and wait for the other threads to exit.
    //
    Process->ActiveThreads--;
    if (Process->ActiveThreads == 0) {
        PS_SET_BITS (&Process->Flags, PS_PROCESS_FLAGS_PROCESS_DELETE);

        LastThread = TRUE;
        if (ExitStatus == STATUS_THREAD_IS_TERMINATING) {
            if (Process->ExitStatus == STATUS_PENDING) {
                Process->ExitStatus = Process->LastThreadExitStatus;
            }
        } else {
            Process->ExitStatus = ExitStatus;
        }

        //
        // We are the last thread to leave the process. We have to wait till all the other threads have exited before we do.
        //
        for (Entry = Process->ThreadListHead.Flink;
             Entry != &Process->ThreadListHead;
             Entry = Entry->Flink) {

            WaitThread = CONTAINING_RECORD (Entry, ETHREAD, ThreadListEntry);
            if (WaitThread != Thread &&
                !KeReadStateThread (&WaitThread->Tcb) &&
                ObReferenceObjectSafe (WaitThread)) {

                PspUnlockProcessExclusive (Process, Thread);

                KeWaitForSingleObject (WaitThread,
                                       Executive,
                                       KernelMode,
                                       FALSE,
                                       NULL);

                if (DerefThread != NULL) {
                    ObDereferenceObject (DerefThread);
                }
                DerefThread = WaitThread;
                PspLockProcessExclusive (Process, Thread);
            }
        }
    } else {
        if (ExitStatus != STATUS_THREAD_IS_TERMINATING) {
            Process->LastThreadExitStatus = ExitStatus;
        }
    }

    PspUnlockProcessExclusive (Process, Thread);

    if (DerefThread != NULL) {
        ObDereferenceObject (DerefThread);
    }


    //
    // If we need to send debug messages then do so.
    //

    if (Process->DebugPort != NULL) {
        //
        // Don't report system thread exit to the debugger as we don't report them.
        //
        if (!IS_SYSTEM_THREAD (Thread)) {
            if (LastThread) {
                DbgkExitProcess (Process->ExitStatus);
            } else {
                DbgkExitThread (ExitStatus);
            }
        }
    }

    if (KD_DEBUGGER_ENABLED) {

        if (Thread->CrossThreadFlags & PS_CROSS_THREAD_FLAGS_BREAK_ON_TERMINATION) {
            PspCatchCriticalBreak ("Critical thread 0x%p (in %s) exited\n",
                                   Thread,
                                   Process->ImageFileName);
        }
    } // End of critical thread/process exit detect

    if (LastThread &&
        (Process->Flags & PS_PROCESS_FLAGS_BREAK_ON_TERMINATION)) {
        if (KD_DEBUGGER_ENABLED) {
            PspCatchCriticalBreak ("Critical process 0x%p (%s) exited\n",
                                   Process,
                                   Process->ImageFileName);
        } else {
            KeBugCheckEx (CRITICAL_PROCESS_DIED,
                          (ULONG_PTR)Process,
                          0,
                          0,
                          0);
        }
    }


    ASSERT(Thread->Tcb.CombinedApcDisable == 0);

    //
    // Process the TerminationPort. This is only accessed from this thread
    //
    TerminationPort = Thread->TerminationPort;
    if (TerminationPort != NULL) {

        CdMsg.PortMsg.u1.s1.DataLength = sizeof(LARGE_INTEGER);
        CdMsg.PortMsg.u1.s1.TotalLength = sizeof(LPC_CLIENT_DIED_MSG);
        CdMsg.PortMsg.u2.s2.Type = LPC_CLIENT_DIED;
        CdMsg.PortMsg.u2.s2.DataInfoOffset = 0;

        do {

            CdMsg.CreateTime.QuadPart = PS_GET_THREAD_CREATE_TIME (Thread);
            while (1) {
                Status = LpcRequestPort (TerminationPort->Port, (PPORT_MESSAGE)&CdMsg);
                if (Status == STATUS_NO_MEMORY || Status == STATUS_INSUFFICIENT_RESOURCES) {
                    KeDelayExecutionThread (KernelMode, FALSE, &ShortTime);
                    continue;
                }
                break;
            }
            ObDereferenceObject (TerminationPort->Port);

            NextPort = TerminationPort->Next;

            ExFreePoolWithTag (TerminationPort, 'pTsP'|PROTECTED_POOL);

            TerminationPort = NextPort;

        } while (TerminationPort != NULL);
    } else {

        //
        // If there are no ports to send notifications to,
        // but there is an exception port, then we have to
        // send a client died message through the exception
        // port. This will allow a server a chance to get notification
        // if an app/thread dies before it even starts
        //
        //
        // We only send the exception if the thread creation really worked.
        // DeadThread is set when an NtCreateThread returns an error, but
        // the thread will actually execute this path. If DeadThread is not
        // set than the thread creation succeeded. The other place DeadThread
        // is set is when we were terminated without having any chance to move.
        // in this case, DeadThread is set and the exit status is set to
        // STATUS_THREAD_IS_TERMINATING
        //

        if ((ExitStatus == STATUS_THREAD_IS_TERMINATING &&
            (Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_DEADTHREAD)) ||
            !(Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_DEADTHREAD)) {

            CdMsg.PortMsg.u1.s1.DataLength = sizeof (LARGE_INTEGER);
            CdMsg.PortMsg.u1.s1.TotalLength = sizeof (LPC_CLIENT_DIED_MSG);
            CdMsg.PortMsg.u2.s2.Type = LPC_CLIENT_DIED;
            CdMsg.PortMsg.u2.s2.DataInfoOffset = 0;
            if (Process->ExceptionPort != NULL) {
                CdMsg.CreateTime.QuadPart = PS_GET_THREAD_CREATE_TIME (Thread);
                while (1) {
                    Status = LpcRequestPort (Process->ExceptionPort, (PPORT_MESSAGE)&CdMsg);
                    if (Status == STATUS_NO_MEMORY || Status == STATUS_INSUFFICIENT_RESOURCES) {
                        KeDelayExecutionThread (KernelMode, FALSE, &ShortTime);
                        continue;
                    }
                    break;
                }
            }
        }
    }

    //
    // rundown the Win32 structures
    //

    if (Thread->Tcb.Win32Thread) {
        (PspW32ThreadCallout) (Thread, PsW32ThreadCalloutExit);
    }

    if (LastThread && Process->Win32Process) {
        (PspW32ProcessCallout) (Process, FALSE);
    }

    //
    // User/Gdi has been given a chance to clean up. Now make sure they didn't
    // leave the kernel stack locked which would happen if data was still live on
    // this stack, but was being used by another thread
    //

    if (!Thread->Tcb.EnableStackSwap) {
        KeBugCheckEx (KERNEL_STACK_LOCKED_AT_EXIT, 0, 0, 0, 0);
    }

    //
    // Rundown The Lists:
    //
    //      - Cancel Io By Thread
    //      - Cancel Timers
    //      - Cancel Registry Notify Requests pending against this thread
    //      - Perform kernel thread rundown
    //

    IoCancelThreadIo (Thread);
    ExTimerRundown ();
    CmNotifyRunDown (Thread);
    KeRundownThread ();

#if DBG

    //
    // See if we are exiting while holding a resource
    //

    ExCheckIfResourceOwned ();

#endif

    //
    // Delete the thread's TEB.  If the address of the TEB is in user
    // space, then this is a real user mode TEB.  If the address is in
    // system space, then this is a special system thread TEB allocated
    // from paged or nonpaged pool.
    //


    Teb = Thread->Tcb.Teb;
    if (Teb != NULL) {

        Peb = Process->Peb;

        try {

            //
            // Free the user mode stack on termination if we need to.
            //

            if ((Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_DEADTHREAD) == 0) {
                
                SIZE_T Zero;
                PVOID BaseAddress;
                
                if (Teb->FreeStackOnTermination) {
                    
                    Zero = 0;
                    BaseAddress = Teb->DeallocationStack;
                    ZwFreeVirtualMemory (NtCurrentProcess (),
                                         &BaseAddress,
                                         &Zero,
                                         MEM_RELEASE);
                }

#if defined(_WIN64)
                if (Process->Wow64Process != NULL) {
                    PTEB32 Teb32;

                    Teb32 = WOW64_GET_TEB32_SAFE (Teb);
                    if (Teb32->FreeStackOnTermination) {

                        Zero = 0;
                        BaseAddress = UlongToPtr (Teb32->DeallocationStack);
                    
                        ZwFreeVirtualMemory (NtCurrentProcess (),
                                             &BaseAddress,
                                             &Zero,
                                             MEM_RELEASE);
                    }
                }
#endif
            }

            //
            // Close the debugger object associated with this thread if there is one.
            //
            if (Teb->DbgSsReserved[1] != NULL) {
                ObCloseHandle (Teb->DbgSsReserved[1], UserMode);
            }
        } except (EXCEPTION_EXECUTE_HANDLER) {
        }

        MmDeleteTeb (Process, Teb);
        Thread->Tcb.Teb = NULL;
    }


    //
    // Let LPC component deal with message stack in Thread->LpcReplyMessage
    // but do it after the client ID becomes invalid.
    //

    LpcExitThread (Thread);

    Thread->ExitStatus = ExitStatus;
    KeQuerySystemTime (&Thread->ExitTime);


    ASSERT (Thread->Tcb.CombinedApcDisable == 0);

    if (LastThread) {

        Process->ExitTime = Thread->ExitTime;
        PspExitProcess (TRUE, Process);
            
        ProcessToken = PsReferencePrimaryToken (Process);
        if (SeDetailedAuditingWithToken (ProcessToken)) {
            SeAuditProcessExit (Process);
        }
        PsDereferencePrimaryTokenEx (Process, ProcessToken);

#if defined(_X86_)
        //
        // Rundown VDM DPCs
        //
        if (Process->VdmObjects != NULL) {
            VdmRundownDpcs (Process);
        }
#endif

        //
        // Rundown the handle table
        //
        ObKillProcess (Process);

        //
        // Release the image section
        //
        if (Process->SectionObject != NULL) {
            ObDereferenceObject (Process->SectionObject);
            Process->SectionObject = NULL;
        }

        if (Process->Job != NULL) {

            //
            // Now we can fold the process accounting into the job. Don't need to wait for
            // the delete routine.
            //

            PspExitProcessFromJob (Process->Job, Process);

        }

    }


    //
    // Rundown pending APCs. Protect against being frozen after we raise IRQL but before dispatcher lock is taken.
    //
    KeEnterCriticalRegionThread (&Thread->Tcb);

    //
    // Disable APC queueing for the current thread.
    //

    Thread->Tcb.ApcQueueable = FALSE;

    //
    // At this point we may have been frozen and the APC is pending. First we remove the suspend/freeze bias that
    // may exist and then drop IRQL. The suspend APC if present will fire and drop through. No further suspends are
    // allowed as the thread is marked to prevent APC's
    //
    KeForceResumeThread (&Thread->Tcb);
    KeLeaveCriticalRegionThread (&Thread->Tcb);

    //
    // flush user-mode APC queue
    //

    FirstEntry = KeFlushQueueApc (&Thread->Tcb, UserMode);

    if (FirstEntry != NULL) {

        Entry = FirstEntry;
        do {
            Apc = CONTAINING_RECORD (Entry, KAPC, ApcListEntry);
            Entry = Entry->Flink;

            //
            // If the APC has a rundown routine then call it. Otherwise
            // deallocate the APC
            //

            if (Apc->RundownRoutine) {
                (Apc->RundownRoutine) (Apc);
            } else {
                ExFreePool (Apc);
            }

        } while (Entry != FirstEntry);
    }

    if (LastThread) {
        MmCleanProcessAddressSpace (Process);
    }

    if (Thread->Tcb.BBTData && PspBBTNotifyRoutine) {
        (PspBBTNotifyRoutine) (&Thread->Tcb);
    }

    //
    // flush kernel-mode APC queue
    // There should never be any kernel mode APCs found this far
    // into thread termination. Since we go to PASSIVE_LEVEL upon
    // entering exit.
    //

    FirstEntry = KeFlushQueueApc (&Thread->Tcb, KernelMode);

    if (FirstEntry != NULL || Thread->Tcb.CombinedApcDisable != 0) {
        KeBugCheckEx (KERNEL_APC_PENDING_DURING_EXIT,
                      (ULONG_PTR)FirstEntry,
                      (ULONG_PTR)Thread->Tcb.CombinedApcDisable,
                      (ULONG_PTR)KeGetCurrentIrql(),
                      0);
    }


    //
    // Signal the process
    //

    if (LastThread) {
        KeSetProcess (&Process->Pcb, 0, FALSE);
    }

    //
    // Terminate the thread.
    //
    // N.B. There is no return from this call.
    //
    // N.B. The kernel inserts the current thread in the reaper list and
    //      activates a thread, if necessary, to reap the terminating thread.
    //

    KeTerminateThread (0L);
}

VOID
PspExitProcess(
    IN BOOLEAN LastThreadExit,
    IN PEPROCESS Process
    )
{
    ULONG ActualTime;
    PEJOB Job;
    PETHREAD CurrentThread;

    PAGED_CODE();

    PS_SET_BITS (&Process->Flags, PS_PROCESS_FLAGS_PROCESS_EXITING);

    if (LastThreadExit) {

        PERFINFO_PROCESS_DELETE(Process);

        if (PspCreateProcessNotifyRoutineCount != 0) {
            ULONG i;
            PEX_CALLBACK_ROUTINE_BLOCK CallBack;
            PCREATE_PROCESS_NOTIFY_ROUTINE Rtn;

            for (i = 0; i < PSP_MAX_CREATE_PROCESS_NOTIFY; i++) {
                CallBack = ExReferenceCallBackBlock (&PspCreateProcessNotifyRoutine[i]);
                if (CallBack != NULL) {
                    Rtn = (PCREATE_PROCESS_NOTIFY_ROUTINE) ExGetCallBackBlockRoutine (CallBack);
                    Rtn (Process->InheritedFromUniqueProcessId,
                         Process->UniqueProcessId,
                         FALSE);
                    ExDereferenceCallBackBlock (&PspCreateProcessNotifyRoutine[i],
                                                CallBack);
                }
            }
        }

    }


    PoRundownProcess (Process);

    //
    // Dereference (close) the security port.  This will stop any authentication
    // or EFS requests from this process to the LSA process.  The "well known"
    // value of 1 will prevent the security system from try to re-establish the
    // connection during the process shutdown (e.g. when the rdr deletes a handle)
    //

    if (Process->SecurityPort) {

        if (Process->SecurityPort != ((PVOID) 1)) {
            ObDereferenceObject (Process->SecurityPort);

            Process->SecurityPort = (PVOID) 1 ;
        }
    }
    else {
        
        //
        // Even if there have never been any requests to the LSA process, i.e. the pointer
        // is NULL, set it to 1 anyway.  Filter drivers can apparently cause a network 
        // hop at this point.  This will prevent any such from deadlocking.
        //

        Process->SecurityPort = (PVOID) 1 ;
    }


    if (LastThreadExit) {


        //
        // If the current process has previously set the timer resolution,
        // then reset it.
        //

        if ((Process->Flags&PS_PROCESS_FLAGS_SET_TIMER_RESOLUTION) != 0) {
            ZwSetTimerResolution (KeMaximumIncrement, FALSE, &ActualTime);
        }

        Job = Process->Job;
        if (Job != NULL && Job->CompletionPort != NULL &&
            !(Process->JobStatus & PS_JOB_STATUS_NOT_REALLY_ACTIVE) &&
            !(Process->JobStatus & PS_JOB_STATUS_EXIT_PROCESS_REPORTED)) {

            ULONG_PTR ExitMessageId;

            switch (Process->ExitStatus) {
                case STATUS_GUARD_PAGE_VIOLATION      :
                case STATUS_DATATYPE_MISALIGNMENT     :
                case STATUS_BREAKPOINT                :
                case STATUS_SINGLE_STEP               :
                case STATUS_ACCESS_VIOLATION          :
                case STATUS_IN_PAGE_ERROR             :
                case STATUS_ILLEGAL_INSTRUCTION       :
                case STATUS_NONCONTINUABLE_EXCEPTION  :
                case STATUS_INVALID_DISPOSITION       :
                case STATUS_ARRAY_BOUNDS_EXCEEDED     :
                case STATUS_FLOAT_DENORMAL_OPERAND    :
                case STATUS_FLOAT_DIVIDE_BY_ZERO      :
                case STATUS_FLOAT_INEXACT_RESULT      :
                case STATUS_FLOAT_INVALID_OPERATION   :
                case STATUS_FLOAT_OVERFLOW            :
                case STATUS_FLOAT_STACK_CHECK         :
                case STATUS_FLOAT_UNDERFLOW           :
                case STATUS_INTEGER_DIVIDE_BY_ZERO    :
                case STATUS_INTEGER_OVERFLOW          :
                case STATUS_PRIVILEGED_INSTRUCTION    :
                case STATUS_STACK_OVERFLOW            :
                case STATUS_CONTROL_C_EXIT            :
                case STATUS_FLOAT_MULTIPLE_FAULTS     :
                case STATUS_FLOAT_MULTIPLE_TRAPS      :
                case STATUS_REG_NAT_CONSUMPTION       :
                    ExitMessageId = JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS;
                    break;
                default:
                    ExitMessageId = JOB_OBJECT_MSG_EXIT_PROCESS;
                    break;
            }

            PS_SET_CLEAR_BITS (&Process->JobStatus,
                               PS_JOB_STATUS_EXIT_PROCESS_REPORTED,
                               PS_JOB_STATUS_LAST_REPORT_MEMORY);

            CurrentThread = PsGetCurrentThread ();

            KeEnterCriticalRegionThread (&CurrentThread->Tcb);
            ExAcquireResourceSharedLite (&Job->JobLock, TRUE);

            if (Job->CompletionPort != NULL) {
                IoSetIoCompletion (Job->CompletionPort,
                                   Job->CompletionKey,
                                   (PVOID)Process->UniqueProcessId,
                                   STATUS_SUCCESS,
                                   ExitMessageId,
                                   FALSE);
            }

            ExReleaseResourceLite (&Job->JobLock);
            KeLeaveCriticalRegionThread (&CurrentThread->Tcb);

        }

        if (CCPF_IS_PREFETCHER_ACTIVE ()) {

            //
            // Let prefetcher know that this process is exiting.
            //

            CcPfProcessExitNotification (Process);
        }

    } else {
        MmCleanProcessAddressSpace (Process);
    }

}

VOID
PspProcessDelete(
    IN PVOID Object
    )
{
    PEPROCESS Process;
    PETHREAD CurrentThread;
    KAPC_STATE ApcState;

    PAGED_CODE();

    Process = (PEPROCESS)Object;

    //
    // Zero the GrantedAccess field so the system will not panic
    // when this process is missing from the PsActiveProcess list
    // but is still found in the CID table.
    //

#if defined(_AMD64_)

    Process->GrantedAccess = 0;

#endif

    //
    // Remove the process from the global list
    //
    if (Process->ActiveProcessLinks.Flink != NULL) {
        CurrentThread = PsGetCurrentThread ();

        PspLockProcessList (CurrentThread);
        RemoveEntryList (&Process->ActiveProcessLinks);
        PspUnlockProcessList (CurrentThread);
    }

    if (Process->SeAuditProcessCreationInfo.ImageFileName != NULL) {
        ExFreePool (Process->SeAuditProcessCreationInfo.ImageFileName);
        Process->SeAuditProcessCreationInfo.ImageFileName = NULL;
    }

    if (Process->Job != NULL) {
        PspRemoveProcessFromJob (Process->Job, Process);
        ObDereferenceObjectDeferDelete (Process->Job);
        Process->Job = NULL;
    }

    KeTerminateProcess (&Process->Pcb);


    if (Process->DebugPort != NULL) {
        ObDereferenceObject (Process->DebugPort);
        Process->DebugPort = NULL;
    }
    if (Process->ExceptionPort != NULL) {
        ObDereferenceObject (Process->ExceptionPort);
        Process->ExceptionPort = NULL;
    }

    if (Process->SectionObject != NULL) {
        ObDereferenceObject (Process->SectionObject);
        Process->SectionObject = NULL;
    }

    PspDeleteLdt (Process );
    PspDeleteVdmObjects (Process);

    if (Process->ObjectTable != NULL) {
        KeStackAttachProcess (&Process->Pcb, &ApcState);
        ObKillProcess (Process);
        KeUnstackDetachProcess (&ApcState);
    }


    if (Process->Flags&PS_PROCESS_FLAGS_HAS_ADDRESS_SPACE) {

        //
        // Clean address space of the process
        //

        KeStackAttachProcess (&Process->Pcb, &ApcState);

        PspExitProcess (FALSE, Process);

        KeUnstackDetachProcess (&ApcState);

        MmDeleteProcessAddressSpace (Process);
    }

    if (Process->UniqueProcessId) {
        if (!(ExDestroyHandle (PspCidTable, Process->UniqueProcessId, NULL))) {
            KeBugCheck (CID_HANDLE_DELETION);
        }
    }

    PspDeleteProcessSecurity (Process);


    if (Process->WorkingSetWatch != NULL) {
        ExFreePool (Process->WorkingSetWatch);
        PsReturnProcessNonPagedPoolQuota (Process, WS_CATCH_SIZE);
    }

    ObDereferenceDeviceMap (Process);
    PspDereferenceQuota (Process);

#if !defined(_X86_) && !defined(_AMD64_)
    {
        //
        // Free any alignment exception tracking structures that might
        // have been around to support a user-mode debugger.
        //

        PALIGNMENT_EXCEPTION_TABLE ExceptionTable;
        PALIGNMENT_EXCEPTION_TABLE NextExceptionTable;

        ExceptionTable = Process->Pcb.AlignmentExceptionTable;
        while (ExceptionTable != NULL) {

            NextExceptionTable = ExceptionTable->Next;
            ExFreePool( ExceptionTable );
            ExceptionTable = NextExceptionTable;
        }
    }
#endif

}

VOID
PspThreadDelete(
    IN PVOID Object
    )
{
    PETHREAD Thread;
    PETHREAD CurrentThread;
    PEPROCESS Process;

    PAGED_CODE();

    Thread = (PETHREAD) Object;

    ASSERT(Thread->Tcb.Win32Thread == NULL);

    if (Thread->Tcb.InitialStack) {
        MmDeleteKernelStack(Thread->Tcb.StackBase,
                            (BOOLEAN)Thread->Tcb.LargeStack);
    }

    if (Thread->Cid.UniqueThread != NULL) {
        if (!ExDestroyHandle (PspCidTable, Thread->Cid.UniqueThread, NULL)) {
            KeBugCheck(CID_HANDLE_DELETION);
        }
    }

    PspDeleteThreadSecurity (Thread);

    Process = THREAD_TO_PROCESS(Thread);
    if (Process) {
        //
        // Remove the thread from the process if it was ever inserted.
        //
        if (Thread->ThreadListEntry.Flink != NULL) {

            CurrentThread = PsGetCurrentThread ();

            PspLockProcessExclusive (Process, CurrentThread);

            RemoveEntryList (&Thread->ThreadListEntry);

            PspUnlockProcessExclusive (Process, CurrentThread);
        }

        ObDereferenceObject(Process);
    }
}

NTSTATUS
NtRegisterThreadTerminatePort(
    __in HANDLE PortHandle
    )

/*++

Routine Description:

    This API allows a thread to register a port to be notified upon
    thread termination.

Arguments:

    PortHandle - Supplies an open handle to a port object that will be
        sent a termination message when the thread terminates.

--*/

{

    PVOID Port;
    PTERMINATION_PORT TerminationPort;
    NTSTATUS st;
    PETHREAD Thread;

    PAGED_CODE();

    Thread = PsGetCurrentThread ();

    st = ObReferenceObjectByHandle (PortHandle,
                                    0,
                                    LpcPortObjectType,
                                    KeGetPreviousModeByThread(&Thread->Tcb),
                                    &Port,
                                    NULL);

    if (!NT_SUCCESS (st)) {
        return st;
    }

    TerminationPort = ExAllocatePoolWithQuotaTag (PagedPool|POOL_QUOTA_FAIL_INSTEAD_OF_RAISE,
                                                  sizeof(TERMINATION_PORT),
                                                  'pTsP'|PROTECTED_POOL);
    if (TerminationPort == NULL) {
        ObDereferenceObject (Port);
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    TerminationPort->Port = Port;
    TerminationPort->Next = Thread->TerminationPort;

    Thread->TerminationPort = TerminationPort;

    return STATUS_SUCCESS;
}

LARGE_INTEGER
PsGetProcessExitTime(
    VOID
    )

/*++

Routine Description:

    This routine returns the exit time for the current process.

Arguments:

    None.

Return Value:

    The function value is the exit time for the current process.

Note:

    This routine assumes that the caller wants an error log entry within the
    bounds of the maximum size.

--*/

{
    PAGED_CODE();

    //
    // Simply return the exit time for this process.
    //

    return PsGetCurrentProcess()->ExitTime;
}


#undef PsIsThreadTerminating

BOOLEAN
PsIsThreadTerminating(
    __in PETHREAD Thread
    )

/*++

Routine Description:

    This routine returns TRUE if the specified thread is in the process of
    terminating.

Arguments:

    Thread - Supplies a pointer to the thread to be checked for termination.

Return Value:

    TRUE is returned if the thread is terminating, else FALSE is returned.

--*/

{
    //
    // Simply return whether or not the thread is in the process of terminating.
    //

    if (Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_TERMINATED) {
        return TRUE;
    } else {
        return FALSE;
    }
}



NTSTATUS
PspFreezeProcessWorker (
    PEPROCESS Process,
    PVOID Context
    )
/*++

Routine Description:

    This function is the enumeration worker to suspend all processes.

Arguments:

    Process - Current process being enumerated
    Context - Unused context value

Return Value:

    NTSTATUS - Always returns true to continue enumeration

--*/
{

    UNREFERENCED_PARAMETER (Context);

    if (Process != PsInitialSystemProcess &&
        Process != PsIdleProcess &&
        Process != ExpDefaultErrorPortProcess) {

        if (Process->ExceptionPort != NULL) {
            LpcDisconnectPort (Process->ExceptionPort);
        }
        if ((Process->Flags&PS_PROCESS_FLAGS_PROCESS_EXITING) == 0) {
            PsSuspendProcess (Process);
        }
    }

    return STATUS_SUCCESS;
}

BOOLEAN PsContinueWaiting = FALSE;


LOGICAL
PsShutdownSystem (
    VOID
  )
/*++

Routine Description:

    This function shuts down ps, killing all non-system threads.

Arguments:

    None.

Return Value:

    Returns TRUE if all processes were terminated, FALSE if not.

--*/

{
    PEPROCESS     Process;
    PETHREAD      Thread;
    ULONG         NumProcs;
    ULONG         i;
    ULONG         MaxPasses;
    NTSTATUS      Status;
    LARGE_INTEGER Timeout = {(ULONG)(-10 * 1000 * 1000 * 100), -1};
    LOGICAL       Retval;

#define WAIT_BATCH THREAD_WAIT_OBJECTS
    PKPROCESS     WaitProcs[WAIT_BATCH];
    BOOLEAN       First;

    PAGED_CODE();

    Retval = TRUE;
    //
    // Some processes wait for other processes to die and then initiate actions.
    // Killing all processes without letting any execute any usermode code
    // prevents any unwanted initiated actions.
    //

    Thread = PsGetCurrentThread();

    if (InterlockedCompareExchangePointer(&PspShutdownThread,
                                          Thread,
                                          0) != 0) {
        // Some other thread is already in shutdown -- bail
        return FALSE;
    }

    PsEnumProcesses (PspFreezeProcessWorker, NULL);


    //
    // This loop kills all the processes and then waits for one of a subset
    // of them to die.  They must all be killed first (before any can be waited
    // on) so that any process like a debuggee that is waiting for a debugger
    // won't stall us.
    //
    // Driver unload won't occur until the last handle goes away for a device.
    //

    MaxPasses = 0;
    First = TRUE;
    do {
        NumProcs = 0;

        Status = STATUS_SUCCESS;
        for (Process = PsGetNextProcess (NULL);
             Process != NULL;
             Process = PsGetNextProcess (Process)) {

            if (Process != PsInitialSystemProcess &&
                Process != PsIdleProcess &&
                Process != ExpDefaultErrorPortProcess) {

                ASSERT (MmGetSessionId (Process) == 0);

                Status = PsTerminateProcess (Process,
                                             STATUS_SYSTEM_SHUTDOWN);

                //
                // If there is space save the referenced process away so
                // we can wait on it.  Don't wait on processes with no
                // threads as they will only exit when the last handle goes.
                //

                if ((Process->Flags&PS_PROCESS_FLAGS_PROCESS_EXITING) == 0 &&
                    Status != STATUS_NOTHING_TO_TERMINATE &&
                    NumProcs < WAIT_BATCH) {

                    ObReferenceObject (Process);
                    WaitProcs[NumProcs++] = &Process->Pcb;
                }
            }
        }
        First = FALSE;

        //
        // Wait for one of a small set of the processes to exit.
        //

        if (NumProcs != 0) {
            Status = KeWaitForMultipleObjects (NumProcs,
                                               WaitProcs,
                                               WaitAny,
                                               Executive,
                                               KernelMode,
                                               FALSE,
                                               &Timeout,
                                               NULL);

           for (i = 0; i < NumProcs; i++) {
               Process = CONTAINING_RECORD(WaitProcs[i],
                                           EPROCESS,
                                           Pcb);

               ObDereferenceObject (Process);
           }
        }

        //
        // Don't let an unkillable process stop shutdown from finishing.
        // ASSERT on checked builds so the faulty component causing this
        // can be debugged and fixed.
        //
        if (NumProcs > 0 && Status == STATUS_TIMEOUT) {
            MaxPasses += 1;
            if (MaxPasses > 10) {
                ASSERT (FALSE);
                if (!PsContinueWaiting) {
                    Retval = FALSE;
                    break;
                }
            }
        } else {
            MaxPasses = 0;
        }

    } while (NumProcs > 0);

    if (PoCleanShutdownEnabled()  && ExpDefaultErrorPortProcess) {
        // Explicitly kill csrss -- we don't want to do this in the loop,
        // because we don't want to wait on it, because it has system
        // threads which will exit later.  But we can terminate the user
        // threads, now that everything else has died (we can't terminate
        // them earlier, because DestroyWindowStation()/TerminateConsole()
        // depends on them being around).

        PsTerminateProcess(ExpDefaultErrorPortProcess,
                           STATUS_SYSTEM_SHUTDOWN);

        // Now, make sure that csrss's usermode threads have gotten a
        // chance to terminate.
        PspWaitForUsermodeExit(ExpDefaultErrorPortProcess);
    }

    // And we're done.

    PspShutdownJobLimits();
    MmUnmapViewOfSection(PsInitialSystemProcess, PspSystemDll.DllBase);
    ObDereferenceObject(PspSystemDll.Section);
    ZwClose(PspInitialSystemProcessHandle);
    PspInitialSystemProcessHandle = NULL;

    // Disconnect the system process's LSA security port
    if (PsInitialSystemProcess->SecurityPort) {
        if (PsInitialSystemProcess->SecurityPort != ((PVOID) 1 ))
        {
            ObDereferenceObject(PsInitialSystemProcess->SecurityPort);

            PsInitialSystemProcess->SecurityPort = (PVOID) 1 ;
        }

    }

    return Retval;
}

BOOLEAN
PsWaitForAllProcesses (
    VOID)
/*++

Routine Description:

    This function waits for all the processes to terminate.

Arguments:

    None.

Return Value:

    Returns TRUE if all processes were terminated, FALSE if not.

--*/
{
    NTSTATUS Status;
    LARGE_INTEGER Timeout = {(ULONG)-(100 * 1000), -1};
    ULONG MaxPasses;
    BOOLEAN Wait;
    PEPROCESS Process;
    PEPROCESS WaitProcess=NULL;

    MaxPasses = 0;
    while (1) {
        Wait = FALSE;
        for (Process = PsGetNextProcess (NULL);
             Process != NULL;
             Process = PsGetNextProcess (Process)) {

            if (Process != PsInitialSystemProcess &&
                Process != PsIdleProcess &&
                (Process->Flags&PS_PROCESS_FLAGS_PROCESS_EXITING) != 0) {
                if (Process->ObjectTable != NULL) {
                    Wait = TRUE;
                    WaitProcess = Process;
                    ObReferenceObject (WaitProcess);
                    PsQuitNextProcess (WaitProcess);
                    break;
                }
            }
        }

        if (Wait) {
            Status = KeWaitForSingleObject (WaitProcess,
                                            Executive,
                                            KernelMode,
                                            FALSE,
                                            &Timeout);

            ObDereferenceObject (WaitProcess);

            if (Status == STATUS_TIMEOUT) {
                MaxPasses += 1;
                Timeout.QuadPart *= 2;
                if (MaxPasses > 13) {
                    KdPrint (("PS: %d process left in the system after termination\n",
                             PsProcessType->TotalNumberOfObjects));
                    return FALSE;
                }
            }
        } else {
            return TRUE;
        }
    }

    return TRUE;
}

#ifdef ALLOC_DATA_PRAGMA
#pragma data_seg()
#endif

