diff options
Diffstat (limited to 'winpr/libwinpr/thread')
-rw-r--r-- | winpr/libwinpr/thread/CMakeLists.txt | 34 | ||||
-rw-r--r-- | winpr/libwinpr/thread/ModuleOptions.cmake | 9 | ||||
-rw-r--r-- | winpr/libwinpr/thread/apc.c | 271 | ||||
-rw-r--r-- | winpr/libwinpr/thread/apc.h | 85 | ||||
-rw-r--r-- | winpr/libwinpr/thread/argv.c | 279 | ||||
-rw-r--r-- | winpr/libwinpr/thread/process.c | 598 | ||||
-rw-r--r-- | winpr/libwinpr/thread/processor.c | 41 | ||||
-rw-r--r-- | winpr/libwinpr/thread/test/CMakeLists.txt | 27 | ||||
-rw-r--r-- | winpr/libwinpr/thread/test/TestThreadCommandLineToArgv.c | 74 | ||||
-rw-r--r-- | winpr/libwinpr/thread/test/TestThreadCreateProcess.c | 156 | ||||
-rw-r--r-- | winpr/libwinpr/thread/test/TestThreadExitThread.c | 53 | ||||
-rw-r--r-- | winpr/libwinpr/thread/thread.c | 1045 | ||||
-rw-r--r-- | winpr/libwinpr/thread/thread.h | 95 | ||||
-rw-r--r-- | winpr/libwinpr/thread/tls.c | 72 |
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 |