1109 lines
24 KiB
C
1109 lines
24 KiB
C
/**
|
|
* WinPR: Windows Portable Runtime
|
|
* Process Thread Functions
|
|
*
|
|
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
|
* Copyright 2015 Hewlett-Packard Development Company, L.P.
|
|
* Copyright 2021 David Fort <contact@hardening-consulting.com>
|
|
*
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <winpr/config.h>
|
|
|
|
#include <winpr/assert.h>
|
|
|
|
#include <winpr/handle.h>
|
|
|
|
#include <winpr/thread.h>
|
|
|
|
#ifndef MIN
|
|
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
|
|
#endif
|
|
|
|
#ifndef MAX
|
|
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
|
|
#endif
|
|
|
|
/**
|
|
* api-ms-win-core-processthreads-l1-1-1.dll
|
|
*
|
|
* CreateRemoteThread
|
|
* CreateRemoteThreadEx
|
|
* CreateThread
|
|
* DeleteProcThreadAttributeList
|
|
* ExitThread
|
|
* FlushInstructionCache
|
|
* FlushProcessWriteBuffers
|
|
* GetCurrentThread
|
|
* GetCurrentThreadId
|
|
* GetCurrentThreadStackLimits
|
|
* GetExitCodeThread
|
|
* GetPriorityClass
|
|
* GetStartupInfoW
|
|
* GetThreadContext
|
|
* GetThreadId
|
|
* GetThreadIdealProcessorEx
|
|
* GetThreadPriority
|
|
* GetThreadPriorityBoost
|
|
* GetThreadTimes
|
|
* InitializeProcThreadAttributeList
|
|
* OpenThread
|
|
* OpenThreadToken
|
|
* QueryProcessAffinityUpdateMode
|
|
* QueueUserAPC
|
|
* ResumeThread
|
|
* SetPriorityClass
|
|
* SetThreadContext
|
|
* SetThreadPriority
|
|
* SetThreadPriorityBoost
|
|
* SetThreadStackGuarantee
|
|
* SetThreadToken
|
|
* SuspendThread
|
|
* SwitchToThread
|
|
* TerminateThread
|
|
* UpdateProcThreadAttribute
|
|
*/
|
|
|
|
#ifndef _WIN32
|
|
|
|
#include <winpr/crt.h>
|
|
#include <winpr/platform.h>
|
|
|
|
#include <string.h>
|
|
#ifdef WINPR_HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef WINPR_HAVE_SYS_EVENTFD_H
|
|
#include <sys/eventfd.h>
|
|
#endif
|
|
|
|
#include <winpr/debug.h>
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
|
|
#include <winpr/collections.h>
|
|
|
|
#include "thread.h"
|
|
#include "apc.h"
|
|
|
|
#include "../handle/handle.h"
|
|
#include "../log.h"
|
|
#define TAG WINPR_TAG("thread")
|
|
|
|
static WINPR_THREAD mainThread;
|
|
|
|
#if defined(WITH_THREAD_LIST)
|
|
static wListDictionary* thread_list = NULL;
|
|
#endif
|
|
|
|
static BOOL ThreadCloseHandle(HANDLE handle);
|
|
static void cleanup_handle(void* obj);
|
|
|
|
static BOOL ThreadIsHandled(HANDLE handle)
|
|
{
|
|
return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_THREAD, FALSE);
|
|
}
|
|
|
|
static int ThreadGetFd(HANDLE handle)
|
|
{
|
|
WINPR_THREAD* pThread = (WINPR_THREAD*)handle;
|
|
|
|
if (!ThreadIsHandled(handle))
|
|
return -1;
|
|
|
|
return pThread->event.fds[0];
|
|
}
|
|
|
|
#define run_mutex_init(fkt, mux, arg) run_mutex_init_(fkt, #fkt, mux, arg)
|
|
static BOOL run_mutex_init_(int (*fkt)(pthread_mutex_t*, const pthread_mutexattr_t*),
|
|
const char* name, pthread_mutex_t* mutex,
|
|
const pthread_mutexattr_t* mutexattr)
|
|
{
|
|
int rc = 0;
|
|
|
|
WINPR_ASSERT(fkt);
|
|
WINPR_ASSERT(mutex);
|
|
|
|
rc = fkt(mutex, mutexattr);
|
|
if (rc != 0)
|
|
{
|
|
char ebuffer[256] = { 0 };
|
|
WLog_WARN(TAG, "[%s] failed with [%s]", name, winpr_strerror(rc, ebuffer, sizeof(ebuffer)));
|
|
}
|
|
return rc == 0;
|
|
}
|
|
|
|
#define run_mutex_fkt(fkt, mux) run_mutex_fkt_(fkt, #fkt, mux)
|
|
static BOOL run_mutex_fkt_(int (*fkt)(pthread_mutex_t* mux), const char* name,
|
|
pthread_mutex_t* mutex)
|
|
{
|
|
int rc = 0;
|
|
|
|
WINPR_ASSERT(fkt);
|
|
WINPR_ASSERT(mutex);
|
|
|
|
rc = fkt(mutex);
|
|
if (rc != 0)
|
|
{
|
|
char ebuffer[256] = { 0 };
|
|
WLog_WARN(TAG, "[%s] failed with [%s]", name, winpr_strerror(rc, ebuffer, sizeof(ebuffer)));
|
|
}
|
|
return rc == 0;
|
|
}
|
|
|
|
#define run_cond_init(fkt, cond, arg) run_cond_init_(fkt, #fkt, cond, arg)
|
|
static BOOL run_cond_init_(int (*fkt)(pthread_cond_t*, const pthread_condattr_t*), const char* name,
|
|
pthread_cond_t* condition, const pthread_condattr_t* conditionattr)
|
|
{
|
|
int rc = 0;
|
|
|
|
WINPR_ASSERT(fkt);
|
|
WINPR_ASSERT(condition);
|
|
|
|
rc = fkt(condition, conditionattr);
|
|
if (rc != 0)
|
|
{
|
|
char ebuffer[256] = { 0 };
|
|
WLog_WARN(TAG, "[%s] failed with [%s]", name, winpr_strerror(rc, ebuffer, sizeof(ebuffer)));
|
|
}
|
|
return rc == 0;
|
|
}
|
|
|
|
#define run_cond_fkt(fkt, cond) run_cond_fkt_(fkt, #fkt, cond)
|
|
static BOOL run_cond_fkt_(int (*fkt)(pthread_cond_t* mux), const char* name,
|
|
pthread_cond_t* condition)
|
|
{
|
|
int rc = 0;
|
|
|
|
WINPR_ASSERT(fkt);
|
|
WINPR_ASSERT(condition);
|
|
|
|
rc = fkt(condition);
|
|
if (rc != 0)
|
|
{
|
|
char ebuffer[256] = { 0 };
|
|
WLog_WARN(TAG, "[%s] failed with [%s]", name, winpr_strerror(rc, ebuffer, sizeof(ebuffer)));
|
|
}
|
|
return rc == 0;
|
|
}
|
|
|
|
static int pthread_mutex_checked_unlock(pthread_mutex_t* mutex)
|
|
{
|
|
WINPR_ASSERT(mutex);
|
|
WINPR_ASSERT(pthread_mutex_trylock(mutex) == EBUSY);
|
|
return pthread_mutex_unlock(mutex);
|
|
}
|
|
|
|
static BOOL mux_condition_bundle_init(mux_condition_bundle* bundle)
|
|
{
|
|
WINPR_ASSERT(bundle);
|
|
|
|
bundle->val = FALSE;
|
|
if (!run_mutex_init(pthread_mutex_init, &bundle->mux, NULL))
|
|
return FALSE;
|
|
|
|
if (!run_cond_init(pthread_cond_init, &bundle->cond, NULL))
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
static void mux_condition_bundle_uninit(mux_condition_bundle* bundle)
|
|
{
|
|
mux_condition_bundle empty = { 0 };
|
|
|
|
WINPR_ASSERT(bundle);
|
|
|
|
run_cond_fkt(pthread_cond_destroy, &bundle->cond);
|
|
run_mutex_fkt(pthread_mutex_destroy, &bundle->mux);
|
|
*bundle = empty;
|
|
}
|
|
|
|
static BOOL mux_condition_bundle_signal(mux_condition_bundle* bundle)
|
|
{
|
|
BOOL rc = TRUE;
|
|
WINPR_ASSERT(bundle);
|
|
|
|
if (!run_mutex_fkt(pthread_mutex_lock, &bundle->mux))
|
|
return FALSE;
|
|
bundle->val = TRUE;
|
|
if (!run_cond_fkt(pthread_cond_signal, &bundle->cond))
|
|
rc = FALSE;
|
|
if (!run_mutex_fkt(pthread_mutex_checked_unlock, &bundle->mux))
|
|
rc = FALSE;
|
|
return rc;
|
|
}
|
|
|
|
static BOOL mux_condition_bundle_lock(mux_condition_bundle* bundle)
|
|
{
|
|
WINPR_ASSERT(bundle);
|
|
return run_mutex_fkt(pthread_mutex_lock, &bundle->mux);
|
|
}
|
|
|
|
static BOOL mux_condition_bundle_unlock(mux_condition_bundle* bundle)
|
|
{
|
|
WINPR_ASSERT(bundle);
|
|
return run_mutex_fkt(pthread_mutex_checked_unlock, &bundle->mux);
|
|
}
|
|
|
|
static BOOL mux_condition_bundle_wait(mux_condition_bundle* bundle, const char* name)
|
|
{
|
|
BOOL rc = FALSE;
|
|
|
|
WINPR_ASSERT(bundle);
|
|
WINPR_ASSERT(name);
|
|
WINPR_ASSERT(pthread_mutex_trylock(&bundle->mux) == EBUSY);
|
|
|
|
while (!bundle->val)
|
|
{
|
|
int r = pthread_cond_wait(&bundle->cond, &bundle->mux);
|
|
if (r != 0)
|
|
{
|
|
char ebuffer[256] = { 0 };
|
|
WLog_ERR(TAG, "failed to wait for %s [%s]", name,
|
|
winpr_strerror(r, ebuffer, sizeof(ebuffer)));
|
|
switch (r)
|
|
{
|
|
case ENOTRECOVERABLE:
|
|
case EPERM:
|
|
case ETIMEDOUT:
|
|
case EINVAL:
|
|
goto fail;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
rc = bundle->val;
|
|
|
|
fail:
|
|
return rc;
|
|
}
|
|
|
|
static BOOL signal_thread_ready(WINPR_THREAD* thread)
|
|
{
|
|
WINPR_ASSERT(thread);
|
|
|
|
return mux_condition_bundle_signal(&thread->isCreated);
|
|
}
|
|
|
|
static BOOL signal_thread_is_running(WINPR_THREAD* thread)
|
|
{
|
|
WINPR_ASSERT(thread);
|
|
|
|
return mux_condition_bundle_signal(&thread->isRunning);
|
|
}
|
|
|
|
static DWORD ThreadCleanupHandle(HANDLE handle)
|
|
{
|
|
DWORD status = WAIT_FAILED;
|
|
WINPR_THREAD* thread = (WINPR_THREAD*)handle;
|
|
|
|
if (!ThreadIsHandled(handle))
|
|
return WAIT_FAILED;
|
|
|
|
if (!run_mutex_fkt(pthread_mutex_lock, &thread->mutex))
|
|
return WAIT_FAILED;
|
|
|
|
if (!thread->joined)
|
|
{
|
|
int rc = pthread_join(thread->thread, NULL);
|
|
|
|
if (rc != 0)
|
|
{
|
|
char ebuffer[256] = { 0 };
|
|
WLog_ERR(TAG, "pthread_join failure: [%d] %s", rc,
|
|
winpr_strerror(rc, ebuffer, sizeof(ebuffer)));
|
|
goto fail;
|
|
}
|
|
else
|
|
thread->joined = TRUE;
|
|
}
|
|
|
|
status = WAIT_OBJECT_0;
|
|
|
|
fail:
|
|
if (!run_mutex_fkt(pthread_mutex_checked_unlock, &thread->mutex))
|
|
return WAIT_FAILED;
|
|
|
|
return status;
|
|
}
|
|
|
|
static HANDLE_OPS ops = { ThreadIsHandled,
|
|
ThreadCloseHandle,
|
|
ThreadGetFd,
|
|
ThreadCleanupHandle,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL };
|
|
|
|
static void dump_thread(WINPR_THREAD* thread)
|
|
{
|
|
#if defined(WITH_DEBUG_THREADS)
|
|
void* stack = winpr_backtrace(20);
|
|
char** msg = NULL;
|
|
size_t used = 0;
|
|
WLog_DBG(TAG, "Called from:");
|
|
msg = winpr_backtrace_symbols(stack, &used);
|
|
|
|
for (size_t i = 0; i < used; i++)
|
|
WLog_DBG(TAG, "[%" PRIdz "]: %s", i, msg[i]);
|
|
|
|
free(msg);
|
|
winpr_backtrace_free(stack);
|
|
WLog_DBG(TAG, "Thread handle created still not closed!");
|
|
msg = winpr_backtrace_symbols(thread->create_stack, &used);
|
|
|
|
for (size_t i = 0; i < used; i++)
|
|
WLog_DBG(TAG, "[%" PRIdz "]: %s", i, msg[i]);
|
|
|
|
free(msg);
|
|
|
|
if (thread->started)
|
|
{
|
|
WLog_DBG(TAG, "Thread still running!");
|
|
}
|
|
else if (!thread->exit_stack)
|
|
{
|
|
WLog_DBG(TAG, "Thread suspended.");
|
|
}
|
|
else
|
|
{
|
|
WLog_DBG(TAG, "Thread exited at:");
|
|
msg = winpr_backtrace_symbols(thread->exit_stack, &used);
|
|
|
|
for (size_t i = 0; i < used; i++)
|
|
WLog_DBG(TAG, "[%" PRIdz "]: %s", i, msg[i]);
|
|
|
|
free(msg);
|
|
}
|
|
#else
|
|
WINPR_UNUSED(thread);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* TODO: implement thread suspend/resume using pthreads
|
|
* http://stackoverflow.com/questions/3140867/suspend-pthreads-without-using-condition
|
|
*/
|
|
static BOOL set_event(WINPR_THREAD* thread)
|
|
{
|
|
return winpr_event_set(&thread->event);
|
|
}
|
|
|
|
static BOOL reset_event(WINPR_THREAD* thread)
|
|
{
|
|
return winpr_event_reset(&thread->event);
|
|
}
|
|
|
|
#if defined(WITH_THREAD_LIST)
|
|
static BOOL thread_compare(const void* a, const void* b)
|
|
{
|
|
const pthread_t* p1 = a;
|
|
const pthread_t* p2 = b;
|
|
BOOL rc = pthread_equal(*p1, *p2);
|
|
return rc;
|
|
}
|
|
#endif
|
|
|
|
static INIT_ONCE threads_InitOnce = INIT_ONCE_STATIC_INIT;
|
|
static pthread_t mainThreadId;
|
|
static DWORD currentThreadTlsIndex = TLS_OUT_OF_INDEXES;
|
|
|
|
static BOOL initializeThreads(PINIT_ONCE InitOnce, PVOID Parameter, PVOID* Context)
|
|
{
|
|
if (!apc_init(&mainThread.apc))
|
|
{
|
|
WLog_ERR(TAG, "failed to initialize APC");
|
|
goto out;
|
|
}
|
|
|
|
mainThread.common.Type = HANDLE_TYPE_THREAD;
|
|
mainThreadId = pthread_self();
|
|
|
|
currentThreadTlsIndex = TlsAlloc();
|
|
if (currentThreadTlsIndex == TLS_OUT_OF_INDEXES)
|
|
{
|
|
WLog_ERR(TAG, "Major bug, unable to allocate a TLS value for currentThread");
|
|
}
|
|
|
|
#if defined(WITH_THREAD_LIST)
|
|
thread_list = ListDictionary_New(TRUE);
|
|
|
|
if (!thread_list)
|
|
{
|
|
WLog_ERR(TAG, "Couldn't create global thread list");
|
|
goto error_thread_list;
|
|
}
|
|
|
|
thread_list->objectKey.fnObjectEquals = thread_compare;
|
|
#endif
|
|
|
|
out:
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL signal_and_wait_for_ready(WINPR_THREAD* thread)
|
|
{
|
|
BOOL res = FALSE;
|
|
|
|
WINPR_ASSERT(thread);
|
|
|
|
if (!mux_condition_bundle_lock(&thread->isRunning))
|
|
return FALSE;
|
|
|
|
if (!signal_thread_ready(thread))
|
|
goto fail;
|
|
|
|
if (!mux_condition_bundle_wait(&thread->isRunning, "threadIsRunning"))
|
|
goto fail;
|
|
|
|
#if defined(WITH_THREAD_LIST)
|
|
if (!ListDictionary_Contains(thread_list, &thread->thread))
|
|
{
|
|
WLog_ERR(TAG, "Thread not in thread_list, startup failed!");
|
|
goto fail;
|
|
}
|
|
#endif
|
|
|
|
res = TRUE;
|
|
|
|
fail:
|
|
if (!mux_condition_bundle_unlock(&thread->isRunning))
|
|
return FALSE;
|
|
|
|
return res;
|
|
}
|
|
|
|
/* Thread launcher function responsible for registering
|
|
* cleanup handlers and calling pthread_exit, if not done
|
|
* in thread function. */
|
|
static void* thread_launcher(void* arg)
|
|
{
|
|
DWORD rc = 0;
|
|
WINPR_THREAD* thread = (WINPR_THREAD*)arg;
|
|
LPTHREAD_START_ROUTINE fkt = NULL;
|
|
|
|
if (!thread)
|
|
{
|
|
WLog_ERR(TAG, "Called with invalid argument %p", arg);
|
|
goto exit;
|
|
}
|
|
|
|
if (!TlsSetValue(currentThreadTlsIndex, thread))
|
|
{
|
|
WLog_ERR(TAG, "thread %d, unable to set current thread value", pthread_self());
|
|
goto exit;
|
|
}
|
|
|
|
if (!(fkt = thread->lpStartAddress))
|
|
{
|
|
WLog_ERR(TAG, "Thread function argument is %p", (void*)fkt);
|
|
goto exit;
|
|
}
|
|
|
|
if (!signal_and_wait_for_ready(thread))
|
|
goto exit;
|
|
|
|
rc = fkt(thread->lpParameter);
|
|
exit:
|
|
|
|
if (thread)
|
|
{
|
|
apc_cleanupThread(thread);
|
|
|
|
if (!thread->exited)
|
|
thread->dwExitCode = rc;
|
|
|
|
set_event(thread);
|
|
|
|
signal_thread_ready(thread);
|
|
|
|
if (thread->detached || !thread->started)
|
|
cleanup_handle(thread);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static BOOL winpr_StartThread(WINPR_THREAD* thread)
|
|
{
|
|
BOOL rc = FALSE;
|
|
BOOL locked = FALSE;
|
|
pthread_attr_t attr = { 0 };
|
|
|
|
if (!mux_condition_bundle_lock(&thread->isCreated))
|
|
return FALSE;
|
|
locked = TRUE;
|
|
|
|
pthread_attr_init(&attr);
|
|
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
|
|
|
|
if (thread->dwStackSize > 0)
|
|
pthread_attr_setstacksize(&attr, (size_t)thread->dwStackSize);
|
|
|
|
thread->started = TRUE;
|
|
reset_event(thread);
|
|
|
|
#if defined(WITH_THREAD_LIST)
|
|
if (!ListDictionary_Add(thread_list, &thread->thread, thread))
|
|
{
|
|
WLog_ERR(TAG, "failed to add the thread to the thread list");
|
|
goto error;
|
|
}
|
|
#endif
|
|
|
|
if (pthread_create(&thread->thread, &attr, thread_launcher, thread))
|
|
goto error;
|
|
|
|
if (!mux_condition_bundle_wait(&thread->isCreated, "threadIsCreated"))
|
|
goto error;
|
|
|
|
locked = FALSE;
|
|
if (!mux_condition_bundle_unlock(&thread->isCreated))
|
|
goto error;
|
|
|
|
if (!signal_thread_is_running(thread))
|
|
{
|
|
WLog_ERR(TAG, "failed to signal the thread was ready");
|
|
goto error;
|
|
}
|
|
|
|
rc = TRUE;
|
|
error:
|
|
if (locked)
|
|
{
|
|
if (!mux_condition_bundle_unlock(&thread->isCreated))
|
|
rc = FALSE;
|
|
}
|
|
|
|
pthread_attr_destroy(&attr);
|
|
|
|
if (rc)
|
|
dump_thread(thread);
|
|
|
|
return rc;
|
|
}
|
|
|
|
BOOL SetThreadPriority(HANDLE hThread, int nPriority)
|
|
{
|
|
ULONG Type = 0;
|
|
WINPR_HANDLE* Object = NULL;
|
|
|
|
if (!winpr_Handle_GetInfo(hThread, &Type, &Object) || Object->Type != HANDLE_TYPE_THREAD)
|
|
return FALSE;
|
|
|
|
WINPR_THREAD* thread = (WINPR_THREAD*)Object;
|
|
|
|
const int min = 19;
|
|
const int max = 0;
|
|
const int diff = (max - min);
|
|
const int normal = min + diff / 2;
|
|
const int off = MIN(1, diff / 4);
|
|
int sched_priority = -1;
|
|
|
|
switch (nPriority & ~(THREAD_MODE_BACKGROUND_BEGIN | THREAD_MODE_BACKGROUND_END))
|
|
{
|
|
case THREAD_PRIORITY_ABOVE_NORMAL:
|
|
sched_priority = MIN(normal + off, max);
|
|
break;
|
|
case THREAD_PRIORITY_BELOW_NORMAL:
|
|
sched_priority = MAX(normal - off, min);
|
|
break;
|
|
case THREAD_PRIORITY_HIGHEST:
|
|
sched_priority = max;
|
|
break;
|
|
case THREAD_PRIORITY_IDLE:
|
|
sched_priority = min;
|
|
break;
|
|
case THREAD_PRIORITY_LOWEST:
|
|
sched_priority = min;
|
|
break;
|
|
case THREAD_PRIORITY_TIME_CRITICAL:
|
|
sched_priority = max;
|
|
break;
|
|
default:
|
|
case THREAD_PRIORITY_NORMAL:
|
|
sched_priority = normal;
|
|
break;
|
|
}
|
|
#if defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200809L) && defined(PTHREAD_SETSCHEDPRIO)
|
|
const int rc = pthread_setschedprio(thread->thread, sched_priority);
|
|
if (rc != 0)
|
|
WLog_ERR(TAG, "pthread_setschedprio(%d) %s [%d]", sched_priority, strerror(rc), rc);
|
|
return rc == 0;
|
|
#else
|
|
WLog_WARN(TAG, "pthread_setschedprio(%d) not implemented, requires POSIX 2008 or later",
|
|
sched_priority);
|
|
return TRUE;
|
|
#endif
|
|
}
|
|
|
|
HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize,
|
|
LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter,
|
|
DWORD dwCreationFlags, LPDWORD lpThreadId)
|
|
{
|
|
HANDLE handle = NULL;
|
|
WINPR_THREAD* thread = (WINPR_THREAD*)calloc(1, sizeof(WINPR_THREAD));
|
|
|
|
if (!thread)
|
|
return NULL;
|
|
|
|
thread->dwStackSize = dwStackSize;
|
|
thread->lpParameter = lpParameter;
|
|
thread->lpStartAddress = lpStartAddress;
|
|
thread->lpThreadAttributes = lpThreadAttributes;
|
|
thread->common.ops = &ops;
|
|
#if defined(WITH_DEBUG_THREADS)
|
|
thread->create_stack = winpr_backtrace(20);
|
|
dump_thread(thread);
|
|
#endif
|
|
|
|
if (!winpr_event_init(&thread->event))
|
|
{
|
|
WLog_ERR(TAG, "failed to create event");
|
|
goto fail;
|
|
}
|
|
|
|
if (!run_mutex_init(pthread_mutex_init, &thread->mutex, NULL))
|
|
{
|
|
WLog_ERR(TAG, "failed to initialize thread mutex");
|
|
goto fail;
|
|
}
|
|
|
|
if (!apc_init(&thread->apc))
|
|
{
|
|
WLog_ERR(TAG, "failed to initialize APC");
|
|
goto fail;
|
|
}
|
|
|
|
if (!mux_condition_bundle_init(&thread->isCreated))
|
|
goto fail;
|
|
if (!mux_condition_bundle_init(&thread->isRunning))
|
|
goto fail;
|
|
|
|
WINPR_HANDLE_SET_TYPE_AND_MODE(thread, HANDLE_TYPE_THREAD, WINPR_FD_READ);
|
|
handle = (HANDLE)thread;
|
|
|
|
InitOnceExecuteOnce(&threads_InitOnce, initializeThreads, NULL, NULL);
|
|
|
|
if (!(dwCreationFlags & CREATE_SUSPENDED))
|
|
{
|
|
if (!winpr_StartThread(thread))
|
|
goto fail;
|
|
}
|
|
else
|
|
{
|
|
if (!set_event(thread))
|
|
goto fail;
|
|
}
|
|
|
|
return handle;
|
|
fail:
|
|
cleanup_handle(thread);
|
|
return NULL;
|
|
}
|
|
|
|
void cleanup_handle(void* obj)
|
|
{
|
|
WINPR_THREAD* thread = (WINPR_THREAD*)obj;
|
|
if (!thread)
|
|
return;
|
|
|
|
if (!apc_uninit(&thread->apc))
|
|
WLog_ERR(TAG, "failed to destroy APC");
|
|
|
|
mux_condition_bundle_uninit(&thread->isCreated);
|
|
mux_condition_bundle_uninit(&thread->isRunning);
|
|
run_mutex_fkt(pthread_mutex_destroy, &thread->mutex);
|
|
|
|
winpr_event_uninit(&thread->event);
|
|
|
|
#if defined(WITH_THREAD_LIST)
|
|
ListDictionary_Remove(thread_list, &thread->thread);
|
|
#endif
|
|
#if defined(WITH_DEBUG_THREADS)
|
|
|
|
if (thread->create_stack)
|
|
winpr_backtrace_free(thread->create_stack);
|
|
|
|
if (thread->exit_stack)
|
|
winpr_backtrace_free(thread->exit_stack);
|
|
|
|
#endif
|
|
free(thread);
|
|
}
|
|
|
|
BOOL ThreadCloseHandle(HANDLE handle)
|
|
{
|
|
WINPR_THREAD* thread = (WINPR_THREAD*)handle;
|
|
|
|
#if defined(WITH_THREAD_LIST)
|
|
if (!thread_list)
|
|
{
|
|
WLog_ERR(TAG, "Thread list does not exist, check call!");
|
|
dump_thread(thread);
|
|
}
|
|
else if (!ListDictionary_Contains(thread_list, &thread->thread))
|
|
{
|
|
WLog_ERR(TAG, "Thread list does not contain this thread! check call!");
|
|
dump_thread(thread);
|
|
}
|
|
else
|
|
{
|
|
ListDictionary_Lock(thread_list);
|
|
#endif
|
|
dump_thread(thread);
|
|
|
|
if ((thread->started) && (WaitForSingleObject(thread, 0) != WAIT_OBJECT_0))
|
|
{
|
|
WLog_DBG(TAG, "Thread running, setting to detached state!");
|
|
thread->detached = TRUE;
|
|
pthread_detach(thread->thread);
|
|
}
|
|
else
|
|
{
|
|
cleanup_handle(thread);
|
|
}
|
|
|
|
#if defined(WITH_THREAD_LIST)
|
|
ListDictionary_Unlock(thread_list);
|
|
}
|
|
#endif
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
HANDLE CreateRemoteThread(HANDLE hProcess, LPSECURITY_ATTRIBUTES lpThreadAttributes,
|
|
SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress,
|
|
LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId)
|
|
{
|
|
WLog_ERR(TAG, "not implemented");
|
|
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
|
|
return NULL;
|
|
}
|
|
|
|
VOID ExitThread(DWORD dwExitCode)
|
|
{
|
|
#if defined(WITH_THREAD_LIST)
|
|
DWORD rc;
|
|
pthread_t tid = pthread_self();
|
|
|
|
if (!thread_list)
|
|
{
|
|
WLog_ERR(TAG, "function called without existing thread list!");
|
|
#if defined(WITH_DEBUG_THREADS)
|
|
DumpThreadHandles();
|
|
#endif
|
|
pthread_exit(0);
|
|
}
|
|
else if (!ListDictionary_Contains(thread_list, &tid))
|
|
{
|
|
WLog_ERR(TAG, "function called, but no matching entry in thread list!");
|
|
#if defined(WITH_DEBUG_THREADS)
|
|
DumpThreadHandles();
|
|
#endif
|
|
pthread_exit(0);
|
|
}
|
|
else
|
|
{
|
|
WINPR_THREAD* thread;
|
|
ListDictionary_Lock(thread_list);
|
|
thread = ListDictionary_GetItemValue(thread_list, &tid);
|
|
WINPR_ASSERT(thread);
|
|
thread->exited = TRUE;
|
|
thread->dwExitCode = dwExitCode;
|
|
#if defined(WITH_DEBUG_THREADS)
|
|
thread->exit_stack = winpr_backtrace(20);
|
|
#endif
|
|
ListDictionary_Unlock(thread_list);
|
|
set_event(thread);
|
|
rc = thread->dwExitCode;
|
|
|
|
if (thread->detached || !thread->started)
|
|
cleanup_handle(thread);
|
|
|
|
pthread_exit((void*)(size_t)rc);
|
|
}
|
|
#else
|
|
WINPR_UNUSED(dwExitCode);
|
|
#endif
|
|
}
|
|
|
|
BOOL GetExitCodeThread(HANDLE hThread, LPDWORD lpExitCode)
|
|
{
|
|
ULONG Type = 0;
|
|
WINPR_HANDLE* Object = NULL;
|
|
WINPR_THREAD* thread = NULL;
|
|
|
|
if (!winpr_Handle_GetInfo(hThread, &Type, &Object) || Object->Type != HANDLE_TYPE_THREAD)
|
|
{
|
|
WLog_ERR(TAG, "hThread is not a thread");
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return FALSE;
|
|
}
|
|
|
|
thread = (WINPR_THREAD*)Object;
|
|
*lpExitCode = thread->dwExitCode;
|
|
return TRUE;
|
|
}
|
|
|
|
WINPR_THREAD* winpr_GetCurrentThread(VOID)
|
|
{
|
|
WINPR_THREAD* ret = NULL;
|
|
|
|
InitOnceExecuteOnce(&threads_InitOnce, initializeThreads, NULL, NULL);
|
|
if (mainThreadId == pthread_self())
|
|
return (HANDLE)&mainThread;
|
|
|
|
ret = TlsGetValue(currentThreadTlsIndex);
|
|
return ret;
|
|
}
|
|
|
|
HANDLE _GetCurrentThread(VOID)
|
|
{
|
|
return (HANDLE)winpr_GetCurrentThread();
|
|
}
|
|
|
|
DWORD GetCurrentThreadId(VOID)
|
|
{
|
|
pthread_t tid = 0;
|
|
tid = pthread_self();
|
|
/* Since pthread_t can be 64-bits on some systems, take just the */
|
|
/* lower 32-bits of it for the thread ID returned by this function. */
|
|
return (DWORD)tid & 0xffffffffUL;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
WINPR_APC_ITEM apc;
|
|
PAPCFUNC completion;
|
|
ULONG_PTR completionArg;
|
|
} UserApcItem;
|
|
|
|
static void userAPC(LPVOID arg)
|
|
{
|
|
UserApcItem* userApc = (UserApcItem*)arg;
|
|
|
|
userApc->completion(userApc->completionArg);
|
|
|
|
userApc->apc.markedForRemove = TRUE;
|
|
}
|
|
|
|
DWORD QueueUserAPC(PAPCFUNC pfnAPC, HANDLE hThread, ULONG_PTR dwData)
|
|
{
|
|
ULONG Type = 0;
|
|
WINPR_HANDLE* Object = NULL;
|
|
WINPR_APC_ITEM* apc = NULL;
|
|
UserApcItem* apcItem = NULL;
|
|
|
|
if (!pfnAPC)
|
|
return 1;
|
|
|
|
if (!winpr_Handle_GetInfo(hThread, &Type, &Object) || Object->Type != HANDLE_TYPE_THREAD)
|
|
{
|
|
WLog_ERR(TAG, "hThread is not a thread");
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return (DWORD)0;
|
|
}
|
|
|
|
apcItem = calloc(1, sizeof(*apcItem));
|
|
if (!apcItem)
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return (DWORD)0;
|
|
}
|
|
|
|
apc = &apcItem->apc;
|
|
apc->type = APC_TYPE_USER;
|
|
apc->markedForFree = TRUE;
|
|
apc->alwaysSignaled = TRUE;
|
|
apc->completion = userAPC;
|
|
apc->completionArgs = apc;
|
|
apcItem->completion = pfnAPC;
|
|
apcItem->completionArg = dwData;
|
|
apc_register(hThread, apc);
|
|
return 1;
|
|
}
|
|
|
|
DWORD ResumeThread(HANDLE hThread)
|
|
{
|
|
ULONG Type = 0;
|
|
WINPR_HANDLE* Object = NULL;
|
|
WINPR_THREAD* thread = NULL;
|
|
|
|
if (!winpr_Handle_GetInfo(hThread, &Type, &Object) || Object->Type != HANDLE_TYPE_THREAD)
|
|
{
|
|
WLog_ERR(TAG, "hThread is not a thread");
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return (DWORD)-1;
|
|
}
|
|
|
|
thread = (WINPR_THREAD*)Object;
|
|
|
|
if (!run_mutex_fkt(pthread_mutex_lock, &thread->mutex))
|
|
return (DWORD)-1;
|
|
|
|
if (!thread->started)
|
|
{
|
|
if (!winpr_StartThread(thread))
|
|
{
|
|
run_mutex_fkt(pthread_mutex_checked_unlock, &thread->mutex);
|
|
return (DWORD)-1;
|
|
}
|
|
}
|
|
else
|
|
WLog_WARN(TAG, "Thread already started!");
|
|
|
|
if (!run_mutex_fkt(pthread_mutex_checked_unlock, &thread->mutex))
|
|
return (DWORD)-1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
DWORD SuspendThread(HANDLE hThread)
|
|
{
|
|
WLog_ERR(TAG, "not implemented");
|
|
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
|
|
return (DWORD)-1;
|
|
}
|
|
|
|
BOOL SwitchToThread(VOID)
|
|
{
|
|
/**
|
|
* Note: on some operating systems sched_yield is a stub returning -1.
|
|
* usleep should at least trigger a context switch if any thread is waiting.
|
|
*/
|
|
if (sched_yield() != 0)
|
|
usleep(1);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL TerminateThread(HANDLE hThread, DWORD dwExitCode)
|
|
{
|
|
ULONG Type = 0;
|
|
WINPR_HANDLE* Object = NULL;
|
|
WINPR_THREAD* thread = NULL;
|
|
|
|
if (!winpr_Handle_GetInfo(hThread, &Type, &Object) || Object->Type != HANDLE_TYPE_THREAD)
|
|
return FALSE;
|
|
|
|
thread = (WINPR_THREAD*)Object;
|
|
thread->exited = TRUE;
|
|
thread->dwExitCode = dwExitCode;
|
|
|
|
if (!run_mutex_fkt(pthread_mutex_lock, &thread->mutex))
|
|
return FALSE;
|
|
|
|
#ifndef ANDROID
|
|
pthread_cancel(thread->thread);
|
|
#else
|
|
WLog_ERR(TAG, "Function not supported on this platform!");
|
|
#endif
|
|
|
|
if (!run_mutex_fkt(pthread_mutex_checked_unlock, &thread->mutex))
|
|
return FALSE;
|
|
|
|
set_event(thread);
|
|
return TRUE;
|
|
}
|
|
|
|
VOID DumpThreadHandles(void)
|
|
{
|
|
#if defined(WITH_DEBUG_THREADS)
|
|
char** msg = NULL;
|
|
size_t used = 0;
|
|
void* stack = winpr_backtrace(20);
|
|
WLog_DBG(TAG, "---------------- Called from ----------------------------");
|
|
msg = winpr_backtrace_symbols(stack, &used);
|
|
|
|
for (size_t i = 0; i < used; i++)
|
|
{
|
|
WLog_DBG(TAG, "[%" PRIdz "]: %s", i, msg[i]);
|
|
}
|
|
|
|
free(msg);
|
|
winpr_backtrace_free(stack);
|
|
WLog_DBG(TAG, "---------------- Start Dumping thread handles -----------");
|
|
|
|
#if defined(WITH_THREAD_LIST)
|
|
if (!thread_list)
|
|
{
|
|
WLog_DBG(TAG, "All threads properly shut down and disposed of.");
|
|
}
|
|
else
|
|
{
|
|
ULONG_PTR* keys = NULL;
|
|
ListDictionary_Lock(thread_list);
|
|
int x, count = ListDictionary_GetKeys(thread_list, &keys);
|
|
WLog_DBG(TAG, "Dumping %d elements", count);
|
|
|
|
for (size_t x = 0; x < count; x++)
|
|
{
|
|
WINPR_THREAD* thread = ListDictionary_GetItemValue(thread_list, (void*)keys[x]);
|
|
WLog_DBG(TAG, "Thread [%d] handle created still not closed!", x);
|
|
msg = winpr_backtrace_symbols(thread->create_stack, &used);
|
|
|
|
for (size_t i = 0; i < used; i++)
|
|
{
|
|
WLog_DBG(TAG, "[%" PRIdz "]: %s", i, msg[i]);
|
|
}
|
|
|
|
free(msg);
|
|
|
|
if (thread->started)
|
|
{
|
|
WLog_DBG(TAG, "Thread [%d] still running!", x);
|
|
}
|
|
else
|
|
{
|
|
WLog_DBG(TAG, "Thread [%d] exited at:", x);
|
|
msg = winpr_backtrace_symbols(thread->exit_stack, &used);
|
|
|
|
for (size_t i = 0; i < used; i++)
|
|
WLog_DBG(TAG, "[%" PRIdz "]: %s", i, msg[i]);
|
|
|
|
free(msg);
|
|
}
|
|
}
|
|
|
|
free(keys);
|
|
ListDictionary_Unlock(thread_list);
|
|
}
|
|
#endif
|
|
|
|
WLog_DBG(TAG, "---------------- End Dumping thread handles -------------");
|
|
#endif
|
|
}
|
|
#endif
|