699 lines
16 KiB
C
699 lines
16 KiB
C
/*++
|
|
|
|
Copyright (c) 1989-1998 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
threads.c
|
|
|
|
Abstract:
|
|
|
|
This module defines support functions for the worker, waiter, and
|
|
timer thread pools.
|
|
|
|
Author:
|
|
|
|
Gurdeep Singh Pall (gurdeep) Nov 13, 1997
|
|
|
|
Revision History:
|
|
|
|
lokeshs - extended/modified threadpool.
|
|
Rob Earhart (earhart) September 28, 2000
|
|
Moved globals from threads.h to threads.c
|
|
Split into independant modules
|
|
Event cache cleanup
|
|
|
|
Environment:
|
|
|
|
These routines are statically linked in the caller's executable and
|
|
are callable only from user mode. They make use of Nt system services.
|
|
|
|
|
|
--*/
|
|
|
|
#include <ntos.h>
|
|
#include <ntrtl.h>
|
|
#include <nturtl.h>
|
|
#include <wow64t.h>
|
|
#include "ntrtlp.h"
|
|
#include "threads.h"
|
|
|
|
// Thread pool globals
|
|
|
|
// Events used for synchronization
|
|
|
|
// NTRAID#201102-2000/10/10-earhart -- C requires that statically
|
|
// declared structures be initialized to zero, which is correct for an
|
|
// slist -- although it would be nice to have some sort of an
|
|
// SLIST_HEADER_STATIC_INITIALIZER available to use here.
|
|
SLIST_HEADER EventCache;
|
|
|
|
RTLP_START_THREAD RtlpStartThread ;
|
|
PRTLP_START_THREAD RtlpStartThreadFunc = RtlpStartThread ;
|
|
RTLP_EXIT_THREAD RtlpExitThread ;
|
|
PRTLP_EXIT_THREAD RtlpExitThreadFunc = RtlpExitThread ;
|
|
|
|
ULONG MaxThreads = 500;
|
|
|
|
#if DBG1
|
|
PVOID CallbackFn1, CallbackFn2, Context1, Context2 ;
|
|
#endif
|
|
|
|
#if DBG
|
|
CHAR InvalidSignatureMsg[] = "Invalid threadpool object signature";
|
|
CHAR InvalidDelSignatureMsg[] = "Invalid or deleted threadpool object signature";
|
|
#endif
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
RtlSetThreadPoolStartFunc(
|
|
PRTLP_START_THREAD StartFunc,
|
|
PRTLP_EXIT_THREAD ExitFunc
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine sets the thread pool's thread creation function. This is not
|
|
thread safe, because it is intended solely for kernel32 to call for processes
|
|
that aren't csrss/smss.
|
|
|
|
Arguments:
|
|
|
|
StartFunc - Function to create a new thread
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
|
|
{
|
|
RtlpStartThreadFunc = StartFunc ;
|
|
RtlpExitThreadFunc = ExitFunc ;
|
|
return STATUS_SUCCESS ;
|
|
}
|
|
|
|
NTSTATUS
|
|
RtlThreadPoolCleanup (
|
|
ULONG Flags
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
This routine cleans up the thread pool.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS : if none of the components are in use.
|
|
STATUS_UNSUCCESSFUL : if some components are still in use.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status, NextStatus;
|
|
|
|
return STATUS_UNSUCCESSFUL;
|
|
|
|
//
|
|
// Attempt to cleanup all modules. Keep, as our final return
|
|
// value, the status of the first cleanup routine to error (it's
|
|
// pretty arbitrary), but continue on and attempt cleanup of all
|
|
// modules.
|
|
//
|
|
|
|
Status = RtlpTimerCleanup();
|
|
NextStatus = RtlpWaitCleanup();
|
|
if (NT_SUCCESS(Status)) {
|
|
Status = NextStatus;
|
|
}
|
|
NextStatus = RtlpWorkerCleanup();
|
|
if (NT_SUCCESS(Status)) {
|
|
Status = NextStatus;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
RtlpStartThread (
|
|
PUSER_THREAD_START_ROUTINE Function,
|
|
PVOID Parameter,
|
|
HANDLE *ThreadHandleReturn
|
|
)
|
|
{
|
|
return RtlCreateUserThread(
|
|
NtCurrentProcess(), // process handle
|
|
NULL, // security descriptor
|
|
TRUE, // Create suspended?
|
|
0L, // ZeroBits: default
|
|
0L, // Max stack size: default
|
|
0L, // Committed stack size: default
|
|
Function, // Function to start in
|
|
Parameter, // Parameter to start with
|
|
ThreadHandleReturn, // Thread handle return
|
|
NULL // Thread id
|
|
);
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
RtlpStartThreadpoolThread (
|
|
PUSER_THREAD_START_ROUTINE Function,
|
|
PVOID Parameter,
|
|
HANDLE *ThreadHandleReturn
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is used start a new wait thread in the pool.
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS - Timer Queue created successfully.
|
|
|
|
STATUS_NO_MEMORY - There was not sufficient heap to perform the requested operation.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
HANDLE ThreadHandle;
|
|
|
|
if (ThreadHandleReturn) {
|
|
*ThreadHandleReturn = NULL;
|
|
}
|
|
|
|
if (LdrpShutdownInProgress) {
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
// We don't want to create the thread while impersonating;
|
|
// this was nt raid #278770
|
|
ASSERT(! RtlIsImpersonating());
|
|
|
|
// Create the thread.
|
|
|
|
#if DBG
|
|
DbgPrintEx(DPFLTR_RTLTHREADPOOL_ID,
|
|
RTLP_THREADPOOL_TRACE_MASK,
|
|
"StartThread: Starting worker thread %p(%p)\n",
|
|
Function,
|
|
Parameter);
|
|
#endif
|
|
|
|
Status = RtlpStartThreadFunc(Function,
|
|
Parameter,
|
|
&ThreadHandle);
|
|
|
|
#if DBG
|
|
DbgPrintEx(DPFLTR_RTLTHREADPOOL_ID,
|
|
RTLP_THREADPOOL_TRACE_MASK,
|
|
"StartThread: Started worker thread: status %p, handle %x\n",
|
|
Status,
|
|
ThreadHandle);
|
|
#endif
|
|
|
|
if (! NT_SUCCESS(Status)) {
|
|
goto cleanup;
|
|
}
|
|
|
|
if (ThreadHandleReturn) {
|
|
// Set the thread handle return before we resume the
|
|
// thread -- in case the thread proceeds to use the
|
|
// returned handle.
|
|
*ThreadHandleReturn = ThreadHandle;
|
|
}
|
|
|
|
Status = NtResumeThread(ThreadHandle, NULL);
|
|
|
|
if (! NT_SUCCESS(Status)) {
|
|
|
|
NtTerminateThread(ThreadHandle, Status);
|
|
|
|
if (ThreadHandleReturn) {
|
|
*ThreadHandleReturn = NULL;
|
|
}
|
|
|
|
NtClose(ThreadHandle);
|
|
}
|
|
|
|
cleanup:
|
|
|
|
return Status ;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
RtlpExitThread(
|
|
NTSTATUS Status
|
|
)
|
|
{
|
|
return NtTerminateThread( NtCurrentThread(), Status );
|
|
}
|
|
|
|
VOID
|
|
RtlpDoNothing (
|
|
PVOID NotUsed1,
|
|
PVOID NotUsed2,
|
|
PVOID NotUsed3
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is used to see if the thread is alive
|
|
|
|
Arguments:
|
|
|
|
NotUsed1, NotUsed2 and NotUsed 3 - not used
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
|
|
}
|
|
|
|
VOID
|
|
RtlpThreadCleanup (
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is used for exiting timer, wait and IOworker threads.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
{
|
|
NtTerminateThread( NtCurrentThread(), 0) ;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
RtlpWaitForEvent (
|
|
HANDLE Event,
|
|
HANDLE ThreadHandle
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Waits for the event to be signalled. If the event is not signalled within
|
|
one second, then checks to see that the thread is alive
|
|
|
|
Arguments:
|
|
|
|
Event : Event handle used for signalling completion of request
|
|
|
|
ThreadHandle: Thread to check whether still alive
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS if event was signalled
|
|
else return NTSTATUS
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
HANDLE Handles[2];
|
|
|
|
Handles[0] = Event;
|
|
Handles[1] = ThreadHandle;
|
|
|
|
Status = NtWaitForMultipleObjects(2, Handles, WaitAny, FALSE, NULL);
|
|
|
|
if (Status == STATUS_WAIT_0) {
|
|
//
|
|
// The event has been signalled
|
|
//
|
|
Status = STATUS_SUCCESS;
|
|
|
|
} else if (Status == STATUS_WAIT_1) {
|
|
//
|
|
// The target thread has died.
|
|
//
|
|
#if DBG
|
|
DbgPrintEx(DPFLTR_RTLTHREADPOOL_ID,
|
|
RTLP_THREADPOOL_ERROR_MASK,
|
|
"Threadpool thread died before event could be signalled\n");
|
|
#endif
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
|
|
} else if (NT_SUCCESS(Status)) {
|
|
//
|
|
// Something else has happened; make sure we fail.
|
|
//
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
PRTLP_EVENT
|
|
RtlpGetWaitEvent (
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns an event from the event cache.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
Pointer to event structure
|
|
|
|
--*/
|
|
{
|
|
PSLIST_ENTRY Entry;
|
|
PRTLP_EVENT Event;
|
|
NTSTATUS Status;
|
|
|
|
ASSERT(! RtlIsImpersonating());
|
|
|
|
Entry = RtlInterlockedPopEntrySList(&EventCache);
|
|
|
|
if (Entry) {
|
|
|
|
Event = CONTAINING_RECORD(Entry, RTLP_EVENT, Link);
|
|
|
|
} else {
|
|
|
|
Event = RtlpAllocateTPHeap(sizeof(RTLP_EVENT), 0);
|
|
|
|
if (Event) {
|
|
Status = NtCreateEvent(&Event->Handle,
|
|
EVENT_ALL_ACCESS,
|
|
NULL,
|
|
SynchronizationEvent,
|
|
FALSE);
|
|
if (! NT_SUCCESS(Status)) {
|
|
RtlpFreeTPHeap(Event);
|
|
Event = NULL;
|
|
} else {
|
|
OBJECT_HANDLE_FLAG_INFORMATION HandleInfo;
|
|
|
|
HandleInfo.Inherit = FALSE;
|
|
HandleInfo.ProtectFromClose = TRUE;
|
|
NtSetInformationObject(Event->Handle,
|
|
ObjectHandleFlagInformation,
|
|
&HandleInfo,
|
|
sizeof(HandleInfo));
|
|
}
|
|
}
|
|
}
|
|
|
|
return Event;
|
|
}
|
|
|
|
VOID
|
|
RtlpFreeWaitEvent (
|
|
PRTLP_EVENT Event
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Frees the event to the event cache
|
|
|
|
Arguments:
|
|
|
|
Event - the event struct to put back into the cache
|
|
|
|
Return Value:
|
|
|
|
Nothing
|
|
|
|
--*/
|
|
{
|
|
ASSERT(Event != NULL);
|
|
|
|
//
|
|
// Note: There's a race between checking the depth and pushing the
|
|
// event. This doesn't hurt anything.
|
|
//
|
|
|
|
if (RtlQueryDepthSList(&EventCache) >= MAX_UNUSED_EVENTS) {
|
|
OBJECT_HANDLE_FLAG_INFORMATION HandleInfo;
|
|
|
|
HandleInfo.Inherit = FALSE;
|
|
HandleInfo.ProtectFromClose = FALSE;
|
|
NtSetInformationObject(Event->Handle,
|
|
ObjectHandleFlagInformation,
|
|
&HandleInfo,
|
|
sizeof(HandleInfo));
|
|
NtClose(Event->Handle);
|
|
RtlpFreeTPHeap(Event);
|
|
} else {
|
|
RtlInterlockedPushEntrySList(&EventCache,
|
|
(PSLIST_ENTRY)&Event->Link);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
RtlpWaitOrTimerCallout(WAITORTIMERCALLBACKFUNC Function,
|
|
PVOID Context,
|
|
BOOLEAN TimedOut,
|
|
PACTIVATION_CONTEXT ActivationContext,
|
|
HANDLE ImpersonationToken,
|
|
PRTL_CRITICAL_SECTION const *LocksHeld)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Perform a safe callout to the supplied wait or timer callback
|
|
|
|
Arguments:
|
|
|
|
Function -- the function to call
|
|
|
|
Context -- the context parameter for the function
|
|
|
|
TimedOut -- whether this callback occurred because of a timer
|
|
expiration
|
|
|
|
ActivationContext -- the request's originating activation context
|
|
|
|
ImpersonationToken -- the request's impersonation token
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
RTL_CALLER_ALLOCATED_ACTIVATION_CONTEXT_STACK_FRAME ActivationFrame = { sizeof(ActivationFrame), RTL_CALLER_ALLOCATED_ACTIVATION_CONTEXT_STACK_FRAME_FORMAT_WHISTLER };
|
|
|
|
#if (DBG1)
|
|
DBG_SET_FUNCTION( Function, Context );
|
|
#endif
|
|
if (ImpersonationToken) {
|
|
RTL_VERIFY(
|
|
NT_SUCCESS(
|
|
NtSetInformationThread(NtCurrentThread(),
|
|
ThreadImpersonationToken,
|
|
(PVOID)&ImpersonationToken,
|
|
(ULONG)sizeof(HANDLE))));
|
|
}
|
|
|
|
RtlActivateActivationContextUnsafeFast(&ActivationFrame, ActivationContext);
|
|
__try {
|
|
// ISSUE-2000/10/10-earhart: Once the top level exception
|
|
// handling code's moved to RTL, we need to catch any
|
|
// exceptions here, and recover the thread.
|
|
Function(Context, TimedOut);
|
|
} __finally {
|
|
|
|
RtlCheckHeldCriticalSections(NtCurrentThread(), LocksHeld);
|
|
|
|
RtlDeactivateActivationContextUnsafeFast(&ActivationFrame);
|
|
|
|
if (RtlIsImpersonating()) {
|
|
HANDLE NewToken = NULL;
|
|
|
|
RTL_VERIFY(
|
|
NT_SUCCESS(
|
|
NtSetInformationThread(NtCurrentThread(),
|
|
ThreadImpersonationToken,
|
|
(PVOID)&NewToken,
|
|
(ULONG)sizeof(HANDLE))));
|
|
}
|
|
}
|
|
}
|
|
|
|
VOID
|
|
RtlpApcCallout(APC_CALLBACK_FUNCTION Function,
|
|
NTSTATUS Status,
|
|
PVOID Context1,
|
|
PVOID Context2)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Perform a safe callout to the supplied APC callback
|
|
|
|
Arguments:
|
|
|
|
Function -- the function to call
|
|
|
|
Status -- the status parameter for the function
|
|
|
|
Context1 -- the first context parameter for the function
|
|
|
|
Context2 -- the second context parameter for the function
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
// ISSUE-2000/10/10-earhart: Once the top level exception handling
|
|
// code's moved to RTL, we need to catch any exceptions here, and
|
|
// recover the thread.
|
|
Function(Status, Context1, Context2) ;
|
|
|
|
RtlCheckHeldCriticalSections(NtCurrentThread(), NULL);
|
|
}
|
|
|
|
VOID
|
|
RtlpWorkerCallout(WORKERCALLBACKFUNC Function,
|
|
PVOID Context,
|
|
PACTIVATION_CONTEXT ActivationContext,
|
|
HANDLE ImpersonationToken)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Perform a safe callout to the supplied worker callback
|
|
|
|
Arguments:
|
|
|
|
Function -- the function to call
|
|
|
|
Context -- the context parameter for the function
|
|
|
|
ActivationContext -- the request's originating activation context
|
|
|
|
ImpersonationToken -- the request's impersonation token
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
RTL_CALLER_ALLOCATED_ACTIVATION_CONTEXT_STACK_FRAME ActivationFrame = { sizeof(ActivationFrame), RTL_CALLER_ALLOCATED_ACTIVATION_CONTEXT_STACK_FRAME_FORMAT_WHISTLER };
|
|
|
|
#if (DBG1)
|
|
DBG_SET_FUNCTION( Function, Context ) ;
|
|
#endif
|
|
if (ImpersonationToken) {
|
|
RTL_VERIFY(
|
|
NT_SUCCESS(
|
|
NtSetInformationThread(NtCurrentThread(),
|
|
ThreadImpersonationToken,
|
|
(PVOID)&ImpersonationToken,
|
|
(ULONG)sizeof(HANDLE))));
|
|
}
|
|
|
|
RtlActivateActivationContextUnsafeFast(&ActivationFrame, ActivationContext);
|
|
__try {
|
|
// ISSUE-2000/10/10-earhart: Once the top level exception
|
|
// handling code's moved to RTL, we need to catch any
|
|
// exceptions here, and recover the thread.
|
|
Function(Context) ;
|
|
} __finally {
|
|
|
|
RtlCheckHeldCriticalSections(NtCurrentThread(), NULL);
|
|
|
|
RtlDeactivateActivationContextUnsafeFast(&ActivationFrame);
|
|
|
|
if (RtlIsImpersonating()) {
|
|
HANDLE NewToken = NULL;
|
|
|
|
RTL_VERIFY(
|
|
NT_SUCCESS(
|
|
NtSetInformationThread(NtCurrentThread(),
|
|
ThreadImpersonationToken,
|
|
(PVOID)&NewToken,
|
|
(ULONG)sizeof(HANDLE))));
|
|
}
|
|
}
|
|
}
|
|
|
|
NTSTATUS
|
|
RtlpCaptureImpersonation(
|
|
IN LOGICAL RequestDuplicateAccess,
|
|
OUT PHANDLE Token
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
HANDLE NewToken;
|
|
|
|
*Token = NULL;
|
|
|
|
if (RtlIsImpersonating()) {
|
|
|
|
Status = NtOpenThreadToken(NtCurrentThread(),
|
|
TOKEN_IMPERSONATE
|
|
| (RequestDuplicateAccess
|
|
? TOKEN_DUPLICATE
|
|
: 0),
|
|
TRUE,
|
|
Token);
|
|
if (! NT_SUCCESS(Status)) {
|
|
return Status;
|
|
}
|
|
|
|
NewToken = NULL;
|
|
|
|
Status = NtSetInformationThread(NtCurrentThread(),
|
|
ThreadImpersonationToken,
|
|
(PVOID) &NewToken,
|
|
(ULONG) sizeof(NewToken));
|
|
|
|
if (! NT_SUCCESS(Status)) {
|
|
|
|
NtSetInformationThread(NtCurrentThread(),
|
|
ThreadImpersonationToken,
|
|
(PVOID) Token,
|
|
(ULONG) sizeof(*Token));
|
|
NtClose(*Token);
|
|
*Token = NULL;
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
VOID
|
|
RtlpRestartImpersonation(
|
|
IN HANDLE Token
|
|
)
|
|
{
|
|
NtSetInformationThread(NtCurrentThread(),
|
|
ThreadImpersonationToken,
|
|
&Token,
|
|
sizeof(Token));
|
|
}
|