summaryrefslogtreecommitdiffstats
path: root/winpr/libwinpr/thread
diff options
context:
space:
mode:
Diffstat (limited to 'winpr/libwinpr/thread')
-rw-r--r--winpr/libwinpr/thread/CMakeLists.txt34
-rw-r--r--winpr/libwinpr/thread/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/thread/apc.c271
-rw-r--r--winpr/libwinpr/thread/apc.h85
-rw-r--r--winpr/libwinpr/thread/argv.c279
-rw-r--r--winpr/libwinpr/thread/process.c598
-rw-r--r--winpr/libwinpr/thread/processor.c41
-rw-r--r--winpr/libwinpr/thread/test/CMakeLists.txt27
-rw-r--r--winpr/libwinpr/thread/test/TestThreadCommandLineToArgv.c74
-rw-r--r--winpr/libwinpr/thread/test/TestThreadCreateProcess.c156
-rw-r--r--winpr/libwinpr/thread/test/TestThreadExitThread.c53
-rw-r--r--winpr/libwinpr/thread/thread.c1045
-rw-r--r--winpr/libwinpr/thread/thread.h95
-rw-r--r--winpr/libwinpr/thread/tls.c72
14 files changed, 2839 insertions, 0 deletions
diff --git a/winpr/libwinpr/thread/CMakeLists.txt b/winpr/libwinpr/thread/CMakeLists.txt
new file mode 100644
index 0000000..bfc04dd
--- /dev/null
+++ b/winpr/libwinpr/thread/CMakeLists.txt
@@ -0,0 +1,34 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-thread cmake build script
+#
+# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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.
+
+winpr_module_add(
+ apc.h
+ apc.c
+ argv.c
+ process.c
+ processor.c
+ thread.c
+ thread.h
+ tls.c)
+
+if(${CMAKE_SYSTEM_NAME} MATCHES SunOS)
+ winpr_library_add_private(rt)
+endif()
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/winpr/libwinpr/thread/ModuleOptions.cmake b/winpr/libwinpr/thread/ModuleOptions.cmake
new file mode 100644
index 0000000..ae52dd9
--- /dev/null
+++ b/winpr/libwinpr/thread/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "core")
+set(MINWIN_MAJOR_VERSION "1")
+set(MINWIN_MINOR_VERSION "1")
+set(MINWIN_SHORT_NAME "processthreads")
+set(MINWIN_LONG_NAME "Process and Thread Functions")
+set(MODULE_LIBRARY_NAME "api-ms-win-${MINWIN_GROUP}-${MINWIN_SHORT_NAME}-l${MINWIN_LAYER}-${MINWIN_MAJOR_VERSION}-${MINWIN_MINOR_VERSION}")
+
diff --git a/winpr/libwinpr/thread/apc.c b/winpr/libwinpr/thread/apc.c
new file mode 100644
index 0000000..96ff8c3
--- /dev/null
+++ b/winpr/libwinpr/thread/apc.c
@@ -0,0 +1,271 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * APC implementation
+ *
+ * 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.
+ */
+#ifndef _WIN32
+
+#include "apc.h"
+#include "thread.h"
+#include "../log.h"
+#include "../synch/pollset.h"
+#include <winpr/assert.h>
+
+#define TAG WINPR_TAG("apc")
+
+BOOL apc_init(APC_QUEUE* apc)
+{
+ pthread_mutexattr_t attr;
+ BOOL ret = FALSE;
+
+ WINPR_ASSERT(apc);
+
+ pthread_mutexattr_init(&attr);
+ if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0)
+ {
+ WLog_ERR(TAG, "failed to initialize mutex attributes to recursive");
+ return FALSE;
+ }
+
+ memset(apc, 0, sizeof(*apc));
+
+ if (pthread_mutex_init(&apc->mutex, &attr) != 0)
+ {
+ WLog_ERR(TAG, "failed to initialize main thread APC mutex");
+ goto out;
+ }
+
+ ret = TRUE;
+out:
+ pthread_mutexattr_destroy(&attr);
+ return ret;
+}
+
+BOOL apc_uninit(APC_QUEUE* apc)
+{
+ WINPR_ASSERT(apc);
+ return pthread_mutex_destroy(&apc->mutex) == 0;
+}
+
+void apc_register(WINPR_THREAD* thread, WINPR_APC_ITEM* addItem)
+{
+ WINPR_APC_ITEM** nextp = NULL;
+ APC_QUEUE* apc = NULL;
+
+ WINPR_ASSERT(thread);
+ WINPR_ASSERT(addItem);
+
+ apc = &thread->apc;
+ WINPR_ASSERT(apc);
+
+ pthread_mutex_lock(&apc->mutex);
+ if (apc->tail)
+ {
+ nextp = &apc->tail->next;
+ addItem->last = apc->tail;
+ }
+ else
+ {
+ nextp = &apc->head;
+ }
+
+ *nextp = addItem;
+ apc->tail = addItem;
+ apc->length++;
+
+ addItem->markedForRemove = FALSE;
+ addItem->boundThread = GetCurrentThreadId();
+ addItem->linked = TRUE;
+ pthread_mutex_unlock(&apc->mutex);
+}
+
+static INLINE void apc_item_remove(APC_QUEUE* apc, WINPR_APC_ITEM* item)
+{
+ WINPR_ASSERT(apc);
+ WINPR_ASSERT(item);
+
+ if (!item->last)
+ apc->head = item->next;
+ else
+ item->last->next = item->next;
+
+ if (!item->next)
+ apc->tail = item->last;
+ else
+ item->next->last = item->last;
+
+ apc->length--;
+}
+
+APC_REMOVE_RESULT apc_remove(WINPR_APC_ITEM* item)
+{
+ WINPR_THREAD* thread = winpr_GetCurrentThread();
+ APC_QUEUE* apc = NULL;
+ APC_REMOVE_RESULT ret = APC_REMOVE_OK;
+
+ WINPR_ASSERT(item);
+
+ if (!item->linked)
+ return APC_REMOVE_OK;
+
+ if (item->boundThread != GetCurrentThreadId())
+ {
+ WLog_ERR(TAG, "removing an APC entry should be done in the creating thread");
+ return APC_REMOVE_ERROR;
+ }
+
+ if (!thread)
+ {
+ WLog_ERR(TAG, "unable to retrieve current thread");
+ return APC_REMOVE_ERROR;
+ }
+
+ apc = &thread->apc;
+ WINPR_ASSERT(apc);
+
+ pthread_mutex_lock(&apc->mutex);
+ if (apc->treatingCompletions)
+ {
+ item->markedForRemove = TRUE;
+ ret = APC_REMOVE_DELAY_FREE;
+ goto out;
+ }
+
+ apc_item_remove(apc, item);
+
+out:
+ pthread_mutex_unlock(&apc->mutex);
+ item->boundThread = 0xFFFFFFFF;
+ item->linked = FALSE;
+ return ret;
+}
+
+BOOL apc_collectFds(WINPR_THREAD* thread, WINPR_POLL_SET* set, BOOL* haveAutoSignaled)
+{
+ WINPR_APC_ITEM* item = NULL;
+ BOOL ret = FALSE;
+ APC_QUEUE* apc = NULL;
+
+ WINPR_ASSERT(thread);
+ WINPR_ASSERT(haveAutoSignaled);
+
+ apc = &thread->apc;
+ WINPR_ASSERT(apc);
+
+ *haveAutoSignaled = FALSE;
+ pthread_mutex_lock(&apc->mutex);
+ item = apc->head;
+ for (; item; item = item->next)
+ {
+ if (item->alwaysSignaled)
+ {
+ *haveAutoSignaled = TRUE;
+ }
+ else if (!pollset_add(set, item->pollFd, item->pollMode))
+ goto out;
+ }
+
+ ret = TRUE;
+out:
+ pthread_mutex_unlock(&apc->mutex);
+ return ret;
+}
+
+int apc_executeCompletions(WINPR_THREAD* thread, WINPR_POLL_SET* set, size_t idx)
+{
+ APC_QUEUE* apc = NULL;
+ WINPR_APC_ITEM* nextItem = NULL;
+ int ret = 0;
+
+ WINPR_ASSERT(thread);
+
+ apc = &thread->apc;
+ WINPR_ASSERT(apc);
+
+ pthread_mutex_lock(&apc->mutex);
+ apc->treatingCompletions = TRUE;
+
+ /* first pass to compute signaled items */
+ for (WINPR_APC_ITEM* item = apc->head; item; item = item->next)
+ {
+ item->isSignaled = item->alwaysSignaled || pollset_isSignaled(set, idx);
+ if (!item->alwaysSignaled)
+ idx++;
+ }
+
+ /* second pass: run completions */
+ for (WINPR_APC_ITEM* item = apc->head; item; item = nextItem)
+ {
+ if (item->isSignaled)
+ {
+ if (item->completion && !item->markedForRemove)
+ item->completion(item->completionArgs);
+ ret++;
+ }
+
+ nextItem = item->next;
+ }
+
+ /* third pass: to do final cleanup */
+ for (WINPR_APC_ITEM* item = apc->head; item; item = nextItem)
+ {
+ nextItem = item->next;
+
+ if (item->markedForRemove)
+ {
+ apc_item_remove(apc, item);
+ if (item->markedForFree)
+ free(item);
+ }
+ }
+
+ apc->treatingCompletions = FALSE;
+ pthread_mutex_unlock(&apc->mutex);
+
+ return ret;
+}
+
+void apc_cleanupThread(WINPR_THREAD* thread)
+{
+ WINPR_APC_ITEM* item = NULL;
+ WINPR_APC_ITEM* nextItem = NULL;
+ APC_QUEUE* apc = NULL;
+
+ WINPR_ASSERT(thread);
+
+ apc = &thread->apc;
+ WINPR_ASSERT(apc);
+
+ pthread_mutex_lock(&apc->mutex);
+ item = apc->head;
+ for (; item; item = nextItem)
+ {
+ nextItem = item->next;
+
+ if (item->type == APC_TYPE_HANDLE_FREE)
+ item->completion(item->completionArgs);
+
+ item->last = item->next = NULL;
+ item->linked = FALSE;
+ if (item->markedForFree)
+ free(item);
+ }
+
+ apc->head = apc->tail = NULL;
+ pthread_mutex_unlock(&apc->mutex);
+}
+
+#endif
diff --git a/winpr/libwinpr/thread/apc.h b/winpr/libwinpr/thread/apc.h
new file mode 100644
index 0000000..c69920d
--- /dev/null
+++ b/winpr/libwinpr/thread/apc.h
@@ -0,0 +1,85 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * APC implementation
+ *
+ * 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.
+ */
+
+#ifndef WINPR_APC_H
+#define WINPR_APC_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+
+#ifndef _WIN32
+
+#include <pthread.h>
+
+typedef struct winpr_thread WINPR_THREAD;
+typedef struct winpr_APC_item WINPR_APC_ITEM;
+typedef struct winpr_poll_set WINPR_POLL_SET;
+
+typedef void (*apc_treatment)(LPVOID arg);
+
+typedef enum
+{
+ APC_TYPE_USER,
+ APC_TYPE_TIMER,
+ APC_TYPE_HANDLE_FREE
+} ApcType;
+
+struct winpr_APC_item
+{
+ ApcType type;
+ int pollFd;
+ DWORD pollMode;
+ apc_treatment completion;
+ LPVOID completionArgs;
+ BOOL markedForFree;
+
+ /* private fields used by the APC */
+ BOOL alwaysSignaled;
+ BOOL isSignaled;
+ DWORD boundThread;
+ BOOL linked;
+ BOOL markedForRemove;
+ WINPR_APC_ITEM *last, *next;
+};
+
+typedef enum
+{
+ APC_REMOVE_OK,
+ APC_REMOVE_ERROR,
+ APC_REMOVE_DELAY_FREE
+} APC_REMOVE_RESULT;
+
+typedef struct
+{
+ pthread_mutex_t mutex;
+ DWORD length;
+ WINPR_APC_ITEM *head, *tail;
+ BOOL treatingCompletions;
+} APC_QUEUE;
+
+BOOL apc_init(APC_QUEUE* apc);
+BOOL apc_uninit(APC_QUEUE* apc);
+void apc_register(WINPR_THREAD* thread, WINPR_APC_ITEM* addItem);
+APC_REMOVE_RESULT apc_remove(WINPR_APC_ITEM* item);
+BOOL apc_collectFds(WINPR_THREAD* thread, WINPR_POLL_SET* set, BOOL* haveAutoSignaled);
+int apc_executeCompletions(WINPR_THREAD* thread, WINPR_POLL_SET* set, size_t startIndex);
+void apc_cleanupThread(WINPR_THREAD* thread);
+#endif
+
+#endif /* WINPR_APC_H */
diff --git a/winpr/libwinpr/thread/argv.c b/winpr/libwinpr/thread/argv.c
new file mode 100644
index 0000000..3eedc41
--- /dev/null
+++ b/winpr/libwinpr/thread/argv.c
@@ -0,0 +1,279 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Process Argument Vector Functions
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.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/crt.h>
+#include <winpr/handle.h>
+
+#include <winpr/thread.h>
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "../log.h"
+#define TAG WINPR_TAG("thread")
+
+/**
+ * CommandLineToArgvW function:
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/bb776391/
+ *
+ * CommandLineToArgvW has a special interpretation of backslash characters
+ * when they are followed by a quotation mark character ("), as follows:
+ *
+ * 2n backslashes followed by a quotation mark produce n backslashes followed by a quotation mark.
+ * (2n) + 1 backslashes followed by a quotation mark again produce n backslashes followed by a
+ * quotation mark. n backslashes not followed by a quotation mark simply produce n backslashes.
+ *
+ * The address returned by CommandLineToArgvW is the address of the first element in an array of
+ * LPWSTR values; the number of pointers in this array is indicated by pNumArgs. Each pointer to a
+ * null-terminated Unicode string represents an individual argument found on the command line.
+ *
+ * CommandLineToArgvW allocates a block of contiguous memory for pointers to the argument strings,
+ * and for the argument strings themselves; the calling application must free the memory used by the
+ * argument list when it is no longer needed. To free the memory, use a single call to the LocalFree
+ * function.
+ */
+
+/**
+ * Parsing C++ Command-Line Arguments:
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/17w5ykft
+ *
+ * Microsoft C/C++ startup code uses the following rules when
+ * interpreting arguments given on the operating system command line:
+ *
+ * Arguments are delimited by white space, which is either a space or a tab.
+ *
+ * The caret character (^) is not recognized as an escape character or delimiter.
+ * The character is handled completely by the command-line parser in the operating
+ * system before being passed to the argv array in the program.
+ *
+ * A string surrounded by double quotation marks ("string") is interpreted as a
+ * single argument, regardless of white space contained within. A quoted string
+ * can be embedded in an argument.
+ *
+ * A double quotation mark preceded by a backslash (\") is interpreted as a
+ * literal double quotation mark character (").
+ *
+ * Backslashes are interpreted literally, unless they immediately
+ * precede a double quotation mark.
+ *
+ * If an even number of backslashes is followed by a double quotation mark,
+ * one backslash is placed in the argv array for every pair of backslashes,
+ * and the double quotation mark is interpreted as a string delimiter.
+ *
+ * If an odd number of backslashes is followed by a double quotation mark,
+ * one backslash is placed in the argv array for every pair of backslashes,
+ * and the double quotation mark is "escaped" by the remaining backslash,
+ * causing a literal double quotation mark (") to be placed in argv.
+ *
+ */
+
+LPSTR* CommandLineToArgvA(LPCSTR lpCmdLine, int* pNumArgs)
+{
+ const char* p = NULL;
+ size_t length = 0;
+ const char* pBeg = NULL;
+ const char* pEnd = NULL;
+ char* buffer = NULL;
+ char* pOutput = NULL;
+ int numArgs = 0;
+ LPSTR* pArgs = NULL;
+ size_t maxNumArgs = 0;
+ size_t maxBufferSize = 0;
+ size_t cmdLineLength = 0;
+ BOOL* lpEscapedChars = NULL;
+ LPSTR lpEscapedCmdLine = NULL;
+
+ if (!lpCmdLine)
+ return NULL;
+
+ if (!pNumArgs)
+ return NULL;
+
+ pArgs = NULL;
+ lpEscapedCmdLine = NULL;
+ cmdLineLength = strlen(lpCmdLine);
+ lpEscapedChars = (BOOL*)calloc(cmdLineLength + 1, sizeof(BOOL));
+
+ if (!lpEscapedChars)
+ return NULL;
+
+ if (strstr(lpCmdLine, "\\\""))
+ {
+ size_t n = 0;
+ const char* pLastEnd = NULL;
+ lpEscapedCmdLine = (char*)calloc(cmdLineLength + 1, sizeof(char));
+
+ if (!lpEscapedCmdLine)
+ {
+ free(lpEscapedChars);
+ return NULL;
+ }
+
+ p = (const char*)lpCmdLine;
+ pLastEnd = (const char*)lpCmdLine;
+ pOutput = (char*)lpEscapedCmdLine;
+
+ while (p < &lpCmdLine[cmdLineLength])
+ {
+ pBeg = strstr(p, "\\\"");
+
+ if (!pBeg)
+ {
+ length = strlen(p);
+ CopyMemory(pOutput, p, length);
+ pOutput += length;
+ break;
+ }
+
+ pEnd = pBeg + 2;
+
+ while (pBeg >= lpCmdLine)
+ {
+ if (*pBeg != '\\')
+ {
+ pBeg++;
+ break;
+ }
+
+ pBeg--;
+ }
+
+ n = ((pEnd - pBeg) - 1);
+ length = (pBeg - pLastEnd);
+ CopyMemory(pOutput, p, length);
+ pOutput += length;
+ p += length;
+
+ for (size_t i = 0; i < (n / 2); i++)
+ *pOutput++ = '\\';
+
+ p += n + 1;
+
+ if ((n % 2) != 0)
+ lpEscapedChars[pOutput - lpEscapedCmdLine] = TRUE;
+
+ *pOutput++ = '"';
+ pLastEnd = p;
+ }
+
+ *pOutput++ = '\0';
+ lpCmdLine = (LPCSTR)lpEscapedCmdLine;
+ cmdLineLength = strlen(lpCmdLine);
+ }
+
+ maxNumArgs = 2;
+ p = (const char*)lpCmdLine;
+
+ while (p < lpCmdLine + cmdLineLength)
+ {
+ p += strcspn(p, " \t");
+ p += strspn(p, " \t");
+ maxNumArgs++;
+ }
+
+ maxBufferSize = (maxNumArgs * (sizeof(char*))) + (cmdLineLength + 1);
+ buffer = calloc(maxBufferSize, sizeof(char));
+
+ if (!buffer)
+ {
+ free(lpEscapedCmdLine);
+ free(lpEscapedChars);
+ return NULL;
+ }
+
+ pArgs = (LPSTR*)buffer;
+ pOutput = (char*)&buffer[maxNumArgs * (sizeof(char*))];
+ p = (const char*)lpCmdLine;
+
+ while (p < lpCmdLine + cmdLineLength)
+ {
+ pBeg = p;
+
+ while (1)
+ {
+ p += strcspn(p, " \t\"\0");
+
+ if ((*p != '"') || !lpEscapedChars[p - lpCmdLine])
+ break;
+
+ p++;
+ }
+
+ if (*p != '"')
+ {
+ /* no whitespace escaped with double quotes */
+ length = (p - pBeg);
+ CopyMemory(pOutput, pBeg, length);
+ pOutput[length] = '\0';
+ pArgs[numArgs++] = pOutput;
+ pOutput += (length + 1);
+ }
+ else
+ {
+ p++;
+
+ while (1)
+ {
+ p += strcspn(p, "\"\0");
+
+ if ((*p != '"') || !lpEscapedChars[p - lpCmdLine])
+ break;
+
+ p++;
+ }
+
+ if (*p != '"')
+ WLog_ERR(TAG, "parsing error: uneven number of unescaped double quotes!");
+
+ if (*p && *(++p))
+ p += strcspn(p, " \t\0");
+
+ pArgs[numArgs++] = pOutput;
+
+ while (pBeg < p)
+ {
+ if (*pBeg != '"')
+ *pOutput++ = *pBeg;
+
+ pBeg++;
+ }
+
+ *pOutput++ = '\0';
+ }
+
+ p += strspn(p, " \t");
+ }
+
+ free(lpEscapedCmdLine);
+ free(lpEscapedChars);
+ *pNumArgs = numArgs;
+ return pArgs;
+}
+
+#ifndef _WIN32
+
+LPWSTR* CommandLineToArgvW(LPCWSTR lpCmdLine, int* pNumArgs)
+{
+ return NULL;
+}
+
+#endif
diff --git a/winpr/libwinpr/thread/process.c b/winpr/libwinpr/thread/process.c
new file mode 100644
index 0000000..0dbd940
--- /dev/null
+++ b/winpr/libwinpr/thread/process.c
@@ -0,0 +1,598 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Process Thread Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 DI (FH) Martin Haimberger <martin.haimberger@thincast.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/handle.h>
+#include "../handle/nonehandle.h"
+
+#include <winpr/thread.h>
+
+/**
+ * CreateProcessA
+ * CreateProcessW
+ * CreateProcessAsUserA
+ * CreateProcessAsUserW
+ * ExitProcess
+ * GetCurrentProcess
+ * GetCurrentProcessId
+ * GetExitCodeProcess
+ * GetProcessHandleCount
+ * GetProcessId
+ * GetProcessIdOfThread
+ * GetProcessMitigationPolicy
+ * GetProcessTimes
+ * GetProcessVersion
+ * OpenProcess
+ * OpenProcessToken
+ * ProcessIdToSessionId
+ * SetProcessAffinityUpdateMode
+ * SetProcessMitigationPolicy
+ * SetProcessShutdownParameters
+ * TerminateProcess
+ */
+
+#ifndef _WIN32
+
+#include <winpr/assert.h>
+#include <winpr/crt.h>
+#include <winpr/path.h>
+#include <winpr/environment.h>
+
+#include <grp.h>
+
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#ifdef __linux__
+#include <sys/syscall.h>
+#include <fcntl.h>
+#include <errno.h>
+#endif /* __linux__ */
+
+#include "thread.h"
+
+#include "../security/security.h"
+
+#ifndef NSIG
+#ifdef SIGMAX
+#define NSIG SIGMAX
+#else
+#define NSIG 64
+#endif
+#endif
+
+/**
+ * If the file name does not contain a directory path, the system searches for the executable file
+ * in the following sequence:
+ *
+ * 1) The directory from which the application loaded.
+ * 2) The current directory for the parent process.
+ * 3) The 32-bit Windows system directory. Use the GetSystemDirectory function to get the path of
+ * this directory. 4) The 16-bit Windows system directory. There is no function that obtains the
+ * path of this directory, but it is searched. The name of this directory is System. 5) The Windows
+ * directory. Use the GetWindowsDirectory function to get the path of this directory. 6) The
+ * directories that are listed in the PATH environment variable. Note that this function does not
+ * search the per-application path specified by the App Paths registry key. To include this
+ * per-application path in the search sequence, use the ShellExecute function.
+ */
+
+static char* FindApplicationPath(char* application)
+{
+ LPCSTR pathName = "PATH";
+ char* path = NULL;
+ char* save = NULL;
+ DWORD nSize = 0;
+ LPSTR lpSystemPath = NULL;
+ char* filename = NULL;
+
+ if (!application)
+ return NULL;
+
+ if (application[0] == '/')
+ return _strdup(application);
+
+ nSize = GetEnvironmentVariableA(pathName, NULL, 0);
+
+ if (!nSize)
+ return _strdup(application);
+
+ lpSystemPath = (LPSTR)malloc(nSize);
+
+ if (!lpSystemPath)
+ return NULL;
+
+ if (GetEnvironmentVariableA(pathName, lpSystemPath, nSize) != nSize - 1)
+ {
+ free(lpSystemPath);
+ return NULL;
+ }
+
+ save = NULL;
+ path = strtok_s(lpSystemPath, ":", &save);
+
+ while (path)
+ {
+ filename = GetCombinedPath(path, application);
+
+ if (winpr_PathFileExists(filename))
+ {
+ break;
+ }
+
+ free(filename);
+ filename = NULL;
+ path = strtok_s(NULL, ":", &save);
+ }
+
+ free(lpSystemPath);
+ return filename;
+}
+
+static HANDLE CreateProcessHandle(pid_t pid);
+static BOOL ProcessHandleCloseHandle(HANDLE handle);
+
+static BOOL _CreateProcessExA(HANDLE hToken, DWORD dwLogonFlags, LPCSTR lpApplicationName,
+ LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles,
+ DWORD dwCreationFlags, LPVOID lpEnvironment,
+ LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation)
+{
+ pid_t pid = 0;
+ int numArgs = 0;
+ LPSTR* pArgs = NULL;
+ char** envp = NULL;
+ char* filename = NULL;
+ HANDLE thread = NULL;
+ HANDLE process = NULL;
+ WINPR_ACCESS_TOKEN* token = NULL;
+ LPTCH lpszEnvironmentBlock = NULL;
+ BOOL ret = FALSE;
+ sigset_t oldSigMask;
+ sigset_t newSigMask;
+ BOOL restoreSigMask = FALSE;
+ numArgs = 0;
+ lpszEnvironmentBlock = NULL;
+ /* https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa
+ */
+ if (lpCommandLine)
+ pArgs = CommandLineToArgvA(lpCommandLine, &numArgs);
+ else
+ pArgs = CommandLineToArgvA(lpApplicationName, &numArgs);
+
+ if (!pArgs)
+ return FALSE;
+
+ token = (WINPR_ACCESS_TOKEN*)hToken;
+
+ if (lpEnvironment)
+ {
+ envp = EnvironmentBlockToEnvpA(lpEnvironment);
+ }
+ else
+ {
+ lpszEnvironmentBlock = GetEnvironmentStrings();
+
+ if (!lpszEnvironmentBlock)
+ goto finish;
+
+ envp = EnvironmentBlockToEnvpA(lpszEnvironmentBlock);
+ }
+
+ if (!envp)
+ goto finish;
+
+ filename = FindApplicationPath(pArgs[0]);
+
+ if (NULL == filename)
+ goto finish;
+
+ /* block all signals so that the child can safely reset the caller's handlers */
+ sigfillset(&newSigMask);
+ restoreSigMask = !pthread_sigmask(SIG_SETMASK, &newSigMask, &oldSigMask);
+ /* fork and exec */
+ pid = fork();
+
+ if (pid < 0)
+ {
+ /* fork failure */
+ goto finish;
+ }
+
+ if (pid == 0)
+ {
+ /* child process */
+#ifndef __sun
+ int maxfd = 0;
+#endif
+ sigset_t set = { 0 };
+ struct sigaction act = { 0 };
+ /* set default signal handlers */
+ act.sa_handler = SIG_DFL;
+ act.sa_flags = 0;
+ sigemptyset(&act.sa_mask);
+
+ for (int sig = 1; sig < NSIG; sig++)
+ sigaction(sig, &act, NULL);
+
+ /* unblock all signals */
+ sigfillset(&set);
+ pthread_sigmask(SIG_UNBLOCK, &set, NULL);
+
+ if (lpStartupInfo)
+ {
+ int handle_fd = 0;
+ handle_fd = winpr_Handle_getFd(lpStartupInfo->hStdOutput);
+
+ if (handle_fd != -1)
+ dup2(handle_fd, STDOUT_FILENO);
+
+ handle_fd = winpr_Handle_getFd(lpStartupInfo->hStdError);
+
+ if (handle_fd != -1)
+ dup2(handle_fd, STDERR_FILENO);
+
+ handle_fd = winpr_Handle_getFd(lpStartupInfo->hStdInput);
+
+ if (handle_fd != -1)
+ dup2(handle_fd, STDIN_FILENO);
+ }
+
+#ifdef __sun
+ closefrom(3);
+#else
+#ifdef F_MAXFD // on some BSD derivates
+ maxfd = fcntl(0, F_MAXFD);
+#else
+ maxfd = sysconf(_SC_OPEN_MAX);
+#endif
+
+ for (int fd = 3; fd < maxfd; fd++)
+ close(fd);
+
+#endif // __sun
+
+ if (token)
+ {
+ if (token->GroupId)
+ {
+ int rc = setgid((gid_t)token->GroupId);
+
+ if (rc < 0)
+ {
+ }
+ else
+ {
+ initgroups(token->Username, (gid_t)token->GroupId);
+ }
+ }
+
+ if (token->UserId)
+ {
+ int rc = setuid((uid_t)token->UserId);
+ if (rc != 0)
+ goto finish;
+ }
+ }
+
+ /* TODO: add better cwd handling and error checking */
+ if (lpCurrentDirectory && strlen(lpCurrentDirectory) > 0)
+ {
+ int rc = chdir(lpCurrentDirectory);
+ if (rc != 0)
+ goto finish;
+ }
+
+ if (execve(filename, pArgs, envp) < 0)
+ {
+ /* execve failed - end the process */
+ _exit(1);
+ }
+ }
+ else
+ {
+ /* parent process */
+ }
+
+ process = CreateProcessHandle(pid);
+
+ if (!process)
+ {
+ goto finish;
+ }
+
+ thread = CreateNoneHandle();
+
+ if (!thread)
+ {
+ ProcessHandleCloseHandle(process);
+ goto finish;
+ }
+
+ lpProcessInformation->hProcess = process;
+ lpProcessInformation->hThread = thread;
+ lpProcessInformation->dwProcessId = (DWORD)pid;
+ lpProcessInformation->dwThreadId = (DWORD)pid;
+ ret = TRUE;
+finish:
+
+ /* restore caller's original signal mask */
+ if (restoreSigMask)
+ pthread_sigmask(SIG_SETMASK, &oldSigMask, NULL);
+
+ free(filename);
+ free(pArgs);
+
+ if (lpszEnvironmentBlock)
+ FreeEnvironmentStrings(lpszEnvironmentBlock);
+
+ if (envp)
+ {
+ int i = 0;
+
+ while (envp[i])
+ {
+ free(envp[i]);
+ i++;
+ }
+
+ free(envp);
+ }
+
+ return ret;
+}
+
+BOOL CreateProcessA(LPCSTR lpApplicationName, LPSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles,
+ DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory,
+ LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation)
+{
+ return _CreateProcessExA(NULL, 0, lpApplicationName, lpCommandLine, lpProcessAttributes,
+ lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment,
+ lpCurrentDirectory, lpStartupInfo, lpProcessInformation);
+}
+
+BOOL CreateProcessW(LPCWSTR lpApplicationName, LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles,
+ DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation)
+{
+ return FALSE;
+}
+
+BOOL CreateProcessAsUserA(HANDLE hToken, LPCSTR lpApplicationName, LPSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles,
+ DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory,
+ LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation)
+{
+ return _CreateProcessExA(hToken, 0, lpApplicationName, lpCommandLine, lpProcessAttributes,
+ lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment,
+ lpCurrentDirectory, lpStartupInfo, lpProcessInformation);
+}
+
+BOOL CreateProcessAsUserW(HANDLE hToken, LPCWSTR lpApplicationName, LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles,
+ DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation)
+{
+ return FALSE;
+}
+
+BOOL CreateProcessWithLogonA(LPCSTR lpUsername, LPCSTR lpDomain, LPCSTR lpPassword,
+ DWORD dwLogonFlags, LPCSTR lpApplicationName, LPSTR lpCommandLine,
+ DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory,
+ LPSTARTUPINFOA lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation)
+{
+ return FALSE;
+}
+
+BOOL CreateProcessWithLogonW(LPCWSTR lpUsername, LPCWSTR lpDomain, LPCWSTR lpPassword,
+ DWORD dwLogonFlags, LPCWSTR lpApplicationName, LPWSTR lpCommandLine,
+ DWORD dwCreationFlags, LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation)
+{
+ return FALSE;
+}
+
+BOOL CreateProcessWithTokenA(HANDLE hToken, DWORD dwLogonFlags, LPCSTR lpApplicationName,
+ LPSTR lpCommandLine, DWORD dwCreationFlags, LPVOID lpEnvironment,
+ LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation)
+{
+ return _CreateProcessExA(NULL, 0, lpApplicationName, lpCommandLine, NULL, NULL, FALSE,
+ dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo,
+ lpProcessInformation);
+}
+
+BOOL CreateProcessWithTokenW(HANDLE hToken, DWORD dwLogonFlags, LPCWSTR lpApplicationName,
+ LPWSTR lpCommandLine, DWORD dwCreationFlags, LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation)
+{
+ return FALSE;
+}
+
+VOID ExitProcess(UINT uExitCode)
+{
+ exit((int)uExitCode);
+}
+
+BOOL GetExitCodeProcess(HANDLE hProcess, LPDWORD lpExitCode)
+{
+ WINPR_PROCESS* process = NULL;
+
+ if (!hProcess)
+ return FALSE;
+
+ if (!lpExitCode)
+ return FALSE;
+
+ process = (WINPR_PROCESS*)hProcess;
+ *lpExitCode = process->dwExitCode;
+ return TRUE;
+}
+
+HANDLE _GetCurrentProcess(VOID)
+{
+ return NULL;
+}
+
+DWORD GetCurrentProcessId(VOID)
+{
+ return ((DWORD)getpid());
+}
+
+BOOL TerminateProcess(HANDLE hProcess, UINT uExitCode)
+{
+ WINPR_PROCESS* process = NULL;
+ process = (WINPR_PROCESS*)hProcess;
+
+ if (!process || (process->pid <= 0))
+ return FALSE;
+
+ if (kill(process->pid, SIGTERM))
+ return FALSE;
+
+ return TRUE;
+}
+
+static BOOL ProcessHandleCloseHandle(HANDLE handle)
+{
+ WINPR_PROCESS* process = (WINPR_PROCESS*)handle;
+ WINPR_ASSERT(process);
+ if (process->fd >= 0)
+ {
+ close(process->fd);
+ process->fd = -1;
+ }
+ free(process);
+ return TRUE;
+}
+
+static BOOL ProcessHandleIsHandle(HANDLE handle)
+{
+ return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_PROCESS, FALSE);
+}
+
+static int ProcessGetFd(HANDLE handle)
+{
+ WINPR_PROCESS* process = (WINPR_PROCESS*)handle;
+
+ if (!ProcessHandleIsHandle(handle))
+ return -1;
+
+ return process->fd;
+}
+
+static DWORD ProcessCleanupHandle(HANDLE handle)
+{
+ WINPR_PROCESS* process = (WINPR_PROCESS*)handle;
+
+ WINPR_ASSERT(process);
+ if (process->fd > 0)
+ {
+ if (waitpid(process->pid, &process->status, WNOHANG) == process->pid)
+ process->dwExitCode = (DWORD)process->status;
+ }
+ return WAIT_OBJECT_0;
+}
+
+static HANDLE_OPS ops = { ProcessHandleIsHandle,
+ ProcessHandleCloseHandle,
+ ProcessGetFd,
+ ProcessCleanupHandle, /* CleanupHandle */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL };
+
+static int _pidfd_open(pid_t pid)
+{
+#ifdef __linux__
+#if !defined(__NR_pidfd_open)
+#define __NR_pidfd_open 434
+#endif /* __NR_pidfd_open */
+
+#ifndef PIDFD_NONBLOCK
+#define PIDFD_NONBLOCK O_NONBLOCK
+#endif /* PIDFD_NONBLOCK */
+
+ int fd = syscall(__NR_pidfd_open, pid, PIDFD_NONBLOCK);
+ if (fd < 0 && errno == EINVAL)
+ {
+ /* possibly PIDFD_NONBLOCK is not supported, let's try to create a pidfd and set it
+ * non blocking afterward */
+ int flags = 0;
+ fd = syscall(__NR_pidfd_open, pid, 0);
+ if (fd < 0)
+ return -1;
+
+ flags = fcntl(fd, F_GETFL);
+ if (flags < 0 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0)
+ {
+ close(fd);
+ fd = -1;
+ }
+ }
+ return fd;
+#else
+ return -1;
+#endif
+}
+
+HANDLE CreateProcessHandle(pid_t pid)
+{
+ WINPR_PROCESS* process = NULL;
+ process = (WINPR_PROCESS*)calloc(1, sizeof(WINPR_PROCESS));
+
+ if (!process)
+ return NULL;
+
+ process->pid = pid;
+ process->common.Type = HANDLE_TYPE_PROCESS;
+ process->common.ops = &ops;
+ process->fd = _pidfd_open(pid);
+ if (process->fd >= 0)
+ process->common.Mode = WINPR_FD_READ;
+ return (HANDLE)process;
+}
+
+#endif
diff --git a/winpr/libwinpr/thread/processor.c b/winpr/libwinpr/thread/processor.c
new file mode 100644
index 0000000..ed3df55
--- /dev/null
+++ b/winpr/libwinpr/thread/processor.c
@@ -0,0 +1,41 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Process Thread Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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/handle.h>
+
+#include <winpr/thread.h>
+
+/**
+ * GetCurrentProcessorNumber
+ * GetCurrentProcessorNumberEx
+ * GetThreadIdealProcessorEx
+ * SetThreadIdealProcessorEx
+ * IsProcessorFeaturePresent
+ */
+
+#ifndef _WIN32
+
+DWORD GetCurrentProcessorNumber(VOID)
+{
+ return 0;
+}
+
+#endif
diff --git a/winpr/libwinpr/thread/test/CMakeLists.txt b/winpr/libwinpr/thread/test/CMakeLists.txt
new file mode 100644
index 0000000..a78e584
--- /dev/null
+++ b/winpr/libwinpr/thread/test/CMakeLists.txt
@@ -0,0 +1,27 @@
+
+set(MODULE_NAME "TestThread")
+set(MODULE_PREFIX "TEST_THREAD")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestThreadCommandLineToArgv.c
+ TestThreadCreateProcess.c
+ TestThreadExitThread.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
diff --git a/winpr/libwinpr/thread/test/TestThreadCommandLineToArgv.c b/winpr/libwinpr/thread/test/TestThreadCommandLineToArgv.c
new file mode 100644
index 0000000..33d0c94
--- /dev/null
+++ b/winpr/libwinpr/thread/test/TestThreadCommandLineToArgv.c
@@ -0,0 +1,74 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/thread.h>
+
+static const char* test_args_line_1 = "app.exe abc d e";
+
+static const char* test_args_list_1[] = { "app.exe", "abc", "d", "e", NULL };
+
+static const char* test_args_line_2 = "app.exe abc \t def";
+
+static const char* test_args_list_2[] = { "app.exe", "abc", "def", NULL };
+
+static const char* test_args_line_3 = "app.exe \"abc\" d e";
+
+static const char* test_args_list_3[] = { "app.exe", "abc", "d", "e", NULL };
+
+static const char* test_args_line_4 = "app.exe a\\\\b d\"e f\"g h";
+
+static const char* test_args_list_4[] = { "app.exe", "a\\\\b", "de fg", "h", NULL };
+
+static const char* test_args_line_5 = "app.exe a\\\\\\\"b c d";
+
+static const char* test_args_list_5[] = { "app.exe", "a\\\"b", "c", "d", NULL };
+
+static const char* test_args_line_6 = "app.exe a\\\\\\\\\"b c\" d e";
+
+static const char* test_args_list_6[] = { "app.exe", "a\\\\b c", "d", "e", NULL };
+
+static const char* test_args_line_7 = "app.exe a\\\\\\\\\"b c\" d e f\\\\\\\\\"g h\" i j";
+
+static const char* test_args_list_7[] = { "app.exe", "a\\\\b c", "d", "e",
+ "f\\\\g h", "i", "j", NULL };
+
+static int test_command_line_parsing_case(const char* line, const char** list)
+{
+ LPSTR* pArgs = NULL;
+ int numArgs = 0;
+
+ pArgs = NULL;
+ numArgs = 0;
+
+ printf("Parsing: %s\n", line);
+
+ pArgs = CommandLineToArgvA(line, &numArgs);
+
+ printf("pNumArgs: %d\n", numArgs);
+
+ for (int i = 0; i < numArgs; i++)
+ {
+ printf("argv[%d] = %s\n", i, pArgs[i]);
+ }
+
+ free(pArgs);
+
+ return 0;
+}
+
+int TestThreadCommandLineToArgv(int argc, char* argv[])
+{
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ test_command_line_parsing_case(test_args_line_1, test_args_list_1);
+ test_command_line_parsing_case(test_args_line_2, test_args_list_2);
+ test_command_line_parsing_case(test_args_line_3, test_args_list_3);
+ test_command_line_parsing_case(test_args_line_4, test_args_list_4);
+ test_command_line_parsing_case(test_args_line_5, test_args_list_5);
+ test_command_line_parsing_case(test_args_line_6, test_args_list_6);
+ test_command_line_parsing_case(test_args_line_7, test_args_list_7);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/thread/test/TestThreadCreateProcess.c b/winpr/libwinpr/thread/test/TestThreadCreateProcess.c
new file mode 100644
index 0000000..1260c1b
--- /dev/null
+++ b/winpr/libwinpr/thread/test/TestThreadCreateProcess.c
@@ -0,0 +1,156 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/tchar.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/environment.h>
+#include <winpr/pipe.h>
+
+#define TESTENV_A "HELLO=WORLD"
+#define TESTENV_T _T(TESTENV_A)
+
+int TestThreadCreateProcess(int argc, char* argv[])
+{
+ BOOL status = 0;
+ DWORD exitCode = 0;
+ LPCTSTR lpApplicationName = NULL;
+
+#ifdef _WIN32
+ TCHAR lpCommandLine[200] = _T("cmd /C set");
+#else
+ TCHAR lpCommandLine[200] = _T("printenv");
+#endif
+
+ // LPTSTR lpCommandLine;
+ LPSECURITY_ATTRIBUTES lpProcessAttributes = NULL;
+ LPSECURITY_ATTRIBUTES lpThreadAttributes = NULL;
+ BOOL bInheritHandles = 0;
+ DWORD dwCreationFlags = 0;
+ LPVOID lpEnvironment = NULL;
+ LPCTSTR lpCurrentDirectory = NULL;
+ STARTUPINFO StartupInfo = { 0 };
+ PROCESS_INFORMATION ProcessInformation = { 0 };
+ LPTCH lpszEnvironmentBlock = NULL;
+ HANDLE pipe_read = NULL;
+ HANDLE pipe_write = NULL;
+ char buf[1024] = { 0 };
+ DWORD read_bytes = 0;
+ int ret = 0;
+ SECURITY_ATTRIBUTES saAttr;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ lpszEnvironmentBlock = GetEnvironmentStrings();
+
+ lpApplicationName = NULL;
+
+ lpProcessAttributes = NULL;
+ lpThreadAttributes = NULL;
+ bInheritHandles = FALSE;
+ dwCreationFlags = 0;
+#ifdef _UNICODE
+ dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
+#endif
+ lpEnvironment = lpszEnvironmentBlock;
+ lpCurrentDirectory = NULL;
+ StartupInfo.cb = sizeof(STARTUPINFO);
+
+ status = CreateProcess(lpApplicationName, lpCommandLine, lpProcessAttributes,
+ lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment,
+ lpCurrentDirectory, &StartupInfo, &ProcessInformation);
+
+ if (!status)
+ {
+ printf("CreateProcess failed. error=%" PRIu32 "\n", GetLastError());
+ return 1;
+ }
+
+ if (WaitForSingleObject(ProcessInformation.hProcess, 5000) != WAIT_OBJECT_0)
+ {
+ printf("Failed to wait for first process. error=%" PRIu32 "\n", GetLastError());
+ return 1;
+ }
+
+ exitCode = 0;
+ status = GetExitCodeProcess(ProcessInformation.hProcess, &exitCode);
+
+ printf("GetExitCodeProcess status: %" PRId32 "\n", status);
+ printf("Process exited with code: 0x%08" PRIX32 "\n", exitCode);
+
+ CloseHandle(ProcessInformation.hProcess);
+ CloseHandle(ProcessInformation.hThread);
+ FreeEnvironmentStrings(lpszEnvironmentBlock);
+
+ /* Test stdin,stdout,stderr redirection */
+
+ saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
+ saAttr.bInheritHandle = TRUE;
+ saAttr.lpSecurityDescriptor = NULL;
+
+ if (!CreatePipe(&pipe_read, &pipe_write, &saAttr, 0))
+ {
+ printf("Pipe creation failed. error=%" PRIu32 "\n", GetLastError());
+ return 1;
+ }
+
+ bInheritHandles = TRUE;
+
+ ZeroMemory(&StartupInfo, sizeof(STARTUPINFO));
+ StartupInfo.cb = sizeof(STARTUPINFO);
+ StartupInfo.hStdOutput = pipe_write;
+ StartupInfo.hStdError = pipe_write;
+ StartupInfo.dwFlags = STARTF_USESTDHANDLES;
+
+ ZeroMemory(&ProcessInformation, sizeof(PROCESS_INFORMATION));
+
+ if (!(lpEnvironment = calloc(1, sizeof(TESTENV_T) + sizeof(TCHAR))))
+ {
+ printf("Failed to allocate environment buffer. error=%" PRIu32 "\n", GetLastError());
+ return 1;
+ }
+ memcpy(lpEnvironment, (void*)TESTENV_T, sizeof(TESTENV_T));
+
+ status = CreateProcess(lpApplicationName, lpCommandLine, lpProcessAttributes,
+ lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment,
+ lpCurrentDirectory, &StartupInfo, &ProcessInformation);
+
+ free(lpEnvironment);
+
+ if (!status)
+ {
+ CloseHandle(pipe_read);
+ CloseHandle(pipe_write);
+ printf("CreateProcess failed. error=%" PRIu32 "\n", GetLastError());
+ return 1;
+ }
+
+ if (WaitForSingleObject(ProcessInformation.hProcess, 5000) != WAIT_OBJECT_0)
+ {
+ printf("Failed to wait for second process. error=%" PRIu32 "\n", GetLastError());
+ return 1;
+ }
+
+ ZeroMemory(buf, sizeof(buf));
+ ReadFile(pipe_read, buf, sizeof(buf) - 1, &read_bytes, NULL);
+ if (!strstr((const char*)buf, TESTENV_A))
+ {
+ printf("No or unexpected data read from pipe\n");
+ ret = 1;
+ }
+
+ CloseHandle(pipe_read);
+ CloseHandle(pipe_write);
+
+ exitCode = 0;
+ status = GetExitCodeProcess(ProcessInformation.hProcess, &exitCode);
+
+ printf("GetExitCodeProcess status: %" PRId32 "\n", status);
+ printf("Process exited with code: 0x%08" PRIX32 "\n", exitCode);
+
+ CloseHandle(ProcessInformation.hProcess);
+ CloseHandle(ProcessInformation.hThread);
+
+ return ret;
+}
diff --git a/winpr/libwinpr/thread/test/TestThreadExitThread.c b/winpr/libwinpr/thread/test/TestThreadExitThread.c
new file mode 100644
index 0000000..015bb85
--- /dev/null
+++ b/winpr/libwinpr/thread/test/TestThreadExitThread.c
@@ -0,0 +1,53 @@
+// Copyright © 2015 Hewlett-Packard Development Company, L.P.
+
+#include <winpr/file.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+
+static DWORD WINAPI thread_func(LPVOID arg)
+{
+ WINPR_UNUSED(arg);
+
+ /* exists of the thread the quickest as possible */
+ ExitThread(0);
+ return 0;
+}
+
+int TestThreadExitThread(int argc, char* argv[])
+{
+ HANDLE thread = NULL;
+ DWORD waitResult = 0;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /* FIXME: create some noise to better guaranty the test validity and
+ * decrease the number of loops */
+ for (int i = 0; i < 100; i++)
+ {
+ thread = CreateThread(NULL, 0, thread_func, NULL, 0, NULL);
+
+ if (thread == INVALID_HANDLE_VALUE)
+ {
+ fprintf(stderr, "Got an invalid thread!\n");
+ return -1;
+ }
+
+ waitResult = WaitForSingleObject(thread, 300);
+ if (waitResult != WAIT_OBJECT_0)
+ {
+ /* When the thread exits before the internal thread_list
+ * was updated, ExitThread() is not able to retrieve the
+ * related WINPR_THREAD object and is not able to signal
+ * the end of the thread. Therefore WaitForSingleObject
+ * never get the signal.
+ */
+ fprintf(stderr,
+ "300ms should have been enough for the thread to be in a signaled state\n");
+ return -1;
+ }
+
+ CloseHandle(thread);
+ }
+ return 0;
+}
diff --git a/winpr/libwinpr/thread/thread.c b/winpr/libwinpr/thread/thread.c
new file mode 100644
index 0000000..edf5639
--- /dev/null
+++ b/winpr/libwinpr/thread/thread.c
@@ -0,0 +1,1045 @@
+/**
+ * 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>
+
+/**
+ * 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>
+
+#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;
+}
+
+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))
+ 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
diff --git a/winpr/libwinpr/thread/thread.h b/winpr/libwinpr/thread/thread.h
new file mode 100644
index 0000000..aef9bc9
--- /dev/null
+++ b/winpr/libwinpr/thread/thread.h
@@ -0,0 +1,95 @@
+/**
+ * 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.
+ */
+
+#ifndef WINPR_THREAD_PRIVATE_H
+#define WINPR_THREAD_PRIVATE_H
+
+#ifndef _WIN32
+
+#include <pthread.h>
+
+#include <winpr/thread.h>
+
+#include "../handle/handle.h"
+#include "../synch/event.h"
+#include "apc.h"
+
+#ifdef __GNUC__
+#define ALIGN64 __attribute__((aligned(8)))
+#else
+#ifdef _WIN32
+#define ALIGN64 __declspec(align(8))
+#else
+#define ALIGN64
+#endif
+#endif
+
+typedef void* (*pthread_start_routine)(void*);
+typedef struct winpr_APC_item WINPR_APC_ITEM;
+
+typedef struct
+{
+ ALIGN64 pthread_mutex_t mux;
+ ALIGN64 pthread_cond_t cond;
+ ALIGN64 BOOL val;
+} mux_condition_bundle;
+
+struct winpr_thread
+{
+ WINPR_HANDLE common;
+
+ ALIGN64 BOOL started;
+ ALIGN64 WINPR_EVENT_IMPL event;
+ ALIGN64 BOOL mainProcess;
+ ALIGN64 BOOL detached;
+ ALIGN64 BOOL joined;
+ ALIGN64 BOOL exited;
+ ALIGN64 DWORD dwExitCode;
+ ALIGN64 pthread_t thread;
+ ALIGN64 SIZE_T dwStackSize;
+ ALIGN64 LPVOID lpParameter;
+ ALIGN64 pthread_mutex_t mutex;
+ mux_condition_bundle isRunning;
+ mux_condition_bundle isCreated;
+ ALIGN64 LPTHREAD_START_ROUTINE lpStartAddress;
+ ALIGN64 LPSECURITY_ATTRIBUTES lpThreadAttributes;
+ ALIGN64 APC_QUEUE apc;
+#if defined(WITH_DEBUG_THREADS)
+ ALIGN64 void* create_stack;
+ ALIGN64 void* exit_stack;
+#endif
+};
+
+WINPR_THREAD* winpr_GetCurrentThread(VOID);
+
+typedef struct
+{
+ WINPR_HANDLE common;
+
+ pid_t pid;
+ int status;
+ DWORD dwExitCode;
+ int fd;
+} WINPR_PROCESS;
+
+#endif
+
+#endif /* WINPR_THREAD_PRIVATE_H */
diff --git a/winpr/libwinpr/thread/tls.c b/winpr/libwinpr/thread/tls.c
new file mode 100644
index 0000000..977d47d
--- /dev/null
+++ b/winpr/libwinpr/thread/tls.c
@@ -0,0 +1,72 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Process Thread Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.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/handle.h>
+
+#include <winpr/thread.h>
+
+/**
+ * TlsAlloc
+ * TlsFree
+ * TlsGetValue
+ * TlsSetValue
+ */
+
+#ifndef _WIN32
+
+#include <pthread.h>
+
+DWORD TlsAlloc(void)
+{
+ pthread_key_t key = 0;
+
+ if (pthread_key_create(&key, NULL) != 0)
+ return TLS_OUT_OF_INDEXES;
+
+ return key;
+}
+
+LPVOID TlsGetValue(DWORD dwTlsIndex)
+{
+ LPVOID value = NULL;
+ pthread_key_t key = 0;
+ key = (pthread_key_t)dwTlsIndex;
+ value = (LPVOID)pthread_getspecific(key);
+ return value;
+}
+
+BOOL TlsSetValue(DWORD dwTlsIndex, LPVOID lpTlsValue)
+{
+ pthread_key_t key = 0;
+ key = (pthread_key_t)dwTlsIndex;
+ pthread_setspecific(key, lpTlsValue);
+ return TRUE;
+}
+
+BOOL TlsFree(DWORD dwTlsIndex)
+{
+ pthread_key_t key = 0;
+ key = (pthread_key_t)dwTlsIndex;
+ pthread_key_delete(key);
+ return TRUE;
+}
+
+#endif