summaryrefslogtreecommitdiffstats
path: root/winpr/libwinpr/synch
diff options
context:
space:
mode:
Diffstat (limited to 'winpr/libwinpr/synch')
-rw-r--r--winpr/libwinpr/synch/CMakeLists.txt40
-rw-r--r--winpr/libwinpr/synch/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/synch/address.c46
-rw-r--r--winpr/libwinpr/synch/barrier.c263
-rw-r--r--winpr/libwinpr/synch/critical.c272
-rw-r--r--winpr/libwinpr/synch/event.c584
-rw-r--r--winpr/libwinpr/synch/event.h57
-rw-r--r--winpr/libwinpr/synch/init.c96
-rw-r--r--winpr/libwinpr/synch/mutex.c247
-rw-r--r--winpr/libwinpr/synch/pollset.c274
-rw-r--r--winpr/libwinpr/synch/pollset.h74
-rw-r--r--winpr/libwinpr/synch/semaphore.c257
-rw-r--r--winpr/libwinpr/synch/sleep.c148
-rw-r--r--winpr/libwinpr/synch/synch.h159
-rw-r--r--winpr/libwinpr/synch/test/CMakeLists.txt41
-rw-r--r--winpr/libwinpr/synch/test/TestSynchAPC.c173
-rw-r--r--winpr/libwinpr/synch/test/TestSynchBarrier.c257
-rw-r--r--winpr/libwinpr/synch/test/TestSynchCritical.c363
-rw-r--r--winpr/libwinpr/synch/test/TestSynchEvent.c94
-rw-r--r--winpr/libwinpr/synch/test/TestSynchInit.c156
-rw-r--r--winpr/libwinpr/synch/test/TestSynchMultipleThreads.c243
-rw-r--r--winpr/libwinpr/synch/test/TestSynchMutex.c258
-rw-r--r--winpr/libwinpr/synch/test/TestSynchSemaphore.c21
-rw-r--r--winpr/libwinpr/synch/test/TestSynchThread.c131
-rw-r--r--winpr/libwinpr/synch/test/TestSynchTimerQueue.c125
-rw-r--r--winpr/libwinpr/synch/test/TestSynchWaitableTimer.c83
-rw-r--r--winpr/libwinpr/synch/test/TestSynchWaitableTimerAPC.c92
-rw-r--r--winpr/libwinpr/synch/timer.c1093
-rw-r--r--winpr/libwinpr/synch/wait.c583
29 files changed, 6239 insertions, 0 deletions
diff --git a/winpr/libwinpr/synch/CMakeLists.txt b/winpr/libwinpr/synch/CMakeLists.txt
new file mode 100644
index 0000000..84053aa
--- /dev/null
+++ b/winpr/libwinpr/synch/CMakeLists.txt
@@ -0,0 +1,40 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-synch 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(
+ address.c
+ barrier.c
+ critical.c
+ event.c
+ init.c
+ mutex.c
+ pollset.c
+ pollset.h
+ semaphore.c
+ sleep.c
+ synch.h
+ timer.c
+ wait.c)
+
+if(FREEBSD)
+ winpr_include_directory_add(${EPOLLSHIM_INCLUDE_DIR})
+ winpr_library_add_private(${EPOLLSHIM_LIBS})
+endif()
+
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/winpr/libwinpr/synch/ModuleOptions.cmake b/winpr/libwinpr/synch/ModuleOptions.cmake
new file mode 100644
index 0000000..1aac06d
--- /dev/null
+++ b/winpr/libwinpr/synch/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "core")
+set(MINWIN_MAJOR_VERSION "2")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "synch")
+set(MINWIN_LONG_NAME "Synchronization 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/synch/address.c b/winpr/libwinpr/synch/address.c
new file mode 100644
index 0000000..d6d074b
--- /dev/null
+++ b/winpr/libwinpr/synch/address.c
@@ -0,0 +1,46 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Synchronization 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/synch.h>
+
+/**
+ * WakeByAddressAll
+ * WakeByAddressSingle
+ * WaitOnAddress
+ */
+
+#ifndef _WIN32
+
+VOID WakeByAddressAll(PVOID Address)
+{
+}
+
+VOID WakeByAddressSingle(PVOID Address)
+{
+}
+
+BOOL WaitOnAddress(VOID volatile* Address, PVOID CompareAddress, SIZE_T AddressSize,
+ DWORD dwMilliseconds)
+{
+ return TRUE;
+}
+
+#endif
diff --git a/winpr/libwinpr/synch/barrier.c b/winpr/libwinpr/synch/barrier.c
new file mode 100644
index 0000000..0021408
--- /dev/null
+++ b/winpr/libwinpr/synch/barrier.c
@@ -0,0 +1,263 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Synchronization Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2016 Norbert Federa <norbert.federa@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/synch.h>
+#include <winpr/assert.h>
+
+#include "synch.h"
+
+#include <winpr/crt.h>
+
+#ifdef WINPR_SYNCHRONIZATION_BARRIER
+
+#include <winpr/sysinfo.h>
+#include <winpr/library.h>
+#include <winpr/interlocked.h>
+#include <winpr/thread.h>
+
+/**
+ * WinPR uses the internal RTL_BARRIER struct members exactly like Windows:
+ *
+ * DWORD Reserved1: number of threads that have not yet entered the barrier
+ * DWORD Reserved2: number of threads required to enter the barrier
+ * ULONG_PTR Reserved3[2]; two synchronization events (manual reset events)
+ * DWORD Reserved4; number of processors
+ * DWORD Reserved5; spincount
+ */
+
+#ifdef _WIN32
+
+static HMODULE g_Kernel32 = NULL;
+static BOOL g_NativeBarrier = FALSE;
+static INIT_ONCE g_InitOnce = INIT_ONCE_STATIC_INIT;
+
+typedef BOOL(WINAPI* fnInitializeSynchronizationBarrier)(LPSYNCHRONIZATION_BARRIER lpBarrier,
+ LONG lTotalThreads, LONG lSpinCount);
+typedef BOOL(WINAPI* fnEnterSynchronizationBarrier)(LPSYNCHRONIZATION_BARRIER lpBarrier,
+ DWORD dwFlags);
+typedef BOOL(WINAPI* fnDeleteSynchronizationBarrier)(LPSYNCHRONIZATION_BARRIER lpBarrier);
+
+static fnInitializeSynchronizationBarrier pfnInitializeSynchronizationBarrier = NULL;
+static fnEnterSynchronizationBarrier pfnEnterSynchronizationBarrier = NULL;
+static fnDeleteSynchronizationBarrier pfnDeleteSynchronizationBarrier = NULL;
+
+static BOOL CALLBACK InitOnce_Barrier(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+ g_Kernel32 = LoadLibraryA("kernel32.dll");
+
+ if (!g_Kernel32)
+ return TRUE;
+
+ pfnInitializeSynchronizationBarrier = (fnInitializeSynchronizationBarrier)GetProcAddress(
+ g_Kernel32, "InitializeSynchronizationBarrier");
+
+ pfnEnterSynchronizationBarrier =
+ (fnEnterSynchronizationBarrier)GetProcAddress(g_Kernel32, "EnterSynchronizationBarrier");
+
+ pfnDeleteSynchronizationBarrier =
+ (fnDeleteSynchronizationBarrier)GetProcAddress(g_Kernel32, "DeleteSynchronizationBarrier");
+
+ if (pfnInitializeSynchronizationBarrier && pfnEnterSynchronizationBarrier &&
+ pfnDeleteSynchronizationBarrier)
+ {
+ g_NativeBarrier = TRUE;
+ }
+
+ return TRUE;
+}
+
+#endif
+
+BOOL WINAPI winpr_InitializeSynchronizationBarrier(LPSYNCHRONIZATION_BARRIER lpBarrier,
+ LONG lTotalThreads, LONG lSpinCount)
+{
+ SYSTEM_INFO sysinfo;
+ HANDLE hEvent0 = NULL;
+ HANDLE hEvent1 = NULL;
+
+#ifdef _WIN32
+ InitOnceExecuteOnce(&g_InitOnce, InitOnce_Barrier, NULL, NULL);
+
+ if (g_NativeBarrier)
+ return pfnInitializeSynchronizationBarrier(lpBarrier, lTotalThreads, lSpinCount);
+#endif
+
+ if (!lpBarrier || lTotalThreads < 1 || lSpinCount < -1)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ ZeroMemory(lpBarrier, sizeof(SYNCHRONIZATION_BARRIER));
+
+ if (lSpinCount == -1)
+ lSpinCount = 2000;
+
+ if (!(hEvent0 = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ return FALSE;
+
+ if (!(hEvent1 = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ CloseHandle(hEvent0);
+ return FALSE;
+ }
+
+ GetNativeSystemInfo(&sysinfo);
+
+ WINPR_ASSERT(lTotalThreads >= 0);
+ lpBarrier->Reserved1 = (DWORD)lTotalThreads;
+ lpBarrier->Reserved2 = (DWORD)lTotalThreads;
+ lpBarrier->Reserved3[0] = (ULONG_PTR)hEvent0;
+ lpBarrier->Reserved3[1] = (ULONG_PTR)hEvent1;
+ lpBarrier->Reserved4 = sysinfo.dwNumberOfProcessors;
+ WINPR_ASSERT(lSpinCount >= 0);
+ lpBarrier->Reserved5 = (DWORD)lSpinCount;
+
+ return TRUE;
+}
+
+BOOL WINAPI winpr_EnterSynchronizationBarrier(LPSYNCHRONIZATION_BARRIER lpBarrier, DWORD dwFlags)
+{
+ LONG remainingThreads = 0;
+ HANDLE hCurrentEvent = NULL;
+ HANDLE hDormantEvent = NULL;
+
+#ifdef _WIN32
+ if (g_NativeBarrier)
+ return pfnEnterSynchronizationBarrier(lpBarrier, dwFlags);
+#endif
+
+ if (!lpBarrier)
+ return FALSE;
+
+ /**
+ * dwFlags according to
+ * https://msdn.microsoft.com/en-us/library/windows/desktop/hh706889(v=vs.85).aspx
+ *
+ * SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY (0x01)
+ * Specifies that the thread entering the barrier should block
+ * immediately until the last thread enters the barrier.
+ *
+ * SYNCHRONIZATION_BARRIER_FLAGS_SPIN_ONLY (0x02)
+ * Specifies that the thread entering the barrier should spin until the
+ * last thread enters the barrier, even if the spinning thread exceeds
+ * the barrier's maximum spin count.
+ *
+ * SYNCHRONIZATION_BARRIER_FLAGS_NO_DELETE (0x04)
+ * Specifies that the function can skip the work required to ensure
+ * that it is safe to delete the barrier, which can improve
+ * performance. All threads that enter this barrier must specify the
+ * flag; otherwise, the flag is ignored. This flag should be used only
+ * if the barrier will never be deleted.
+ */
+
+ hCurrentEvent = (HANDLE)lpBarrier->Reserved3[0];
+ hDormantEvent = (HANDLE)lpBarrier->Reserved3[1];
+
+ remainingThreads = InterlockedDecrement((LONG*)&lpBarrier->Reserved1);
+
+ WINPR_ASSERT(remainingThreads >= 0);
+
+ if (remainingThreads > 0)
+ {
+ DWORD dwProcessors = lpBarrier->Reserved4;
+ BOOL spinOnly = (dwFlags & SYNCHRONIZATION_BARRIER_FLAGS_SPIN_ONLY) ? TRUE : FALSE;
+ BOOL blockOnly = (dwFlags & SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY) ? TRUE : FALSE;
+ BOOL block = TRUE;
+
+ /**
+ * If SYNCHRONIZATION_BARRIER_FLAGS_SPIN_ONLY is set we will
+ * always spin and trust that the user knows what he/she/it
+ * is doing. Otherwise we'll only spin if the flag
+ * SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY is not set and
+ * the number of remaining threads is less than the number
+ * of processors.
+ */
+
+ if (spinOnly || (((ULONG)remainingThreads < dwProcessors) && !blockOnly))
+ {
+ DWORD dwSpinCount = lpBarrier->Reserved5;
+ DWORD sp = 0;
+ /**
+ * nb: we must let the compiler know that our comparand
+ * can change between the iterations in the loop below
+ */
+ volatile ULONG_PTR* cmp = &lpBarrier->Reserved3[0];
+ /* we spin until the last thread _completed_ the event switch */
+ while ((block = (*cmp == (ULONG_PTR)hCurrentEvent)))
+ if (!spinOnly && ++sp > dwSpinCount)
+ break;
+ }
+
+ if (block)
+ WaitForSingleObject(hCurrentEvent, INFINITE);
+
+ return FALSE;
+ }
+
+ /* reset the dormant event first */
+ ResetEvent(hDormantEvent);
+
+ /* reset the remaining counter */
+ lpBarrier->Reserved1 = lpBarrier->Reserved2;
+
+ /* switch events - this will also unblock the spinning threads */
+ lpBarrier->Reserved3[1] = (ULONG_PTR)hCurrentEvent;
+ lpBarrier->Reserved3[0] = (ULONG_PTR)hDormantEvent;
+
+ /* signal the blocked threads */
+ SetEvent(hCurrentEvent);
+
+ return TRUE;
+}
+
+BOOL WINAPI winpr_DeleteSynchronizationBarrier(LPSYNCHRONIZATION_BARRIER lpBarrier)
+{
+#ifdef _WIN32
+ if (g_NativeBarrier)
+ return pfnDeleteSynchronizationBarrier(lpBarrier);
+#endif
+
+ /**
+ * According to https://msdn.microsoft.com/en-us/library/windows/desktop/hh706887(v=vs.85).aspx
+ * Return value:
+ * The DeleteSynchronizationBarrier function always returns TRUE.
+ */
+
+ if (!lpBarrier)
+ return TRUE;
+
+ while (lpBarrier->Reserved1 != lpBarrier->Reserved2)
+ SwitchToThread();
+
+ if (lpBarrier->Reserved3[0])
+ CloseHandle((HANDLE)lpBarrier->Reserved3[0]);
+
+ if (lpBarrier->Reserved3[1])
+ CloseHandle((HANDLE)lpBarrier->Reserved3[1]);
+
+ ZeroMemory(lpBarrier, sizeof(SYNCHRONIZATION_BARRIER));
+
+ return TRUE;
+}
+
+#endif
diff --git a/winpr/libwinpr/synch/critical.c b/winpr/libwinpr/synch/critical.c
new file mode 100644
index 0000000..795d93a
--- /dev/null
+++ b/winpr/libwinpr/synch/critical.c
@@ -0,0 +1,272 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Synchronization Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2013 Norbert Federa <norbert.federa@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/assert.h>
+#include <winpr/tchar.h>
+#include <winpr/synch.h>
+#include <winpr/sysinfo.h>
+#include <winpr/interlocked.h>
+#include <winpr/thread.h>
+
+#include "synch.h"
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#if defined(__APPLE__)
+#include <mach/task.h>
+#include <mach/mach.h>
+#include <mach/semaphore.h>
+#endif
+
+#ifndef _WIN32
+
+#include "../log.h"
+#define TAG WINPR_TAG("synch.critical")
+
+VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection)
+{
+ InitializeCriticalSectionEx(lpCriticalSection, 0, 0);
+}
+
+BOOL InitializeCriticalSectionEx(LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount,
+ DWORD Flags)
+{
+ WINPR_ASSERT(lpCriticalSection);
+ /**
+ * See http://msdn.microsoft.com/en-us/library/ff541979(v=vs.85).aspx
+ * - The LockCount field indicates the number of times that any thread has
+ * called the EnterCriticalSection routine for this critical section,
+ * minus one. This field starts at -1 for an unlocked critical section.
+ * Each call of EnterCriticalSection increments this value; each call of
+ * LeaveCriticalSection decrements it.
+ * - The RecursionCount field indicates the number of times that the owning
+ * thread has called EnterCriticalSection for this critical section.
+ */
+ if (Flags != 0)
+ {
+ WLog_WARN(TAG, "Flags unimplemented");
+ }
+
+ lpCriticalSection->DebugInfo = NULL;
+ lpCriticalSection->LockCount = -1;
+ lpCriticalSection->SpinCount = 0;
+ lpCriticalSection->RecursionCount = 0;
+ lpCriticalSection->OwningThread = NULL;
+ lpCriticalSection->LockSemaphore = (winpr_sem_t*)malloc(sizeof(winpr_sem_t));
+
+ if (!lpCriticalSection->LockSemaphore)
+ return FALSE;
+
+#if defined(__APPLE__)
+
+ if (semaphore_create(mach_task_self(), lpCriticalSection->LockSemaphore, SYNC_POLICY_FIFO, 0) !=
+ KERN_SUCCESS)
+ goto out_fail;
+
+#else
+
+ if (sem_init(lpCriticalSection->LockSemaphore, 0, 0) != 0)
+ goto out_fail;
+
+#endif
+ SetCriticalSectionSpinCount(lpCriticalSection, dwSpinCount);
+ return TRUE;
+out_fail:
+ free(lpCriticalSection->LockSemaphore);
+ return FALSE;
+}
+
+BOOL InitializeCriticalSectionAndSpinCount(LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount)
+{
+ return InitializeCriticalSectionEx(lpCriticalSection, dwSpinCount, 0);
+}
+
+DWORD SetCriticalSectionSpinCount(LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount)
+{
+ WINPR_ASSERT(lpCriticalSection);
+#if !defined(WINPR_CRITICAL_SECTION_DISABLE_SPINCOUNT)
+ SYSTEM_INFO sysinfo;
+ DWORD dwPreviousSpinCount = lpCriticalSection->SpinCount;
+
+ if (dwSpinCount)
+ {
+ /* Don't spin on uniprocessor systems! */
+ GetNativeSystemInfo(&sysinfo);
+
+ if (sysinfo.dwNumberOfProcessors < 2)
+ dwSpinCount = 0;
+ }
+
+ lpCriticalSection->SpinCount = dwSpinCount;
+ return dwPreviousSpinCount;
+#else
+ return 0;
+#endif
+}
+
+static VOID _WaitForCriticalSection(LPCRITICAL_SECTION lpCriticalSection)
+{
+ WINPR_ASSERT(lpCriticalSection);
+#if defined(__APPLE__)
+ semaphore_wait(*((winpr_sem_t*)lpCriticalSection->LockSemaphore));
+#else
+ sem_wait((winpr_sem_t*)lpCriticalSection->LockSemaphore);
+#endif
+}
+
+static VOID _UnWaitCriticalSection(LPCRITICAL_SECTION lpCriticalSection)
+{
+ WINPR_ASSERT(lpCriticalSection);
+#if defined __APPLE__
+ semaphore_signal(*((winpr_sem_t*)lpCriticalSection->LockSemaphore));
+#else
+ sem_post((winpr_sem_t*)lpCriticalSection->LockSemaphore);
+#endif
+}
+
+VOID EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection)
+{
+ WINPR_ASSERT(lpCriticalSection);
+#if !defined(WINPR_CRITICAL_SECTION_DISABLE_SPINCOUNT)
+ ULONG SpinCount = lpCriticalSection->SpinCount;
+
+ /* If we're lucky or if the current thread is already owner we can return early */
+ if (SpinCount && TryEnterCriticalSection(lpCriticalSection))
+ return;
+
+ /* Spin requested times but don't compete with another waiting thread */
+ while (SpinCount-- && lpCriticalSection->LockCount < 1)
+ {
+ /* Atomically try to acquire and check the if the section is free. */
+ if (InterlockedCompareExchange(&lpCriticalSection->LockCount, 0, -1) == -1)
+ {
+ lpCriticalSection->RecursionCount = 1;
+ lpCriticalSection->OwningThread = (HANDLE)(ULONG_PTR)GetCurrentThreadId();
+ return;
+ }
+
+ /* Failed to get the lock. Let the scheduler know that we're spinning. */
+ if (sched_yield() != 0)
+ {
+ /**
+ * On some operating systems sched_yield is a stub.
+ * usleep should at least trigger a context switch if any thread is waiting.
+ * A ThreadYield() would be nice in winpr ...
+ */
+ usleep(1);
+ }
+ }
+
+#endif
+
+ /* First try the fastest possible path to get the lock. */
+ if (InterlockedIncrement(&lpCriticalSection->LockCount))
+ {
+ /* Section is already locked. Check if it is owned by the current thread. */
+ if (lpCriticalSection->OwningThread == (HANDLE)(ULONG_PTR)GetCurrentThreadId())
+ {
+ /* Recursion. No need to wait. */
+ lpCriticalSection->RecursionCount++;
+ return;
+ }
+
+ /* Section is locked by another thread. We have to wait. */
+ _WaitForCriticalSection(lpCriticalSection);
+ }
+
+ /* We got the lock. Own it ... */
+ lpCriticalSection->RecursionCount = 1;
+ lpCriticalSection->OwningThread = (HANDLE)(ULONG_PTR)GetCurrentThreadId();
+}
+
+BOOL TryEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection)
+{
+ HANDLE current_thread = (HANDLE)(ULONG_PTR)GetCurrentThreadId();
+
+ WINPR_ASSERT(lpCriticalSection);
+
+ /* Atomically acquire the the lock if the section is free. */
+ if (InterlockedCompareExchange(&lpCriticalSection->LockCount, 0, -1) == -1)
+ {
+ lpCriticalSection->RecursionCount = 1;
+ lpCriticalSection->OwningThread = current_thread;
+ return TRUE;
+ }
+
+ /* Section is already locked. Check if it is owned by the current thread. */
+ if (lpCriticalSection->OwningThread == current_thread)
+ {
+ /* Recursion, return success */
+ lpCriticalSection->RecursionCount++;
+ InterlockedIncrement(&lpCriticalSection->LockCount);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection)
+{
+ WINPR_ASSERT(lpCriticalSection);
+
+ /* Decrement RecursionCount and check if this is the last LeaveCriticalSection call ...*/
+ if (--lpCriticalSection->RecursionCount < 1)
+ {
+ /* Last recursion, clear owner, unlock and if there are other waiting threads ... */
+ lpCriticalSection->OwningThread = NULL;
+
+ if (InterlockedDecrement(&lpCriticalSection->LockCount) >= 0)
+ {
+ /* ...signal the semaphore to unblock the next waiting thread */
+ _UnWaitCriticalSection(lpCriticalSection);
+ }
+ }
+ else
+ {
+ InterlockedDecrement(&lpCriticalSection->LockCount);
+ }
+}
+
+VOID DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection)
+{
+ WINPR_ASSERT(lpCriticalSection);
+
+ lpCriticalSection->LockCount = -1;
+ lpCriticalSection->SpinCount = 0;
+ lpCriticalSection->RecursionCount = 0;
+ lpCriticalSection->OwningThread = NULL;
+
+ if (lpCriticalSection->LockSemaphore != NULL)
+ {
+#if defined __APPLE__
+ semaphore_destroy(mach_task_self(), *((winpr_sem_t*)lpCriticalSection->LockSemaphore));
+#else
+ sem_destroy((winpr_sem_t*)lpCriticalSection->LockSemaphore);
+#endif
+ free(lpCriticalSection->LockSemaphore);
+ lpCriticalSection->LockSemaphore = NULL;
+ }
+}
+
+#endif
diff --git a/winpr/libwinpr/synch/event.c b/winpr/libwinpr/synch/event.c
new file mode 100644
index 0000000..7204add
--- /dev/null
+++ b/winpr/libwinpr/synch/event.c
@@ -0,0 +1,584 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Synchronization Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2017 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2017 Thincast Technologies GmbH
+ *
+ * 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 <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <winpr/synch.h>
+
+#ifndef _WIN32
+
+#include "synch.h"
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifdef WINPR_HAVE_SYS_EVENTFD_H
+#include <sys/eventfd.h>
+#endif
+
+#include <fcntl.h>
+#include <errno.h>
+
+#include "../handle/handle.h"
+#include "../pipe/pipe.h"
+
+#include "../log.h"
+#include "event.h"
+#define TAG WINPR_TAG("synch.event")
+
+#if defined(WITH_DEBUG_EVENTS)
+static wArrayList* global_event_list = NULL;
+
+static void dump_event(WINPR_EVENT* event, size_t index)
+{
+ char** msg = NULL;
+ size_t used = 0;
+#if 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);
+#endif
+ WLog_DBG(TAG, "Event handle created still not closed! [%" PRIuz ", %p]", index, event);
+ msg = winpr_backtrace_symbols(event->create_stack, &used);
+
+ for (size_t i = 2; i < used; i++)
+ WLog_DBG(TAG, "[%" PRIdz "]: %s", i, msg[i]);
+
+ free(msg);
+}
+#endif /* WITH_DEBUG_EVENTS */
+
+#ifdef WINPR_HAVE_SYS_EVENTFD_H
+#if !defined(WITH_EVENTFD_READ_WRITE)
+static int eventfd_read(int fd, eventfd_t* value)
+{
+ return (read(fd, value, sizeof(*value)) == sizeof(*value)) ? 0 : -1;
+}
+
+static int eventfd_write(int fd, eventfd_t value)
+{
+ return (write(fd, &value, sizeof(value)) == sizeof(value)) ? 0 : -1;
+}
+#endif
+#endif
+
+#ifndef WINPR_HAVE_SYS_EVENTFD_H
+static BOOL set_non_blocking_fd(int fd)
+{
+ int flags;
+ flags = fcntl(fd, F_GETFL);
+ if (flags < 0)
+ return FALSE;
+
+ return fcntl(fd, F_SETFL, flags | O_NONBLOCK) >= 0;
+}
+#endif /* !WINPR_HAVE_SYS_EVENTFD_H */
+
+BOOL winpr_event_init(WINPR_EVENT_IMPL* event)
+{
+#ifdef WINPR_HAVE_SYS_EVENTFD_H
+ event->fds[1] = -1;
+ event->fds[0] = eventfd(0, EFD_NONBLOCK);
+
+ return event->fds[0] >= 0;
+#else
+ int flags;
+
+ if (pipe(event->fds) < 0)
+ return FALSE;
+
+ if (!set_non_blocking_fd(event->fds[0]) || !set_non_blocking_fd(event->fds[1]))
+ goto out_error;
+
+ return TRUE;
+
+out_error:
+ winpr_event_uninit(event);
+ return FALSE;
+#endif
+}
+
+void winpr_event_init_from_fd(WINPR_EVENT_IMPL* event, int fd)
+{
+ event->fds[0] = fd;
+#ifndef WINPR_HAVE_SYS_EVENTFD_H
+ event->fds[1] = fd;
+#endif
+}
+
+BOOL winpr_event_set(WINPR_EVENT_IMPL* event)
+{
+ int ret = 0;
+ do
+ {
+#ifdef WINPR_HAVE_SYS_EVENTFD_H
+ eventfd_t value = 1;
+ ret = eventfd_write(event->fds[0], value);
+#else
+ ret = write(event->fds[1], "-", 1);
+#endif
+ } while (ret < 0 && errno == EINTR);
+
+ return ret >= 0;
+}
+
+BOOL winpr_event_reset(WINPR_EVENT_IMPL* event)
+{
+ int ret = 0;
+ do
+ {
+ do
+ {
+#ifdef WINPR_HAVE_SYS_EVENTFD_H
+ eventfd_t value = 1;
+ ret = eventfd_read(event->fds[0], &value);
+#else
+ char value;
+ ret = read(event->fds[0], &value, 1);
+#endif
+ } while (ret < 0 && errno == EINTR);
+ } while (ret >= 0);
+
+ return (errno == EAGAIN);
+}
+
+void winpr_event_uninit(WINPR_EVENT_IMPL* event)
+{
+ if (event->fds[0] != -1)
+ {
+ close(event->fds[0]);
+ event->fds[0] = -1;
+ }
+
+ if (event->fds[1] != -1)
+ {
+ close(event->fds[1]);
+ event->fds[1] = -1;
+ }
+}
+
+static BOOL EventCloseHandle(HANDLE handle);
+
+static BOOL EventIsHandled(HANDLE handle)
+{
+ return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_EVENT, FALSE);
+}
+
+static int EventGetFd(HANDLE handle)
+{
+ WINPR_EVENT* event = (WINPR_EVENT*)handle;
+
+ if (!EventIsHandled(handle))
+ return -1;
+
+ return event->impl.fds[0];
+}
+
+static BOOL EventCloseHandle_(WINPR_EVENT* event)
+{
+ if (!event)
+ return FALSE;
+
+ if (event->bAttached)
+ {
+ // don't close attached file descriptor
+ event->impl.fds[0] = -1; // mark as invalid
+ }
+
+ winpr_event_uninit(&event->impl);
+
+#if defined(WITH_DEBUG_EVENTS)
+ if (global_event_list)
+ {
+ ArrayList_Remove(global_event_list, event);
+ if (ArrayList_Count(global_event_list) < 1)
+ {
+ ArrayList_Free(global_event_list);
+ global_event_list = NULL;
+ }
+ }
+
+ winpr_backtrace_free(event->create_stack);
+#endif
+ free(event->name);
+ free(event);
+ return TRUE;
+}
+
+static BOOL EventCloseHandle(HANDLE handle)
+{
+ WINPR_EVENT* event = (WINPR_EVENT*)handle;
+
+ if (!EventIsHandled(handle))
+ return FALSE;
+
+ return EventCloseHandle_(event);
+}
+
+static HANDLE_OPS ops = { EventIsHandled,
+ EventCloseHandle,
+ EventGetFd,
+ NULL, /* CleanupHandle */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL };
+
+HANDLE CreateEventW(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState,
+ LPCWSTR lpName)
+{
+ HANDLE handle = NULL;
+ char* name = NULL;
+
+ if (lpName)
+ {
+ name = ConvertWCharToUtf8Alloc(lpName, NULL);
+ if (!name)
+ return NULL;
+ }
+
+ handle = CreateEventA(lpEventAttributes, bManualReset, bInitialState, name);
+ free(name);
+ return handle;
+}
+
+HANDLE CreateEventA(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState,
+ LPCSTR lpName)
+{
+ WINPR_EVENT* event = (WINPR_EVENT*)calloc(1, sizeof(WINPR_EVENT));
+
+ if (lpEventAttributes)
+ WLog_WARN(TAG, "[%s] does not support lpEventAttributes", lpName);
+
+ if (!event)
+ return NULL;
+
+ if (lpName)
+ event->name = strdup(lpName);
+
+ event->impl.fds[0] = -1;
+ event->impl.fds[1] = -1;
+ event->bAttached = FALSE;
+ event->bManualReset = bManualReset;
+ event->common.ops = &ops;
+ WINPR_HANDLE_SET_TYPE_AND_MODE(event, HANDLE_TYPE_EVENT, FD_READ);
+
+ if (!event->bManualReset)
+ WLog_ERR(TAG, "auto-reset events not yet implemented");
+
+ if (!winpr_event_init(&event->impl))
+ goto fail;
+
+ if (bInitialState)
+ {
+ if (!SetEvent(event))
+ goto fail;
+ }
+
+#if defined(WITH_DEBUG_EVENTS)
+ event->create_stack = winpr_backtrace(20);
+ if (!global_event_list)
+ global_event_list = ArrayList_New(TRUE);
+
+ if (global_event_list)
+ ArrayList_Append(global_event_list, event);
+#endif
+ return (HANDLE)event;
+fail:
+ EventCloseHandle_(event);
+ return NULL;
+}
+
+HANDLE CreateEventExW(LPSECURITY_ATTRIBUTES lpEventAttributes, LPCWSTR lpName, DWORD dwFlags,
+ DWORD dwDesiredAccess)
+{
+ BOOL initial = FALSE;
+ BOOL manual = FALSE;
+
+ if (dwFlags & CREATE_EVENT_INITIAL_SET)
+ initial = TRUE;
+
+ if (dwFlags & CREATE_EVENT_MANUAL_RESET)
+ manual = TRUE;
+
+ if (dwDesiredAccess != 0)
+ WLog_WARN(TAG, "[%s] does not support dwDesiredAccess 0x%08" PRIx32, lpName,
+ dwDesiredAccess);
+
+ return CreateEventW(lpEventAttributes, manual, initial, lpName);
+}
+
+HANDLE CreateEventExA(LPSECURITY_ATTRIBUTES lpEventAttributes, LPCSTR lpName, DWORD dwFlags,
+ DWORD dwDesiredAccess)
+{
+ BOOL initial = FALSE;
+ BOOL manual = FALSE;
+
+ if (dwFlags & CREATE_EVENT_INITIAL_SET)
+ initial = TRUE;
+
+ if (dwFlags & CREATE_EVENT_MANUAL_RESET)
+ manual = TRUE;
+
+ if (dwDesiredAccess != 0)
+ WLog_WARN(TAG, "[%s] does not support dwDesiredAccess 0x%08" PRIx32, lpName,
+ dwDesiredAccess);
+
+ return CreateEventA(lpEventAttributes, manual, initial, lpName);
+}
+
+HANDLE OpenEventW(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCWSTR lpName)
+{
+ /* TODO: Implement */
+ WINPR_UNUSED(dwDesiredAccess);
+ WINPR_UNUSED(bInheritHandle);
+ WINPR_UNUSED(lpName);
+ WLog_ERR(TAG, "not implemented");
+ return NULL;
+}
+
+HANDLE OpenEventA(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCSTR lpName)
+{
+ /* TODO: Implement */
+ WINPR_UNUSED(dwDesiredAccess);
+ WINPR_UNUSED(bInheritHandle);
+ WINPR_UNUSED(lpName);
+ WLog_ERR(TAG, "not implemented");
+ return NULL;
+}
+
+BOOL SetEvent(HANDLE hEvent)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+ WINPR_EVENT* event = NULL;
+
+ if (!winpr_Handle_GetInfo(hEvent, &Type, &Object) || Type != HANDLE_TYPE_EVENT)
+ {
+ WLog_ERR(TAG, "SetEvent: hEvent is not an event");
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ event = (WINPR_EVENT*)Object;
+ return winpr_event_set(&event->impl);
+}
+
+BOOL ResetEvent(HANDLE hEvent)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+ WINPR_EVENT* event = NULL;
+
+ if (!winpr_Handle_GetInfo(hEvent, &Type, &Object) || Type != HANDLE_TYPE_EVENT)
+ {
+ WLog_ERR(TAG, "ResetEvent: hEvent is not an event");
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ event = (WINPR_EVENT*)Object;
+ return winpr_event_reset(&event->impl);
+}
+
+#endif
+
+HANDLE CreateFileDescriptorEventW(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset,
+ BOOL bInitialState, int FileDescriptor, ULONG mode)
+{
+#ifndef _WIN32
+ WINPR_EVENT* event = NULL;
+ HANDLE handle = NULL;
+ event = (WINPR_EVENT*)calloc(1, sizeof(WINPR_EVENT));
+
+ if (event)
+ {
+ event->impl.fds[0] = -1;
+ event->impl.fds[1] = -1;
+ event->bAttached = TRUE;
+ event->bManualReset = bManualReset;
+ winpr_event_init_from_fd(&event->impl, FileDescriptor);
+ event->common.ops = &ops;
+ WINPR_HANDLE_SET_TYPE_AND_MODE(event, HANDLE_TYPE_EVENT, mode);
+ handle = (HANDLE)event;
+ }
+
+ return handle;
+#else
+ return NULL;
+#endif
+}
+
+HANDLE CreateFileDescriptorEventA(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset,
+ BOOL bInitialState, int FileDescriptor, ULONG mode)
+{
+ return CreateFileDescriptorEventW(lpEventAttributes, bManualReset, bInitialState,
+ FileDescriptor, mode);
+}
+
+/**
+ * Returns an event based on the handle returned by GetEventWaitObject()
+ */
+HANDLE CreateWaitObjectEvent(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset,
+ BOOL bInitialState, void* pObject)
+{
+#ifndef _WIN32
+ return CreateFileDescriptorEventW(lpEventAttributes, bManualReset, bInitialState,
+ (int)(ULONG_PTR)pObject, WINPR_FD_READ);
+#else
+ HANDLE hEvent = NULL;
+ DuplicateHandle(GetCurrentProcess(), pObject, GetCurrentProcess(), &hEvent, 0, FALSE,
+ DUPLICATE_SAME_ACCESS);
+ return hEvent;
+#endif
+}
+
+/*
+ * Returns inner file descriptor for usage with select()
+ * This file descriptor is not usable on Windows
+ */
+
+int GetEventFileDescriptor(HANDLE hEvent)
+{
+#ifndef _WIN32
+ return winpr_Handle_getFd(hEvent);
+#else
+ return -1;
+#endif
+}
+
+/*
+ * Set inner file descriptor for usage with select()
+ * This file descriptor is not usable on Windows
+ */
+
+int SetEventFileDescriptor(HANDLE hEvent, int FileDescriptor, ULONG mode)
+{
+#ifndef _WIN32
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+ WINPR_EVENT* event = NULL;
+
+ if (!winpr_Handle_GetInfo(hEvent, &Type, &Object) || Type != HANDLE_TYPE_EVENT)
+ {
+ WLog_ERR(TAG, "SetEventFileDescriptor: hEvent is not an event");
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return -1;
+ }
+
+ event = (WINPR_EVENT*)Object;
+
+ if (!event->bAttached && event->impl.fds[0] >= 0 && event->impl.fds[0] != FileDescriptor)
+ close(event->impl.fds[0]);
+
+ event->bAttached = TRUE;
+ event->common.Mode = mode;
+ event->impl.fds[0] = FileDescriptor;
+ return 0;
+#else
+ return -1;
+#endif
+}
+
+/**
+ * Returns platform-specific wait object as a void pointer
+ *
+ * On Windows, the returned object is the same as the hEvent
+ * argument and is an event HANDLE usable in WaitForMultipleObjects
+ *
+ * On other platforms, the returned object can be cast to an int
+ * to obtain a file descriptor usable in select()
+ */
+
+void* GetEventWaitObject(HANDLE hEvent)
+{
+#ifndef _WIN32
+ int fd = 0;
+ void* obj = NULL;
+ fd = GetEventFileDescriptor(hEvent);
+ obj = ((void*)(long)fd);
+ return obj;
+#else
+ return hEvent;
+#endif
+}
+#if defined(WITH_DEBUG_EVENTS)
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+static BOOL dump_handle_list(void* data, size_t index, va_list ap)
+{
+ WINPR_EVENT* event = data;
+ dump_event(event, index);
+ return TRUE;
+}
+
+void DumpEventHandles_(const char* fkt, const char* file, size_t line)
+{
+ struct rlimit r = { 0 };
+ int rc = getrlimit(RLIMIT_NOFILE, &r);
+ if (rc >= 0)
+ {
+ size_t count = 0;
+ for (rlim_t x = 0; x < r.rlim_cur; x++)
+ {
+ int flags = fcntl(x, F_GETFD);
+ if (flags >= 0)
+ count++;
+ }
+ WLog_INFO(TAG, "------- limits [%d/%d] open files %" PRIuz, r.rlim_cur, r.rlim_max, count);
+ }
+ WLog_DBG(TAG, "--------- Start dump [%s %s:%" PRIuz "]", fkt, file, line);
+ if (global_event_list)
+ {
+ ArrayList_Lock(global_event_list);
+ ArrayList_ForEach(global_event_list, dump_handle_list);
+ ArrayList_Unlock(global_event_list);
+ }
+ WLog_DBG(TAG, "--------- End dump [%s %s:%" PRIuz "]", fkt, file, line);
+}
+#endif
diff --git a/winpr/libwinpr/synch/event.h b/winpr/libwinpr/synch/event.h
new file mode 100644
index 0000000..0f57374
--- /dev/null
+++ b/winpr/libwinpr/synch/event.h
@@ -0,0 +1,57 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * event 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_LIBWINPR_SYNCH_EVENT_H_
+#define WINPR_LIBWINPR_SYNCH_EVENT_H_
+
+#include "../handle/handle.h"
+
+#include <winpr/config.h>
+
+#ifdef WINPR_HAVE_SYS_EVENTFD_H
+#include <sys/eventfd.h>
+#endif
+
+struct winpr_event_impl
+{
+ int fds[2];
+};
+
+typedef struct winpr_event_impl WINPR_EVENT_IMPL;
+
+struct winpr_event
+{
+ WINPR_HANDLE common;
+
+ WINPR_EVENT_IMPL impl;
+ BOOL bAttached;
+ BOOL bManualReset;
+ char* name;
+#if defined(WITH_DEBUG_EVENTS)
+ void* create_stack;
+#endif
+};
+typedef struct winpr_event WINPR_EVENT;
+
+BOOL winpr_event_init(WINPR_EVENT_IMPL* event);
+void winpr_event_init_from_fd(WINPR_EVENT_IMPL* event, int fd);
+BOOL winpr_event_set(WINPR_EVENT_IMPL* event);
+BOOL winpr_event_reset(WINPR_EVENT_IMPL* event);
+void winpr_event_uninit(WINPR_EVENT_IMPL* event);
+
+#endif /* WINPR_LIBWINPR_SYNCH_EVENT_H_ */
diff --git a/winpr/libwinpr/synch/init.c b/winpr/libwinpr/synch/init.c
new file mode 100644
index 0000000..0e383eb
--- /dev/null
+++ b/winpr/libwinpr/synch/init.c
@@ -0,0 +1,96 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Synchronization Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 Thincast Technologies GmbH
+ * Copyright 2014 Norbert Federa <norbert.federa@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/synch.h>
+#include <winpr/interlocked.h>
+
+#include "../log.h"
+#define TAG WINPR_TAG("sync")
+
+#if (!defined(_WIN32)) || (defined(_WIN32) && (_WIN32_WINNT < 0x0600))
+
+BOOL winpr_InitOnceBeginInitialize(LPINIT_ONCE lpInitOnce, DWORD dwFlags, PBOOL fPending,
+ LPVOID* lpContext)
+{
+ WLog_ERR(TAG, "not implemented");
+ return FALSE;
+}
+
+BOOL winpr_InitOnceComplete(LPINIT_ONCE lpInitOnce, DWORD dwFlags, LPVOID lpContext)
+{
+ WLog_ERR(TAG, "not implemented");
+ return FALSE;
+}
+
+VOID winpr_InitOnceInitialize(PINIT_ONCE InitOnce)
+{
+ WLog_ERR(TAG, "not implemented");
+}
+
+BOOL winpr_InitOnceExecuteOnce(PINIT_ONCE InitOnce, PINIT_ONCE_FN InitFn, PVOID Parameter,
+ LPVOID* Context)
+{
+ for (;;)
+ {
+ switch ((ULONG_PTR)InitOnce->Ptr & 3)
+ {
+ case 2:
+ /* already completed successfully */
+ return TRUE;
+
+ case 0:
+
+ /* first time */
+ if (InterlockedCompareExchangePointer(&InitOnce->Ptr, (PVOID)1, (PVOID)0) !=
+ (PVOID)0)
+ {
+ /* some other thread was faster */
+ break;
+ }
+
+ /* it's our job to call the init function */
+ if (InitFn(InitOnce, Parameter, Context))
+ {
+ /* success */
+ InitOnce->Ptr = (PVOID)2;
+ return TRUE;
+ }
+
+ /* the init function returned an error, reset the status */
+ InitOnce->Ptr = (PVOID)0;
+ return FALSE;
+
+ case 1:
+ /* in progress */
+ break;
+
+ default:
+ WLog_ERR(TAG, "internal error");
+ return FALSE;
+ }
+
+ Sleep(5);
+ }
+}
+
+#endif
diff --git a/winpr/libwinpr/synch/mutex.c b/winpr/libwinpr/synch/mutex.c
new file mode 100644
index 0000000..6a85db6
--- /dev/null
+++ b/winpr/libwinpr/synch/mutex.c
@@ -0,0 +1,247 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Synchronization 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/synch.h>
+#include <winpr/debug.h>
+#include <winpr/wlog.h>
+#include <winpr/string.h>
+
+#include "synch.h"
+
+#ifndef _WIN32
+
+#include <errno.h>
+
+#include "../handle/handle.h"
+
+#include "../log.h"
+#define TAG WINPR_TAG("sync.mutex")
+
+static BOOL MutexCloseHandle(HANDLE handle);
+
+static BOOL MutexIsHandled(HANDLE handle)
+{
+ return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_MUTEX, FALSE);
+}
+
+static int MutexGetFd(HANDLE handle)
+{
+ WINPR_MUTEX* mux = (WINPR_MUTEX*)handle;
+
+ if (!MutexIsHandled(handle))
+ return -1;
+
+ /* TODO: Mutex does not support file handles... */
+ (void)mux;
+ return -1;
+}
+
+BOOL MutexCloseHandle(HANDLE handle)
+{
+ WINPR_MUTEX* mutex = (WINPR_MUTEX*)handle;
+ int rc = 0;
+
+ if (!MutexIsHandled(handle))
+ return FALSE;
+
+ if ((rc = pthread_mutex_destroy(&mutex->mutex)))
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "pthread_mutex_destroy failed with %s [%d]",
+ winpr_strerror(rc, ebuffer, sizeof(ebuffer)), rc);
+#if defined(WITH_DEBUG_MUTEX)
+ {
+ size_t used = 0;
+ void* stack = winpr_backtrace(20);
+ char** msg = NULL;
+
+ if (stack)
+ msg = winpr_backtrace_symbols(stack, &used);
+
+ if (msg)
+ {
+ for (size_t i = 0; i < used; i++)
+ WLog_ERR(TAG, "%2" PRIdz ": %s", i, msg[i]);
+ }
+
+ free(msg);
+ winpr_backtrace_free(stack);
+ }
+#endif
+ /**
+ * Note: unfortunately we may not return FALSE here since CloseHandle(hmutex) on
+ * Windows always seems to succeed independently of the mutex object locking state
+ */
+ }
+
+ free(mutex->name);
+ free(handle);
+ return TRUE;
+}
+
+static HANDLE_OPS ops = { MutexIsHandled,
+ MutexCloseHandle,
+ MutexGetFd,
+ NULL, /* CleanupHandle */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL };
+
+HANDLE CreateMutexW(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCWSTR lpName)
+{
+ HANDLE handle = NULL;
+ char* name = NULL;
+
+ if (lpName)
+ {
+ name = ConvertWCharToUtf8Alloc(lpName, NULL);
+ if (!name)
+ return NULL;
+ }
+
+ handle = CreateMutexA(lpMutexAttributes, bInitialOwner, name);
+ free(name);
+ return handle;
+}
+
+HANDLE CreateMutexA(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCSTR lpName)
+{
+ HANDLE handle = NULL;
+ WINPR_MUTEX* mutex = NULL;
+ mutex = (WINPR_MUTEX*)calloc(1, sizeof(WINPR_MUTEX));
+
+ if (lpMutexAttributes)
+ WLog_WARN(TAG, "[%s] does not support lpMutexAttributes", lpName);
+
+ if (mutex)
+ {
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+ pthread_mutex_init(&mutex->mutex, &attr);
+ WINPR_HANDLE_SET_TYPE_AND_MODE(mutex, HANDLE_TYPE_MUTEX, WINPR_FD_READ);
+ mutex->common.ops = &ops;
+ handle = (HANDLE)mutex;
+
+ if (bInitialOwner)
+ pthread_mutex_lock(&mutex->mutex);
+
+ if (lpName)
+ mutex->name = strdup(lpName); /* Non runtime relevant information, skip NULL check */
+ }
+
+ return handle;
+}
+
+HANDLE CreateMutexExA(LPSECURITY_ATTRIBUTES lpMutexAttributes, LPCSTR lpName, DWORD dwFlags,
+ DWORD dwDesiredAccess)
+{
+ BOOL initial = FALSE;
+ /* TODO: support access modes */
+
+ if (dwDesiredAccess != 0)
+ WLog_WARN(TAG, "[%s] does not support dwDesiredAccess 0x%08" PRIx32, lpName,
+ dwDesiredAccess);
+
+ if (dwFlags & CREATE_MUTEX_INITIAL_OWNER)
+ initial = TRUE;
+
+ return CreateMutexA(lpMutexAttributes, initial, lpName);
+}
+
+HANDLE CreateMutexExW(LPSECURITY_ATTRIBUTES lpMutexAttributes, LPCWSTR lpName, DWORD dwFlags,
+ DWORD dwDesiredAccess)
+{
+ BOOL initial = FALSE;
+
+ /* TODO: support access modes */
+ if (dwDesiredAccess != 0)
+ WLog_WARN(TAG, "[%s] does not support dwDesiredAccess 0x%08" PRIx32, lpName,
+ dwDesiredAccess);
+
+ if (dwFlags & CREATE_MUTEX_INITIAL_OWNER)
+ initial = TRUE;
+
+ return CreateMutexW(lpMutexAttributes, initial, lpName);
+}
+
+HANDLE OpenMutexA(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCSTR lpName)
+{
+ /* TODO: Implement */
+ WINPR_UNUSED(dwDesiredAccess);
+ WINPR_UNUSED(bInheritHandle);
+ WINPR_UNUSED(lpName);
+ WLog_ERR(TAG, "TODO: Implement");
+ return NULL;
+}
+
+HANDLE OpenMutexW(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCWSTR lpName)
+{
+ /* TODO: Implement */
+ WINPR_UNUSED(dwDesiredAccess);
+ WINPR_UNUSED(bInheritHandle);
+ WINPR_UNUSED(lpName);
+ WLog_ERR(TAG, "TODO: Implement");
+ return NULL;
+}
+
+BOOL ReleaseMutex(HANDLE hMutex)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+
+ if (!winpr_Handle_GetInfo(hMutex, &Type, &Object))
+ return FALSE;
+
+ if (Type == HANDLE_TYPE_MUTEX)
+ {
+ WINPR_MUTEX* mutex = (WINPR_MUTEX*)Object;
+ int rc = pthread_mutex_unlock(&mutex->mutex);
+
+ if (rc)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "pthread_mutex_unlock failed with %s [%d]",
+ winpr_strerror(rc, ebuffer, sizeof(ebuffer)), rc);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+#endif
diff --git a/winpr/libwinpr/synch/pollset.c b/winpr/libwinpr/synch/pollset.c
new file mode 100644
index 0000000..8711ea0
--- /dev/null
+++ b/winpr/libwinpr/synch/pollset.c
@@ -0,0 +1,274 @@
+#ifndef _WIN32
+#include <errno.h>
+
+#include "pollset.h"
+#include <winpr/handle.h>
+#include <winpr/sysinfo.h>
+#include <winpr/assert.h>
+#include "../log.h"
+
+#define TAG WINPR_TAG("sync.pollset")
+
+#ifdef WINPR_HAVE_POLL_H
+static INT16 handle_mode_to_pollevent(ULONG mode)
+{
+ INT16 event = 0;
+
+ if (mode & WINPR_FD_READ)
+ event |= POLLIN;
+
+ if (mode & WINPR_FD_WRITE)
+ event |= POLLOUT;
+
+ return event;
+}
+#endif
+
+BOOL pollset_init(WINPR_POLL_SET* set, size_t nhandles)
+{
+ WINPR_ASSERT(set);
+#ifdef WINPR_HAVE_POLL_H
+ if (nhandles > MAXIMUM_WAIT_OBJECTS)
+ {
+ set->isStatic = FALSE;
+ set->pollset = calloc(nhandles, sizeof(*set->pollset));
+ if (!set->pollset)
+ return FALSE;
+ }
+ else
+ {
+ set->pollset = set->staticSet;
+ set->isStatic = TRUE;
+ }
+#else
+ set->fdIndex = calloc(nhandles, sizeof(*set->fdIndex));
+ if (!set->fdIndex)
+ return FALSE;
+
+ FD_ZERO(&set->rset_base);
+ FD_ZERO(&set->rset);
+ FD_ZERO(&set->wset_base);
+ FD_ZERO(&set->wset);
+ set->maxFd = 0;
+ set->nread = set->nwrite = 0;
+#endif
+
+ set->size = nhandles;
+ set->fillIndex = 0;
+ return TRUE;
+}
+
+void pollset_uninit(WINPR_POLL_SET* set)
+{
+ WINPR_ASSERT(set);
+#ifdef WINPR_HAVE_POLL_H
+ if (!set->isStatic)
+ free(set->pollset);
+#else
+ free(set->fdIndex);
+#endif
+}
+
+void pollset_reset(WINPR_POLL_SET* set)
+{
+ WINPR_ASSERT(set);
+#ifndef WINPR_HAVE_POLL_H
+ FD_ZERO(&set->rset_base);
+ FD_ZERO(&set->wset_base);
+ set->maxFd = 0;
+ set->nread = set->nwrite = 0;
+#endif
+ set->fillIndex = 0;
+}
+
+BOOL pollset_add(WINPR_POLL_SET* set, int fd, ULONG mode)
+{
+ WINPR_ASSERT(set);
+#ifdef WINPR_HAVE_POLL_H
+ struct pollfd* item = NULL;
+ if (set->fillIndex == set->size)
+ return FALSE;
+
+ item = &set->pollset[set->fillIndex];
+ item->fd = fd;
+ item->revents = 0;
+ item->events = handle_mode_to_pollevent(mode);
+#else
+ FdIndex* fdIndex = &set->fdIndex[set->fillIndex];
+ if (mode & WINPR_FD_READ)
+ {
+ FD_SET(fd, &set->rset_base);
+ set->nread++;
+ }
+
+ if (mode & WINPR_FD_WRITE)
+ {
+ FD_SET(fd, &set->wset_base);
+ set->nwrite++;
+ }
+
+ if (fd > set->maxFd)
+ set->maxFd = fd;
+
+ fdIndex->fd = fd;
+ fdIndex->mode = mode;
+#endif
+ set->fillIndex++;
+ return TRUE;
+}
+
+int pollset_poll(WINPR_POLL_SET* set, DWORD dwMilliseconds)
+{
+ WINPR_ASSERT(set);
+ int ret = 0;
+ UINT64 dueTime = 0;
+ UINT64 now = 0;
+
+ now = GetTickCount64();
+ if (dwMilliseconds == INFINITE)
+ dueTime = 0xFFFFFFFFFFFFFFFF;
+ else
+ dueTime = now + dwMilliseconds;
+
+#ifdef WINPR_HAVE_POLL_H
+ int timeout = 0;
+
+ do
+ {
+ if (dwMilliseconds == INFINITE)
+ timeout = -1;
+ else
+ timeout = (int)(dueTime - now);
+
+ ret = poll(set->pollset, set->fillIndex, timeout);
+ if (ret >= 0)
+ return ret;
+
+ if (errno != EINTR)
+ return -1;
+
+ now = GetTickCount64();
+ } while (now < dueTime);
+
+#else
+ do
+ {
+ struct timeval staticTimeout;
+ struct timeval* timeout;
+
+ fd_set* rset = NULL;
+ fd_set* wset = NULL;
+
+ if (dwMilliseconds == INFINITE)
+ {
+ timeout = NULL;
+ }
+ else
+ {
+ long waitTime = (long)(dueTime - now);
+
+ timeout = &staticTimeout;
+ timeout->tv_sec = waitTime / 1000;
+ timeout->tv_usec = (waitTime % 1000) * 1000;
+ }
+
+ if (set->nread)
+ {
+ rset = &set->rset;
+ memcpy(rset, &set->rset_base, sizeof(*rset));
+ }
+
+ if (set->nwrite)
+ {
+ wset = &set->wset;
+ memcpy(wset, &set->wset_base, sizeof(*wset));
+ }
+
+ ret = select(set->maxFd + 1, rset, wset, NULL, timeout);
+ if (ret >= 0)
+ return ret;
+
+ if (errno != EINTR)
+ return -1;
+
+ now = GetTickCount64();
+
+ } while (now < dueTime);
+
+ FD_ZERO(&set->rset);
+ FD_ZERO(&set->wset);
+#endif
+
+ return 0; /* timeout */
+}
+
+BOOL pollset_isSignaled(WINPR_POLL_SET* set, size_t idx)
+{
+ WINPR_ASSERT(set);
+
+ if (idx > set->fillIndex)
+ {
+ WLog_ERR(TAG, "index=%d out of pollset(fillIndex=%" PRIuz ")", idx, set->fillIndex);
+ return FALSE;
+ }
+
+#ifdef WINPR_HAVE_POLL_H
+ return !!(set->pollset[idx].revents & set->pollset[idx].events);
+#else
+ FdIndex* fdIndex = &set->fdIndex[idx];
+ if (fdIndex->fd < 0)
+ return FALSE;
+
+ if ((fdIndex->mode & WINPR_FD_READ) && FD_ISSET(fdIndex->fd, &set->rset))
+ return TRUE;
+
+ if ((fdIndex->mode & WINPR_FD_WRITE) && FD_ISSET(fdIndex->fd, &set->wset))
+ return TRUE;
+
+ return FALSE;
+#endif
+}
+
+BOOL pollset_isReadSignaled(WINPR_POLL_SET* set, size_t idx)
+{
+ WINPR_ASSERT(set);
+
+ if (idx > set->fillIndex)
+ {
+ WLog_ERR(TAG, "index=%d out of pollset(fillIndex=%" PRIuz ")", idx, set->fillIndex);
+ return FALSE;
+ }
+
+#ifdef WINPR_HAVE_POLL_H
+ return !!(set->pollset[idx].revents & POLLIN);
+#else
+ FdIndex* fdIndex = &set->fdIndex[idx];
+ if (fdIndex->fd < 0)
+ return FALSE;
+
+ return FD_ISSET(fdIndex->fd, &set->rset);
+#endif
+}
+
+BOOL pollset_isWriteSignaled(WINPR_POLL_SET* set, size_t idx)
+{
+ WINPR_ASSERT(set);
+
+ if (idx > set->fillIndex)
+ {
+ WLog_ERR(TAG, "index=%d out of pollset(fillIndex=%" PRIuz ")", idx, set->fillIndex);
+ return FALSE;
+ }
+
+#ifdef WINPR_HAVE_POLL_H
+ return !!(set->pollset[idx].revents & POLLOUT);
+#else
+ FdIndex* fdIndex = &set->fdIndex[idx];
+ if (fdIndex->fd < 0)
+ return FALSE;
+
+ return FD_ISSET(fdIndex->fd, &set->wset);
+#endif
+}
+
+#endif
diff --git a/winpr/libwinpr/synch/pollset.h b/winpr/libwinpr/synch/pollset.h
new file mode 100644
index 0000000..6e478e6
--- /dev/null
+++ b/winpr/libwinpr/synch/pollset.h
@@ -0,0 +1,74 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * pollset
+ *
+ * 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_LIBWINPR_SYNCH_POLLSET_H_
+#define WINPR_LIBWINPR_SYNCH_POLLSET_H_
+
+#include <winpr/wtypes.h>
+#include <winpr/synch.h>
+
+#include <winpr/config.h>
+
+#ifndef _WIN32
+
+#ifdef WINPR_HAVE_POLL_H
+#include <poll.h>
+#else
+#include <sys/select.h>
+
+typedef struct
+{
+ int fd;
+ ULONG mode;
+} FdIndex;
+#endif
+
+struct winpr_poll_set
+{
+#ifdef WINPR_HAVE_POLL_H
+ struct pollfd* pollset;
+ struct pollfd staticSet[MAXIMUM_WAIT_OBJECTS];
+ BOOL isStatic;
+#else
+ FdIndex* fdIndex;
+ fd_set rset_base;
+ fd_set rset;
+ fd_set wset_base;
+ fd_set wset;
+ int nread, nwrite;
+ int maxFd;
+#endif
+ size_t fillIndex;
+ size_t size;
+};
+
+typedef struct winpr_poll_set WINPR_POLL_SET;
+
+BOOL pollset_init(WINPR_POLL_SET* set, size_t nhandles);
+void pollset_uninit(WINPR_POLL_SET* set);
+void pollset_reset(WINPR_POLL_SET* set);
+BOOL pollset_add(WINPR_POLL_SET* set, int fd, ULONG mode);
+int pollset_poll(WINPR_POLL_SET* set, DWORD dwMilliseconds);
+
+BOOL pollset_isSignaled(WINPR_POLL_SET* set, size_t idx);
+BOOL pollset_isReadSignaled(WINPR_POLL_SET* set, size_t idx);
+BOOL pollset_isWriteSignaled(WINPR_POLL_SET* set, size_t idx);
+
+#endif
+
+#endif /* WINPR_LIBWINPR_SYNCH_POLLSET_H_ */
diff --git a/winpr/libwinpr/synch/semaphore.c b/winpr/libwinpr/synch/semaphore.c
new file mode 100644
index 0000000..855675b
--- /dev/null
+++ b/winpr/libwinpr/synch/semaphore.c
@@ -0,0 +1,257 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Synchronization 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/debug.h>
+#include <winpr/synch.h>
+
+#include "synch.h"
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifndef _WIN32
+
+#include <errno.h>
+#include "../handle/handle.h"
+#include "../log.h"
+#define TAG WINPR_TAG("synch.semaphore")
+
+static BOOL SemaphoreCloseHandle(HANDLE handle);
+
+static BOOL SemaphoreIsHandled(HANDLE handle)
+{
+ return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_SEMAPHORE, FALSE);
+}
+
+static int SemaphoreGetFd(HANDLE handle)
+{
+ WINPR_SEMAPHORE* sem = (WINPR_SEMAPHORE*)handle;
+
+ if (!SemaphoreIsHandled(handle))
+ return -1;
+
+ return sem->pipe_fd[0];
+}
+
+static DWORD SemaphoreCleanupHandle(HANDLE handle)
+{
+ SSIZE_T length = 0;
+ WINPR_SEMAPHORE* sem = (WINPR_SEMAPHORE*)handle;
+
+ if (!SemaphoreIsHandled(handle))
+ return WAIT_FAILED;
+
+ length = read(sem->pipe_fd[0], &length, 1);
+
+ if (length != 1)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "semaphore read() failure [%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ return WAIT_FAILED;
+ }
+
+ return WAIT_OBJECT_0;
+}
+
+BOOL SemaphoreCloseHandle(HANDLE handle)
+{
+ WINPR_SEMAPHORE* semaphore = (WINPR_SEMAPHORE*)handle;
+
+ if (!SemaphoreIsHandled(handle))
+ return FALSE;
+
+#ifdef WINPR_PIPE_SEMAPHORE
+
+ if (semaphore->pipe_fd[0] != -1)
+ {
+ close(semaphore->pipe_fd[0]);
+ semaphore->pipe_fd[0] = -1;
+
+ if (semaphore->pipe_fd[1] != -1)
+ {
+ close(semaphore->pipe_fd[1]);
+ semaphore->pipe_fd[1] = -1;
+ }
+ }
+
+#else
+#if defined __APPLE__
+ semaphore_destroy(mach_task_self(), *((winpr_sem_t*)semaphore->sem));
+#else
+ sem_destroy((winpr_sem_t*)semaphore->sem);
+#endif
+#endif
+ free(semaphore);
+ return TRUE;
+}
+
+static HANDLE_OPS ops = { SemaphoreIsHandled,
+ SemaphoreCloseHandle,
+ SemaphoreGetFd,
+ SemaphoreCleanupHandle,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL };
+
+HANDLE CreateSemaphoreW(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount,
+ LONG lMaximumCount, LPCWSTR lpName)
+{
+ HANDLE handle = NULL;
+ WINPR_SEMAPHORE* semaphore = NULL;
+ semaphore = (WINPR_SEMAPHORE*)calloc(1, sizeof(WINPR_SEMAPHORE));
+
+ if (!semaphore)
+ return NULL;
+
+ semaphore->pipe_fd[0] = -1;
+ semaphore->pipe_fd[1] = -1;
+ semaphore->sem = (winpr_sem_t*)NULL;
+ semaphore->common.ops = &ops;
+#ifdef WINPR_PIPE_SEMAPHORE
+
+ if (pipe(semaphore->pipe_fd) < 0)
+ {
+ WLog_ERR(TAG, "failed to create semaphore");
+ free(semaphore);
+ return NULL;
+ }
+
+ while (lInitialCount > 0)
+ {
+ if (write(semaphore->pipe_fd[1], "-", 1) != 1)
+ {
+ close(semaphore->pipe_fd[0]);
+ close(semaphore->pipe_fd[1]);
+ free(semaphore);
+ return NULL;
+ }
+
+ lInitialCount--;
+ }
+
+#else
+ semaphore->sem = (winpr_sem_t*)malloc(sizeof(winpr_sem_t));
+
+ if (!semaphore->sem)
+ {
+ WLog_ERR(TAG, "failed to allocate semaphore memory");
+ free(semaphore);
+ return NULL;
+ }
+
+#if defined __APPLE__
+
+ if (semaphore_create(mach_task_self(), semaphore->sem, SYNC_POLICY_FIFO, lMaximumCount) !=
+ KERN_SUCCESS)
+#else
+ if (sem_init(semaphore->sem, 0, lMaximumCount) == -1)
+#endif
+ {
+ WLog_ERR(TAG, "failed to create semaphore");
+ free(semaphore->sem);
+ free(semaphore);
+ return NULL;
+ }
+
+#endif
+ WINPR_HANDLE_SET_TYPE_AND_MODE(semaphore, HANDLE_TYPE_SEMAPHORE, WINPR_FD_READ);
+ handle = (HANDLE)semaphore;
+ return handle;
+}
+
+HANDLE CreateSemaphoreA(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount,
+ LONG lMaximumCount, LPCSTR lpName)
+{
+ return CreateSemaphoreW(lpSemaphoreAttributes, lInitialCount, lMaximumCount, NULL);
+}
+
+HANDLE OpenSemaphoreW(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCWSTR lpName)
+{
+ WLog_ERR(TAG, "not implemented");
+ return NULL;
+}
+
+HANDLE OpenSemaphoreA(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCSTR lpName)
+{
+ WLog_ERR(TAG, "not implemented");
+ return NULL;
+}
+
+BOOL ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+ WINPR_SEMAPHORE* semaphore = NULL;
+
+ if (!winpr_Handle_GetInfo(hSemaphore, &Type, &Object))
+ return FALSE;
+
+ if (Type == HANDLE_TYPE_SEMAPHORE)
+ {
+ semaphore = (WINPR_SEMAPHORE*)Object;
+#ifdef WINPR_PIPE_SEMAPHORE
+
+ if (semaphore->pipe_fd[0] != -1)
+ {
+ while (lReleaseCount > 0)
+ {
+ if (write(semaphore->pipe_fd[1], "-", 1) != 1)
+ return FALSE;
+
+ lReleaseCount--;
+ }
+ }
+
+#else
+
+ while (lReleaseCount > 0)
+ {
+#if defined __APPLE__
+ semaphore_signal(*((winpr_sem_t*)semaphore->sem));
+#else
+ sem_post((winpr_sem_t*)semaphore->sem);
+#endif
+ }
+
+#endif
+ return TRUE;
+ }
+
+ WLog_ERR(TAG, "called on a handle that is not a semaphore");
+ return FALSE;
+}
+
+#endif
diff --git a/winpr/libwinpr/synch/sleep.c b/winpr/libwinpr/synch/sleep.c
new file mode 100644
index 0000000..be2f4c6
--- /dev/null
+++ b/winpr/libwinpr/synch/sleep.c
@@ -0,0 +1,148 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Synchronization 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/platform.h>
+#include <winpr/windows.h>
+
+#include <winpr/synch.h>
+
+#include "../log.h"
+#include "../thread/apc.h"
+#include "../thread/thread.h"
+#include "../synch/pollset.h"
+
+#define TAG WINPR_TAG("synch.sleep")
+
+#ifndef _WIN32
+
+#include <time.h>
+
+WINPR_PRAGMA_DIAG_PUSH
+WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO
+
+#ifdef WINPR_HAVE_UNISTD_H
+#ifndef _XOPEN_SOURCE
+#define _XOPEN_SOURCE 500
+#endif
+#include <unistd.h>
+#endif
+
+WINPR_PRAGMA_DIAG_POP
+
+VOID Sleep(DWORD dwMilliseconds)
+{
+ usleep(dwMilliseconds * 1000);
+}
+
+DWORD SleepEx(DWORD dwMilliseconds, BOOL bAlertable)
+{
+ WINPR_THREAD* thread = winpr_GetCurrentThread();
+ WINPR_POLL_SET pollset;
+ int status = 0;
+ DWORD ret = WAIT_FAILED;
+ BOOL autoSignalled = 0;
+
+ if (thread)
+ {
+ /* treat re-entrancy if a completion is calling us */
+ if (thread->apc.treatingCompletions)
+ bAlertable = FALSE;
+ }
+ else
+ {
+ /* called from a non WinPR thread */
+ bAlertable = FALSE;
+ }
+
+ if (!bAlertable || !thread->apc.length)
+ {
+ usleep(dwMilliseconds * 1000);
+ return 0;
+ }
+
+ if (!pollset_init(&pollset, thread->apc.length))
+ {
+ WLog_ERR(TAG, "unable to initialize pollset");
+ return WAIT_FAILED;
+ }
+
+ if (!apc_collectFds(thread, &pollset, &autoSignalled))
+ {
+ WLog_ERR(TAG, "unable to APC file descriptors");
+ goto out;
+ }
+
+ if (!autoSignalled)
+ {
+ /* we poll and wait only if no APC member is ready */
+ status = pollset_poll(&pollset, dwMilliseconds);
+ if (status < 0)
+ {
+ WLog_ERR(TAG, "polling of apc fds failed");
+ goto out;
+ }
+ }
+
+ if (apc_executeCompletions(thread, &pollset, 0))
+ {
+ ret = WAIT_IO_COMPLETION;
+ }
+ else
+ {
+ /* according to the spec return value is 0 see
+ * https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-sleepex*/
+ ret = 0;
+ }
+out:
+ pollset_uninit(&pollset);
+ return ret;
+}
+
+#endif
+
+VOID USleep(DWORD dwMicroseconds)
+{
+#ifndef _WIN32
+ usleep(dwMicroseconds);
+#else
+ static LARGE_INTEGER freq = { 0 };
+ LARGE_INTEGER t1 = { 0 };
+ LARGE_INTEGER t2 = { 0 };
+
+ QueryPerformanceCounter(&t1);
+
+ if (freq.QuadPart == 0)
+ {
+ QueryPerformanceFrequency(&freq);
+ }
+
+ // in order to save cpu cyles we use Sleep() for the large share ...
+ if (dwMicroseconds >= 1000)
+ {
+ Sleep(dwMicroseconds / 1000);
+ }
+ // ... and busy loop until all the requested micro seconds have passed
+ do
+ {
+ QueryPerformanceCounter(&t2);
+ } while (((t2.QuadPart - t1.QuadPart) * 1000000) / freq.QuadPart < dwMicroseconds);
+#endif
+}
diff --git a/winpr/libwinpr/synch/synch.h b/winpr/libwinpr/synch/synch.h
new file mode 100644
index 0000000..5a9f08c
--- /dev/null
+++ b/winpr/libwinpr/synch/synch.h
@@ -0,0 +1,159 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Synchronization 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.
+ */
+
+#ifndef WINPR_SYNCH_PRIVATE_H
+#define WINPR_SYNCH_PRIVATE_H
+
+#include <winpr/config.h>
+
+#include <winpr/platform.h>
+
+#include <winpr/synch.h>
+
+#include "../handle/handle.h"
+#include "../thread/apc.h"
+#include "event.h"
+
+#ifndef _WIN32
+
+#define WINPR_PIPE_SEMAPHORE 1
+
+#if defined __APPLE__
+#include <pthread.h>
+#include <sys/time.h>
+#include <semaphore.h>
+#include <mach/mach.h>
+#include <mach/semaphore.h>
+#include <mach/task.h>
+#define winpr_sem_t semaphore_t
+#else
+#include <pthread.h>
+#include <semaphore.h>
+#define winpr_sem_t sem_t
+#endif
+
+struct winpr_mutex
+{
+ WINPR_HANDLE common;
+ char* name;
+ pthread_mutex_t mutex;
+};
+typedef struct winpr_mutex WINPR_MUTEX;
+
+struct winpr_semaphore
+{
+ WINPR_HANDLE common;
+
+ int pipe_fd[2];
+ winpr_sem_t* sem;
+};
+typedef struct winpr_semaphore WINPR_SEMAPHORE;
+
+#ifdef WINPR_HAVE_SYS_TIMERFD_H
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/timerfd.h>
+#define TIMER_IMPL_TIMERFD
+
+#elif defined(WITH_POSIX_TIMER)
+#include <fcntl.h>
+#define TIMER_IMPL_POSIX
+
+#elif defined(__APPLE__)
+#define TIMER_IMPL_DISPATCH
+#include <dispatch/dispatch.h>
+#else
+#warning missing timer implementation
+#endif
+
+struct winpr_timer
+{
+ WINPR_HANDLE common;
+
+ int fd;
+ BOOL bInit;
+ LONG lPeriod;
+ BOOL bManualReset;
+ PTIMERAPCROUTINE pfnCompletionRoutine;
+ LPVOID lpArgToCompletionRoutine;
+
+#ifdef TIMER_IMPL_TIMERFD
+ struct itimerspec timeout;
+#endif
+
+#ifdef TIMER_IMPL_POSIX
+ WINPR_EVENT_IMPL event;
+ timer_t tid;
+ struct itimerspec timeout;
+#endif
+
+#ifdef TIMER_IMPL_DISPATCH
+ WINPR_EVENT_IMPL event;
+ dispatch_queue_t queue;
+ dispatch_source_t source;
+ BOOL running;
+#endif
+ char* name;
+
+ WINPR_APC_ITEM apcItem;
+};
+typedef struct winpr_timer WINPR_TIMER;
+
+typedef struct winpr_timer_queue_timer WINPR_TIMER_QUEUE_TIMER;
+
+struct winpr_timer_queue
+{
+ WINPR_HANDLE common;
+
+ pthread_t thread;
+ pthread_attr_t attr;
+ pthread_mutex_t mutex;
+ pthread_cond_t cond;
+ pthread_mutex_t cond_mutex;
+ struct sched_param param;
+
+ BOOL bCancelled;
+ WINPR_TIMER_QUEUE_TIMER* activeHead;
+ WINPR_TIMER_QUEUE_TIMER* inactiveHead;
+};
+typedef struct winpr_timer_queue WINPR_TIMER_QUEUE;
+
+struct winpr_timer_queue_timer
+{
+ WINPR_HANDLE common;
+
+ ULONG Flags;
+ DWORD DueTime;
+ DWORD Period;
+ PVOID Parameter;
+ WAITORTIMERCALLBACK Callback;
+
+ int FireCount;
+
+ struct timespec StartTime;
+ struct timespec ExpirationTime;
+
+ WINPR_TIMER_QUEUE* timerQueue;
+ WINPR_TIMER_QUEUE_TIMER* next;
+};
+
+#endif
+
+#endif /* WINPR_SYNCH_PRIVATE_H */
diff --git a/winpr/libwinpr/synch/test/CMakeLists.txt b/winpr/libwinpr/synch/test/CMakeLists.txt
new file mode 100644
index 0000000..66d15c6
--- /dev/null
+++ b/winpr/libwinpr/synch/test/CMakeLists.txt
@@ -0,0 +1,41 @@
+
+set(MODULE_NAME "TestSynch")
+set(MODULE_PREFIX "TEST_SYNCH")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestSynchInit.c
+ TestSynchEvent.c
+ TestSynchMutex.c
+ TestSynchBarrier.c
+ TestSynchCritical.c
+ TestSynchSemaphore.c
+ TestSynchThread.c
+ # TestSynchMultipleThreads.c
+ TestSynchTimerQueue.c
+ TestSynchWaitableTimer.c
+ TestSynchWaitableTimerAPC.c
+ TestSynchAPC.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+if(FREEBSD)
+ include_directories(${EPOLLSHIM_INCLUDE_DIR})
+endif()
+
+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/synch/test/TestSynchAPC.c b/winpr/libwinpr/synch/test/TestSynchAPC.c
new file mode 100644
index 0000000..d6239d8
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchAPC.c
@@ -0,0 +1,173 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * TestSyncAPC
+ *
+ * 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/wtypes.h>
+#include <winpr/thread.h>
+#include <winpr/synch.h>
+
+typedef struct
+{
+ BOOL error;
+ BOOL called;
+} UserApcArg;
+
+static void CALLBACK userApc(ULONG_PTR arg)
+{
+ UserApcArg* userArg = (UserApcArg*)arg;
+ userArg->called = TRUE;
+}
+
+static DWORD WINAPI uncleanThread(LPVOID lpThreadParameter)
+{
+ /* this thread post an APC that will never get executed */
+ UserApcArg* userArg = (UserApcArg*)lpThreadParameter;
+ if (!QueueUserAPC((PAPCFUNC)userApc, _GetCurrentThread(), (ULONG_PTR)lpThreadParameter))
+ {
+ userArg->error = TRUE;
+ return 1;
+ }
+
+ return 0;
+}
+
+static DWORD WINAPI cleanThread(LPVOID lpThreadParameter)
+{
+ Sleep(500);
+
+ SleepEx(500, TRUE);
+ return 0;
+}
+
+typedef struct
+{
+ HANDLE timer1;
+ DWORD timer1Calls;
+ HANDLE timer2;
+ DWORD timer2Calls;
+ BOOL endTest;
+} UncleanCloseData;
+
+static VOID CALLBACK Timer1APCProc(LPVOID lpArg, DWORD dwTimerLowValue, DWORD dwTimerHighValue)
+{
+ UncleanCloseData* data = (UncleanCloseData*)lpArg;
+ data->timer1Calls++;
+ CloseHandle(data->timer2);
+ data->endTest = TRUE;
+}
+
+static VOID CALLBACK Timer2APCProc(LPVOID lpArg, DWORD dwTimerLowValue, DWORD dwTimerHighValue)
+{
+ UncleanCloseData* data = (UncleanCloseData*)lpArg;
+ data->timer2Calls++;
+}
+
+static DWORD /*WINAPI*/ closeHandleTest(LPVOID lpThreadParameter)
+{
+ LARGE_INTEGER dueTime;
+ UncleanCloseData* data = (UncleanCloseData*)lpThreadParameter;
+ data->endTest = FALSE;
+
+ dueTime.QuadPart = -500;
+ if (!SetWaitableTimer(data->timer1, &dueTime, 0, Timer1APCProc, lpThreadParameter, FALSE))
+ return 1;
+
+ dueTime.QuadPart = -900;
+ if (!SetWaitableTimer(data->timer2, &dueTime, 0, Timer2APCProc, lpThreadParameter, FALSE))
+ return 1;
+
+ while (!data->endTest)
+ {
+ SleepEx(100, TRUE);
+ }
+ return 0;
+}
+
+int TestSynchAPC(int argc, char* argv[])
+{
+ HANDLE thread = NULL;
+ UserApcArg userApcArg;
+
+ userApcArg.error = FALSE;
+ userApcArg.called = FALSE;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ /* first post an APC and check it is executed during a SleepEx */
+ if (!QueueUserAPC((PAPCFUNC)userApc, _GetCurrentThread(), (ULONG_PTR)&userApcArg))
+ return 1;
+
+ if (SleepEx(100, FALSE) != 0)
+ return 2;
+
+ if (SleepEx(100, TRUE) != WAIT_IO_COMPLETION)
+ return 3;
+
+ if (!userApcArg.called)
+ return 4;
+
+ userApcArg.called = FALSE;
+
+ /* test that the APC is cleaned up even when not called */
+ thread = CreateThread(NULL, 0, uncleanThread, &userApcArg, 0, NULL);
+ if (!thread)
+ return 10;
+ WaitForSingleObject(thread, INFINITE);
+ CloseHandle(thread);
+
+ if (userApcArg.called || userApcArg.error)
+ return 11;
+
+ /* test a remote APC queuing */
+ thread = CreateThread(NULL, 0, cleanThread, &userApcArg, 0, NULL);
+ if (!thread)
+ return 20;
+
+ if (!QueueUserAPC((PAPCFUNC)userApc, thread, (ULONG_PTR)&userApcArg))
+ return 21;
+
+ WaitForSingleObject(thread, INFINITE);
+ CloseHandle(thread);
+
+ if (!userApcArg.called)
+ return 22;
+
+#if 0
+ /* test cleanup of timer completions */
+ memset(&uncleanCloseData, 0, sizeof(uncleanCloseData));
+ uncleanCloseData.timer1 = CreateWaitableTimerA(NULL, FALSE, NULL);
+ if (!uncleanCloseData.timer1)
+ return 31;
+
+ uncleanCloseData.timer2 = CreateWaitableTimerA(NULL, FALSE, NULL);
+ if (!uncleanCloseData.timer2)
+ return 32;
+
+ thread = CreateThread(NULL, 0, closeHandleTest, &uncleanCloseData, 0, NULL);
+ if (!thread)
+ return 33;
+
+ WaitForSingleObject(thread, INFINITE);
+ CloseHandle(thread);
+
+ if (uncleanCloseData.timer1Calls != 1 || uncleanCloseData.timer2Calls != 0)
+ return 34;
+ CloseHandle(uncleanCloseData.timer1);
+#endif
+ return 0;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchBarrier.c b/winpr/libwinpr/synch/test/TestSynchBarrier.c
new file mode 100644
index 0000000..b1c91c9
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchBarrier.c
@@ -0,0 +1,257 @@
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/interlocked.h>
+#include <winpr/sysinfo.h>
+
+#include "../synch.h"
+
+static SYNCHRONIZATION_BARRIER gBarrier;
+static HANDLE gStartEvent = NULL;
+static LONG gErrorCount = 0;
+
+#define MAX_SLEEP_MS 22
+
+struct test_params
+{
+ LONG threadCount;
+ LONG trueCount;
+ LONG falseCount;
+ DWORD loops;
+ DWORD flags;
+};
+
+static DWORD WINAPI test_synch_barrier_thread(LPVOID lpParam)
+{
+ BOOL status = FALSE;
+ struct test_params* p = (struct test_params*)lpParam;
+
+ InterlockedIncrement(&p->threadCount);
+
+ // printf("Thread #%03u entered.\n", tnum);
+
+ /* wait for start event from main */
+ if (WaitForSingleObject(gStartEvent, INFINITE) != WAIT_OBJECT_0)
+ {
+ InterlockedIncrement(&gErrorCount);
+ goto out;
+ }
+
+ // printf("Thread #%03u unblocked.\n", tnum);
+
+ for (DWORD i = 0; i < p->loops && gErrorCount == 0; i++)
+ {
+ /* simulate different execution times before the barrier */
+ Sleep(1 + abs((rand() % MAX_SLEEP_MS)));
+ status = EnterSynchronizationBarrier(&gBarrier, p->flags);
+
+ // printf("Thread #%03u status: %s\n", tnum, status ? "TRUE" : "FALSE");
+ if (status)
+ InterlockedIncrement(&p->trueCount);
+ else
+ InterlockedIncrement(&p->falseCount);
+ }
+
+out:
+ // printf("Thread #%03u leaving.\n", tnum);
+ return 0;
+}
+
+static BOOL TestSynchBarrierWithFlags(DWORD dwFlags, DWORD dwThreads, DWORD dwLoops)
+{
+ HANDLE* threads = NULL;
+ struct test_params p;
+ DWORD dwStatus = 0;
+ DWORD expectedTrueCount = 0;
+ DWORD expectedFalseCount = 0;
+ p.threadCount = 0;
+ p.trueCount = 0;
+ p.falseCount = 0;
+ p.loops = dwLoops;
+ p.flags = dwFlags;
+ expectedTrueCount = dwLoops;
+ expectedFalseCount = dwLoops * (dwThreads - 1);
+ printf("%s: >> Testing with flags 0x%08" PRIx32 ". Using %" PRIu32
+ " threads performing %" PRIu32 " loops\n",
+ __func__, dwFlags, dwThreads, dwLoops);
+
+ if (!(threads = calloc(dwThreads, sizeof(HANDLE))))
+ {
+ printf("%s: error allocatin thread array memory\n", __func__);
+ return FALSE;
+ }
+
+ if (!InitializeSynchronizationBarrier(&gBarrier, dwThreads, -1))
+ {
+ printf("%s: InitializeSynchronizationBarrier failed. GetLastError() = 0x%08x", __func__,
+ GetLastError());
+ free(threads);
+ DeleteSynchronizationBarrier(&gBarrier);
+ return FALSE;
+ }
+
+ if (!(gStartEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ printf("%s: CreateEvent failed with error 0x%08x", __func__, GetLastError());
+ free(threads);
+ DeleteSynchronizationBarrier(&gBarrier);
+ return FALSE;
+ }
+
+ DWORD i = 0;
+ for (; i < dwThreads; i++)
+ {
+ if (!(threads[i] = CreateThread(NULL, 0, test_synch_barrier_thread, &p, 0, NULL)))
+ {
+ printf("%s: CreateThread failed for thread #%" PRIu32 " with error 0x%08x\n", __func__,
+ i, GetLastError());
+ InterlockedIncrement(&gErrorCount);
+ break;
+ }
+ }
+
+ if (i > 0)
+ {
+ if (!SetEvent(gStartEvent))
+ {
+ printf("%s: SetEvent(gStartEvent) failed with error = 0x%08x)\n", __func__,
+ GetLastError());
+ InterlockedIncrement(&gErrorCount);
+ }
+
+ while (i--)
+ {
+ if (WAIT_OBJECT_0 != (dwStatus = WaitForSingleObject(threads[i], INFINITE)))
+ {
+ printf("%s: WaitForSingleObject(thread[%" PRIu32 "] unexpectedly returned %" PRIu32
+ " (error = 0x%08x)\n",
+ __func__, i, dwStatus, GetLastError());
+ InterlockedIncrement(&gErrorCount);
+ }
+
+ if (!CloseHandle(threads[i]))
+ {
+ printf("%s: CloseHandle(thread[%" PRIu32 "]) failed with error = 0x%08x)\n",
+ __func__, i, GetLastError());
+ InterlockedIncrement(&gErrorCount);
+ }
+ }
+ }
+
+ free(threads);
+
+ if (!CloseHandle(gStartEvent))
+ {
+ printf("%s: CloseHandle(gStartEvent) failed with error = 0x%08x)\n", __func__,
+ GetLastError());
+ InterlockedIncrement(&gErrorCount);
+ }
+
+ DeleteSynchronizationBarrier(&gBarrier);
+
+ if (p.threadCount != (INT64)dwThreads)
+ InterlockedIncrement(&gErrorCount);
+
+ if (p.trueCount != (INT64)expectedTrueCount)
+ InterlockedIncrement(&gErrorCount);
+
+ if (p.falseCount != (INT64)expectedFalseCount)
+ InterlockedIncrement(&gErrorCount);
+
+ printf("%s: error count: %" PRId32 "\n", __func__, gErrorCount);
+ printf("%s: thread count: %" PRId32 " (expected %" PRIu32 ")\n", __func__, p.threadCount,
+ dwThreads);
+ printf("%s: true count: %" PRId32 " (expected %" PRIu32 ")\n", __func__, p.trueCount,
+ expectedTrueCount);
+ printf("%s: false count: %" PRId32 " (expected %" PRIu32 ")\n", __func__, p.falseCount,
+ expectedFalseCount);
+
+ if (gErrorCount > 0)
+ {
+ printf("%s: Error test failed with %" PRId32 " reported errors\n", __func__, gErrorCount);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int TestSynchBarrier(int argc, char* argv[])
+{
+ SYSTEM_INFO sysinfo;
+ DWORD dwMaxThreads = 0;
+ DWORD dwMinThreads = 0;
+ DWORD dwNumLoops = 10;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ GetNativeSystemInfo(&sysinfo);
+ printf("%s: Number of processors: %" PRIu32 "\n", __func__, sysinfo.dwNumberOfProcessors);
+ dwMinThreads = sysinfo.dwNumberOfProcessors;
+ dwMaxThreads = sysinfo.dwNumberOfProcessors * 4;
+
+ if (dwMaxThreads > 32)
+ dwMaxThreads = 32;
+
+ /* Test invalid parameters */
+ if (InitializeSynchronizationBarrier(&gBarrier, 0, -1))
+ {
+ fprintf(
+ stderr,
+ "%s: InitializeSynchronizationBarrier unecpectedly succeeded with lTotalThreads = 0\n",
+ __func__);
+ return -1;
+ }
+
+ if (InitializeSynchronizationBarrier(&gBarrier, -1, -1))
+ {
+ fprintf(
+ stderr,
+ "%s: InitializeSynchronizationBarrier unecpectedly succeeded with lTotalThreads = -1\n",
+ __func__);
+ return -1;
+ }
+
+ if (InitializeSynchronizationBarrier(&gBarrier, 1, -2))
+ {
+ fprintf(
+ stderr,
+ "%s: InitializeSynchronizationBarrier unecpectedly succeeded with lSpinCount = -2\n",
+ __func__);
+ return -1;
+ }
+
+ /* Functional tests */
+
+ if (!TestSynchBarrierWithFlags(0, dwMaxThreads, dwNumLoops))
+ {
+ fprintf(stderr,
+ "%s: TestSynchBarrierWithFlags(0) unecpectedly succeeded with lTotalThreads = -1\n",
+ __func__);
+ return -1;
+ }
+
+ if (!TestSynchBarrierWithFlags(SYNCHRONIZATION_BARRIER_FLAGS_SPIN_ONLY, dwMinThreads,
+ dwNumLoops))
+ {
+ fprintf(stderr,
+ "%s: TestSynchBarrierWithFlags(SYNCHRONIZATION_BARRIER_FLAGS_SPIN_ONLY) "
+ "unecpectedly succeeded with lTotalThreads = -1\n",
+ __func__);
+ return -1;
+ }
+
+ if (!TestSynchBarrierWithFlags(SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY, dwMaxThreads,
+ dwNumLoops))
+ {
+ fprintf(stderr,
+ "%s: TestSynchBarrierWithFlags(SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY) "
+ "unecpectedly succeeded with lTotalThreads = -1\n",
+ __func__);
+ return -1;
+ }
+
+ printf("%s: Test successfully completed\n", __func__);
+ return 0;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchCritical.c b/winpr/libwinpr/synch/test/TestSynchCritical.c
new file mode 100644
index 0000000..9d56356
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchCritical.c
@@ -0,0 +1,363 @@
+
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/windows.h>
+#include <winpr/synch.h>
+#include <winpr/sysinfo.h>
+#include <winpr/thread.h>
+#include <winpr/interlocked.h>
+
+#define TEST_SYNC_CRITICAL_TEST1_RUNTIME_MS 50
+#define TEST_SYNC_CRITICAL_TEST1_RUNS 4
+
+static CRITICAL_SECTION critical;
+static LONG gTestValueVulnerable = 0;
+static LONG gTestValueSerialized = 0;
+
+static BOOL TestSynchCritical_TriggerAndCheckRaceCondition(HANDLE OwningThread, LONG RecursionCount)
+{
+ /* if called unprotected this will hopefully trigger a race condition ... */
+ gTestValueVulnerable++;
+
+ if (critical.OwningThread != OwningThread)
+ {
+ printf("CriticalSection failure: OwningThread is invalid\n");
+ return FALSE;
+ }
+ if (critical.RecursionCount != RecursionCount)
+ {
+ printf("CriticalSection failure: RecursionCount is invalid\n");
+ return FALSE;
+ }
+
+ /* ... which we try to detect using the serialized counter */
+ if (gTestValueVulnerable != InterlockedIncrement(&gTestValueSerialized))
+ {
+ printf("CriticalSection failure: Data corruption detected\n");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* this thread function shall increment the global dwTestValue until the PBOOL passsed in arg is
+ * FALSE */
+static DWORD WINAPI TestSynchCritical_Test1(LPVOID arg)
+{
+ int rc = 0;
+ HANDLE hThread = (HANDLE)(ULONG_PTR)GetCurrentThreadId();
+
+ PBOOL pbContinueRunning = (PBOOL)arg;
+
+ while (*pbContinueRunning)
+ {
+ EnterCriticalSection(&critical);
+
+ rc = 1;
+
+ if (!TestSynchCritical_TriggerAndCheckRaceCondition(hThread, rc))
+ return 1;
+
+ /* add some random recursion level */
+ int j = rand() % 5;
+ for (int i = 0; i < j; i++)
+ {
+ if (!TestSynchCritical_TriggerAndCheckRaceCondition(hThread, rc++))
+ return 2;
+ EnterCriticalSection(&critical);
+ }
+ for (int i = 0; i < j; i++)
+ {
+ if (!TestSynchCritical_TriggerAndCheckRaceCondition(hThread, rc--))
+ return 2;
+ LeaveCriticalSection(&critical);
+ }
+
+ if (!TestSynchCritical_TriggerAndCheckRaceCondition(hThread, rc))
+ return 3;
+
+ LeaveCriticalSection(&critical);
+ }
+
+ return 0;
+}
+
+/* this thread function tries to call TryEnterCriticalSection while the main thread holds the lock
+ */
+static DWORD WINAPI TestSynchCritical_Test2(LPVOID arg)
+{
+ WINPR_UNUSED(arg);
+ if (TryEnterCriticalSection(&critical) == TRUE)
+ {
+ LeaveCriticalSection(&critical);
+ return 1;
+ }
+ return 0;
+}
+
+static DWORD WINAPI TestSynchCritical_Main(LPVOID arg)
+{
+ SYSTEM_INFO sysinfo;
+ DWORD dwPreviousSpinCount = 0;
+ DWORD dwSpinCount = 0;
+ DWORD dwSpinCountExpected = 0;
+ HANDLE hMainThread = NULL;
+ HANDLE* hThreads = NULL;
+ HANDLE hThread = NULL;
+ DWORD dwThreadCount = 0;
+ DWORD dwThreadExitCode = 0;
+ BOOL bTest1Running = 0;
+
+ PBOOL pbThreadTerminated = (PBOOL)arg;
+
+ GetNativeSystemInfo(&sysinfo);
+
+ hMainThread = (HANDLE)(ULONG_PTR)GetCurrentThreadId();
+
+ /**
+ * Test SpinCount in SetCriticalSectionSpinCount, InitializeCriticalSectionEx and
+ * InitializeCriticalSectionAndSpinCount SpinCount must be forced to be zero on on uniprocessor
+ * systems and on systems where WINPR_CRITICAL_SECTION_DISABLE_SPINCOUNT is defined
+ */
+
+ dwSpinCount = 100;
+ InitializeCriticalSectionEx(&critical, dwSpinCount, 0);
+ while (--dwSpinCount)
+ {
+ dwPreviousSpinCount = SetCriticalSectionSpinCount(&critical, dwSpinCount);
+ dwSpinCountExpected = 0;
+#if !defined(WINPR_CRITICAL_SECTION_DISABLE_SPINCOUNT)
+ if (sysinfo.dwNumberOfProcessors > 1)
+ dwSpinCountExpected = dwSpinCount + 1;
+#endif
+ if (dwPreviousSpinCount != dwSpinCountExpected)
+ {
+ printf("CriticalSection failure: SetCriticalSectionSpinCount returned %" PRIu32
+ " (expected: %" PRIu32 ")\n",
+ dwPreviousSpinCount, dwSpinCountExpected);
+ goto fail;
+ }
+
+ DeleteCriticalSection(&critical);
+
+ if (dwSpinCount % 2 == 0)
+ InitializeCriticalSectionAndSpinCount(&critical, dwSpinCount);
+ else
+ InitializeCriticalSectionEx(&critical, dwSpinCount, 0);
+ }
+ DeleteCriticalSection(&critical);
+
+ /**
+ * Test single-threaded recursive
+ * TryEnterCriticalSection/EnterCriticalSection/LeaveCriticalSection
+ *
+ */
+
+ InitializeCriticalSection(&critical);
+
+ int i = 0;
+ for (; i < 10; i++)
+ {
+ if (critical.RecursionCount != i)
+ {
+ printf("CriticalSection failure: RecursionCount field is %" PRId32 " instead of %d.\n",
+ critical.RecursionCount, i);
+ goto fail;
+ }
+ if (i % 2 == 0)
+ {
+ EnterCriticalSection(&critical);
+ }
+ else
+ {
+ if (TryEnterCriticalSection(&critical) == FALSE)
+ {
+ printf("CriticalSection failure: TryEnterCriticalSection failed where it should "
+ "not.\n");
+ goto fail;
+ }
+ }
+ if (critical.OwningThread != hMainThread)
+ {
+ printf("CriticalSection failure: Could not verify section ownership (loop index=%d).\n",
+ i);
+ goto fail;
+ }
+ }
+ while (--i >= 0)
+ {
+ LeaveCriticalSection(&critical);
+ if (critical.RecursionCount != i)
+ {
+ printf("CriticalSection failure: RecursionCount field is %" PRId32 " instead of %d.\n",
+ critical.RecursionCount, i);
+ goto fail;
+ }
+ if (critical.OwningThread != (HANDLE)(i ? hMainThread : NULL))
+ {
+ printf("CriticalSection failure: Could not verify section ownership (loop index=%d).\n",
+ i);
+ goto fail;
+ }
+ }
+ DeleteCriticalSection(&critical);
+
+ /**
+ * Test using multiple threads modifying the same value
+ */
+
+ dwThreadCount = sysinfo.dwNumberOfProcessors > 1 ? sysinfo.dwNumberOfProcessors : 2;
+
+ hThreads = (HANDLE*)calloc(dwThreadCount, sizeof(HANDLE));
+ if (!hThreads)
+ {
+ printf("Problem allocating memory\n");
+ goto fail;
+ }
+
+ for (int j = 0; j < TEST_SYNC_CRITICAL_TEST1_RUNS; j++)
+ {
+ dwSpinCount = j * 100;
+ InitializeCriticalSectionAndSpinCount(&critical, dwSpinCount);
+
+ gTestValueVulnerable = 0;
+ gTestValueSerialized = 0;
+
+ /* the TestSynchCritical_Test1 threads shall run until bTest1Running is FALSE */
+ bTest1Running = TRUE;
+ for (int i = 0; i < (int)dwThreadCount; i++)
+ {
+ if (!(hThreads[i] =
+ CreateThread(NULL, 0, TestSynchCritical_Test1, &bTest1Running, 0, NULL)))
+ {
+ printf("CriticalSection failure: Failed to create test_1 thread #%d\n", i);
+ goto fail;
+ }
+ }
+
+ /* let it run for TEST_SYNC_CRITICAL_TEST1_RUNTIME_MS ... */
+ Sleep(TEST_SYNC_CRITICAL_TEST1_RUNTIME_MS);
+ bTest1Running = FALSE;
+
+ for (int i = 0; i < (int)dwThreadCount; i++)
+ {
+ if (WaitForSingleObject(hThreads[i], INFINITE) != WAIT_OBJECT_0)
+ {
+ printf("CriticalSection failure: Failed to wait for thread #%d\n", i);
+ goto fail;
+ }
+ GetExitCodeThread(hThreads[i], &dwThreadExitCode);
+ if (dwThreadExitCode != 0)
+ {
+ printf("CriticalSection failure: Thread #%d returned error code %" PRIu32 "\n", i,
+ dwThreadExitCode);
+ goto fail;
+ }
+ CloseHandle(hThreads[i]);
+ }
+
+ if (gTestValueVulnerable != gTestValueSerialized)
+ {
+ printf("CriticalSection failure: unexpected test value %" PRId32 " (expected %" PRId32
+ ")\n",
+ gTestValueVulnerable, gTestValueSerialized);
+ goto fail;
+ }
+
+ DeleteCriticalSection(&critical);
+ }
+
+ free(hThreads);
+
+ /**
+ * TryEnterCriticalSection in thread must fail if we hold the lock in the main thread
+ */
+
+ InitializeCriticalSection(&critical);
+
+ if (TryEnterCriticalSection(&critical) == FALSE)
+ {
+ printf("CriticalSection failure: TryEnterCriticalSection unexpectedly failed.\n");
+ goto fail;
+ }
+ /* This thread tries to call TryEnterCriticalSection which must fail */
+ if (!(hThread = CreateThread(NULL, 0, TestSynchCritical_Test2, NULL, 0, NULL)))
+ {
+ printf("CriticalSection failure: Failed to create test_2 thread\n");
+ goto fail;
+ }
+ if (WaitForSingleObject(hThread, INFINITE) != WAIT_OBJECT_0)
+ {
+ printf("CriticalSection failure: Failed to wait for thread\n");
+ goto fail;
+ }
+ GetExitCodeThread(hThread, &dwThreadExitCode);
+ if (dwThreadExitCode != 0)
+ {
+ printf("CriticalSection failure: Thread returned error code %" PRIu32 "\n",
+ dwThreadExitCode);
+ goto fail;
+ }
+ CloseHandle(hThread);
+
+ *pbThreadTerminated = TRUE; /* requ. for winpr issue, see below */
+ return 0;
+
+fail:
+ *pbThreadTerminated = TRUE; /* requ. for winpr issue, see below */
+ return 1;
+}
+
+int TestSynchCritical(int argc, char* argv[])
+{
+ BOOL bThreadTerminated = FALSE;
+ HANDLE hThread = NULL;
+ DWORD dwThreadExitCode = 0;
+ DWORD dwDeadLockDetectionTimeMs = 0;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ dwDeadLockDetectionTimeMs =
+ 2 * TEST_SYNC_CRITICAL_TEST1_RUNTIME_MS * TEST_SYNC_CRITICAL_TEST1_RUNS;
+
+ printf("Deadlock will be assumed after %" PRIu32 " ms.\n", dwDeadLockDetectionTimeMs);
+
+ if (!(hThread = CreateThread(NULL, 0, TestSynchCritical_Main, &bThreadTerminated, 0, NULL)))
+ {
+ printf("CriticalSection failure: Failed to create main thread\n");
+ return -1;
+ }
+
+ /**
+ * We have to be able to detect dead locks in this test.
+ * At the time of writing winpr's WaitForSingleObject has not implemented timeout for thread
+ * wait
+ *
+ * Workaround checking the value of bThreadTerminated which is passed in the thread arg
+ */
+
+ for (DWORD i = 0; i < dwDeadLockDetectionTimeMs; i += 10)
+ {
+ if (bThreadTerminated)
+ break;
+
+ Sleep(10);
+ }
+
+ if (!bThreadTerminated)
+ {
+ printf("CriticalSection failure: Possible dead lock detected\n");
+ return -1;
+ }
+
+ GetExitCodeThread(hThread, &dwThreadExitCode);
+ CloseHandle(hThread);
+
+ if (dwThreadExitCode != 0)
+ {
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchEvent.c b/winpr/libwinpr/synch/test/TestSynchEvent.c
new file mode 100644
index 0000000..083282c
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchEvent.c
@@ -0,0 +1,94 @@
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+int TestSynchEvent(int argc, char* argv[])
+{
+ HANDLE event = NULL;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ if (ResetEvent(NULL))
+ {
+ printf("ResetEvent(NULL) unexpectedly succeeded\n");
+ return -1;
+ }
+
+ if (SetEvent(NULL))
+ {
+ printf("SetEvent(NULL) unexpectedly succeeded\n");
+ return -1;
+ }
+
+ event = CreateEvent(NULL, TRUE, TRUE, NULL);
+
+ if (!event)
+ {
+ printf("CreateEvent failure\n");
+ return -1;
+ }
+
+ if (WaitForSingleObject(event, INFINITE) != WAIT_OBJECT_0)
+ {
+ printf("WaitForSingleObject failure 1\n");
+ return -1;
+ }
+
+ if (!ResetEvent(event))
+ {
+ printf("ResetEvent failure with signaled event object\n");
+ return -1;
+ }
+
+ if (WaitForSingleObject(event, 0) != WAIT_TIMEOUT)
+ {
+ printf("WaitForSingleObject failure 2\n");
+ return -1;
+ }
+
+ if (!ResetEvent(event))
+ {
+ /* Note: ResetEvent must also succeed if event is currently nonsignaled */
+ printf("ResetEvent failure with nonsignaled event object\n");
+ return -1;
+ }
+
+ if (!SetEvent(event))
+ {
+ printf("SetEvent failure with nonsignaled event object\n");
+ return -1;
+ }
+
+ if (WaitForSingleObject(event, 0) != WAIT_OBJECT_0)
+ {
+ printf("WaitForSingleObject failure 3\n");
+ return -1;
+ }
+
+ for (int i = 0; i < 10000; i++)
+ {
+ if (!SetEvent(event))
+ {
+ printf("SetEvent failure with signaled event object (i = %d)\n", i);
+ return -1;
+ }
+ }
+
+ if (!ResetEvent(event))
+ {
+ printf("ResetEvent failure after multiple SetEvent calls\n");
+ return -1;
+ }
+
+ /* Independent of the amount of the previous SetEvent calls, a single
+ ResetEvent must be sufficient to get into nonsignaled state */
+
+ if (WaitForSingleObject(event, 0) != WAIT_TIMEOUT)
+ {
+ printf("WaitForSingleObject failure 4\n");
+ return -1;
+ }
+
+ CloseHandle(event);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchInit.c b/winpr/libwinpr/synch/test/TestSynchInit.c
new file mode 100644
index 0000000..20da415
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchInit.c
@@ -0,0 +1,156 @@
+#include <stdio.h>
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/interlocked.h>
+
+#define TEST_NUM_THREADS 100
+#define TEST_NUM_FAILURES 10
+
+static INIT_ONCE initOnceTest = INIT_ONCE_STATIC_INIT;
+
+static HANDLE hStartEvent = NULL;
+static LONG* pErrors = NULL;
+static LONG* pTestThreadFunctionCalls = NULL;
+static LONG* pTestOnceFunctionCalls = NULL;
+static LONG* pInitOnceExecuteOnceCalls = NULL;
+
+static BOOL CALLBACK TestOnceFunction(PINIT_ONCE once, PVOID param, PVOID* context)
+{
+ LONG calls = InterlockedIncrement(pTestOnceFunctionCalls) - 1;
+
+ WINPR_UNUSED(once);
+ WINPR_UNUSED(param);
+ WINPR_UNUSED(context);
+
+ /* simulate execution time */
+ Sleep(30 + rand() % 40);
+
+ if (calls < TEST_NUM_FAILURES)
+ {
+ /* simulated error */
+ return FALSE;
+ }
+ if (calls == TEST_NUM_FAILURES)
+ {
+ return TRUE;
+ }
+ fprintf(stderr, "%s: error: called again after success\n", __func__);
+ InterlockedIncrement(pErrors);
+ return FALSE;
+}
+
+static DWORD WINAPI TestThreadFunction(LPVOID lpParam)
+{
+ LONG calls = 0;
+ BOOL ok = 0;
+
+ WINPR_UNUSED(lpParam);
+
+ InterlockedIncrement(pTestThreadFunctionCalls);
+ if (WaitForSingleObject(hStartEvent, INFINITE) != WAIT_OBJECT_0)
+ {
+ fprintf(stderr, "%s: error: failed to wait for start event\n", __func__);
+ InterlockedIncrement(pErrors);
+ return 0;
+ }
+
+ ok = InitOnceExecuteOnce(&initOnceTest, TestOnceFunction, NULL, NULL);
+ calls = InterlockedIncrement(pInitOnceExecuteOnceCalls);
+ if (!ok && calls > TEST_NUM_FAILURES)
+ {
+ fprintf(stderr, "%s: InitOnceExecuteOnce failed unexpectedly\n", __func__);
+ InterlockedIncrement(pErrors);
+ }
+ return 0;
+}
+
+int TestSynchInit(int argc, char* argv[])
+{
+ HANDLE hThreads[TEST_NUM_THREADS];
+ DWORD dwCreatedThreads = 0;
+ BOOL result = FALSE;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ pErrors = winpr_aligned_malloc(sizeof(LONG), sizeof(LONG));
+ pTestThreadFunctionCalls = winpr_aligned_malloc(sizeof(LONG), sizeof(LONG));
+ pTestOnceFunctionCalls = winpr_aligned_malloc(sizeof(LONG), sizeof(LONG));
+ pInitOnceExecuteOnceCalls = winpr_aligned_malloc(sizeof(LONG), sizeof(LONG));
+
+ if (!pErrors || !pTestThreadFunctionCalls || !pTestOnceFunctionCalls ||
+ !pInitOnceExecuteOnceCalls)
+ {
+ fprintf(stderr, "error: _aligned_malloc failed\n");
+ goto out;
+ }
+
+ *pErrors = 0;
+ *pTestThreadFunctionCalls = 0;
+ *pTestOnceFunctionCalls = 0;
+ *pInitOnceExecuteOnceCalls = 0;
+
+ if (!(hStartEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ fprintf(stderr, "error creating start event\n");
+ InterlockedIncrement(pErrors);
+ goto out;
+ }
+
+ for (DWORD i = 0; i < TEST_NUM_THREADS; i++)
+ {
+ if (!(hThreads[i] = CreateThread(NULL, 0, TestThreadFunction, NULL, 0, NULL)))
+ {
+ fprintf(stderr, "error creating thread #%" PRIu32 "\n", i);
+ InterlockedIncrement(pErrors);
+ goto out;
+ }
+ dwCreatedThreads++;
+ }
+
+ Sleep(100);
+ SetEvent(hStartEvent);
+
+ for (DWORD i = 0; i < dwCreatedThreads; i++)
+ {
+ if (WaitForSingleObject(hThreads[i], INFINITE) != WAIT_OBJECT_0)
+ {
+ fprintf(stderr, "error: error waiting for thread #%" PRIu32 "\n", i);
+ InterlockedIncrement(pErrors);
+ goto out;
+ }
+ }
+
+ if (*pErrors == 0 && *pTestThreadFunctionCalls == TEST_NUM_THREADS &&
+ *pInitOnceExecuteOnceCalls == TEST_NUM_THREADS &&
+ *pTestOnceFunctionCalls == TEST_NUM_FAILURES + 1)
+ {
+ result = TRUE;
+ }
+
+out:
+ fprintf(stderr, "Test result: %s\n", result ? "OK" : "ERROR");
+ fprintf(stderr, "Error count: %" PRId32 "\n", pErrors ? *pErrors : -1);
+ fprintf(stderr, "Threads created: %" PRIu32 "\n", dwCreatedThreads);
+ fprintf(stderr, "TestThreadFunctionCalls: %" PRId32 "\n",
+ pTestThreadFunctionCalls ? *pTestThreadFunctionCalls : -1);
+ fprintf(stderr, "InitOnceExecuteOnceCalls: %" PRId32 "\n",
+ pInitOnceExecuteOnceCalls ? *pInitOnceExecuteOnceCalls : -1);
+ fprintf(stderr, "TestOnceFunctionCalls: %" PRId32 "\n",
+ pTestOnceFunctionCalls ? *pTestOnceFunctionCalls : -1);
+
+ winpr_aligned_free(pErrors);
+ winpr_aligned_free(pTestThreadFunctionCalls);
+ winpr_aligned_free(pTestOnceFunctionCalls);
+ winpr_aligned_free(pInitOnceExecuteOnceCalls);
+
+ CloseHandle(hStartEvent);
+
+ for (DWORD i = 0; i < dwCreatedThreads; i++)
+ {
+ CloseHandle(hThreads[i]);
+ }
+
+ return (result ? 0 : 1);
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchMultipleThreads.c b/winpr/libwinpr/synch/test/TestSynchMultipleThreads.c
new file mode 100644
index 0000000..7218b64
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchMultipleThreads.c
@@ -0,0 +1,243 @@
+
+#include <stdlib.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+
+#define THREADS 8
+
+static DWORD WINAPI test_thread(LPVOID arg)
+{
+ long timeout = 50 + (rand() % 100);
+ WINPR_UNUSED(arg);
+ Sleep(timeout);
+ ExitThread(0);
+ return 0;
+}
+
+static int start_threads(size_t count, HANDLE* threads)
+{
+ for (size_t i = 0; i < count; i++)
+ {
+ threads[i] = CreateThread(NULL, 0, test_thread, NULL, CREATE_SUSPENDED, NULL);
+
+ if (!threads[i])
+ {
+ fprintf(stderr, "%s: CreateThread [%" PRIuz "] failure\n", __func__, i);
+ return -1;
+ }
+ }
+
+ for (size_t i = 0; i < count; i++)
+ ResumeThread(threads[i]);
+ return 0;
+}
+
+static int close_threads(DWORD count, HANDLE* threads)
+{
+ int rc = 0;
+
+ for (DWORD i = 0; i < count; i++)
+ {
+ if (!threads[i])
+ continue;
+
+ if (!CloseHandle(threads[i]))
+ {
+ fprintf(stderr, "%s: CloseHandle [%" PRIu32 "] failure\n", __func__, i);
+ rc = -1;
+ }
+ threads[i] = NULL;
+ }
+
+ return rc;
+}
+
+static BOOL TestWaitForAll(void)
+{
+ BOOL rc = FALSE;
+ HANDLE threads[THREADS] = { 0 };
+ /* WaitForAll, timeout */
+ if (start_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: start_threads failed\n", __func__);
+ goto fail;
+ }
+
+ const DWORD ret = WaitForMultipleObjects(ARRAYSIZE(threads), threads, TRUE, 10);
+ if (ret != WAIT_TIMEOUT)
+ {
+ fprintf(stderr, "%s: WaitForMultipleObjects bWaitAll, timeout 10 failed, ret=%d\n",
+ __func__, ret);
+ goto fail;
+ }
+
+ if (WaitForMultipleObjects(ARRAYSIZE(threads), threads, TRUE, INFINITE) != WAIT_OBJECT_0)
+ {
+ fprintf(stderr, "%s: WaitForMultipleObjects bWaitAll, INFINITE failed\n", __func__);
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ if (close_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: close_threads failed\n", __func__);
+ return FALSE;
+ }
+
+ return rc;
+}
+
+static BOOL TestWaitOne(void)
+{
+ BOOL rc = FALSE;
+ HANDLE threads[THREADS] = { 0 };
+ /* WaitForAll, timeout */
+ if (start_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: start_threads failed\n", __func__);
+ goto fail;
+ }
+
+ const DWORD ret = WaitForMultipleObjects(ARRAYSIZE(threads), threads, FALSE, INFINITE);
+ if (ret > (WAIT_OBJECT_0 + ARRAYSIZE(threads)))
+ {
+ fprintf(stderr, "%s: WaitForMultipleObjects INFINITE failed\n", __func__);
+ goto fail;
+ }
+
+ if (WaitForMultipleObjects(ARRAYSIZE(threads), threads, TRUE, INFINITE) != WAIT_OBJECT_0)
+ {
+ fprintf(stderr, "%s: WaitForMultipleObjects bWaitAll, INFINITE failed\n", __func__);
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ if (close_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: close_threads failed\n", __func__);
+ return FALSE;
+ }
+
+ return rc;
+}
+
+static BOOL TestWaitOneTimeout(void)
+{
+ BOOL rc = FALSE;
+ HANDLE threads[THREADS] = { 0 };
+ /* WaitForAll, timeout */
+ if (start_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: start_threads failed\n", __func__);
+ goto fail;
+ }
+
+ const DWORD ret = WaitForMultipleObjects(ARRAYSIZE(threads), threads, FALSE, 1);
+ if (ret != WAIT_TIMEOUT)
+ {
+ fprintf(stderr, "%s: WaitForMultipleObjects timeout 50 failed, ret=%d\n", __func__, ret);
+ goto fail;
+ }
+
+ if (WaitForMultipleObjects(ARRAYSIZE(threads), threads, TRUE, INFINITE) != WAIT_OBJECT_0)
+ {
+ fprintf(stderr, "%s: WaitForMultipleObjects bWaitAll, INFINITE failed\n", __func__);
+ goto fail;
+ }
+ rc = TRUE;
+fail:
+ if (close_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: close_threads failed\n", __func__);
+ return FALSE;
+ }
+
+ return rc;
+}
+
+static BOOL TestWaitOneTimeoutMultijoin(void)
+{
+ BOOL rc = FALSE;
+ HANDLE threads[THREADS] = { 0 };
+ /* WaitForAll, timeout */
+ if (start_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: start_threads failed\n", __func__);
+ goto fail;
+ }
+
+ for (size_t i = 0; i < ARRAYSIZE(threads); i++)
+ {
+ const DWORD ret = WaitForMultipleObjects(ARRAYSIZE(threads), threads, FALSE, 0);
+ if (ret != WAIT_TIMEOUT)
+ {
+ fprintf(stderr, "%s: WaitForMultipleObjects timeout 0 failed, ret=%d\n", __func__, ret);
+ goto fail;
+ }
+ }
+
+ if (WaitForMultipleObjects(ARRAYSIZE(threads), threads, TRUE, INFINITE) != WAIT_OBJECT_0)
+ {
+ fprintf(stderr, "%s: WaitForMultipleObjects bWaitAll, INFINITE failed\n", __func__);
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ if (close_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: close_threads failed\n", __func__);
+ return FALSE;
+ }
+
+ return rc;
+}
+
+static BOOL TestDetach(void)
+{
+ BOOL rc = FALSE;
+ HANDLE threads[THREADS] = { 0 };
+ /* WaitForAll, timeout */
+ if (start_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: start_threads failed\n", __func__);
+ goto fail;
+ }
+
+ rc = TRUE;
+fail:
+ if (close_threads(ARRAYSIZE(threads), threads))
+ {
+ fprintf(stderr, "%s: close_threads failed\n", __func__);
+ return FALSE;
+ }
+
+ return rc;
+}
+
+int TestSynchMultipleThreads(int argc, char* argv[])
+{
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!TestWaitForAll())
+ return -1;
+
+ if (!TestWaitOne())
+ return -2;
+
+ if (!TestWaitOneTimeout())
+ return -3;
+
+ if (!TestWaitOneTimeoutMultijoin())
+ return -4;
+
+ if (!TestDetach())
+ return -5;
+
+ return 0;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchMutex.c b/winpr/libwinpr/synch/test/TestSynchMutex.c
new file mode 100644
index 0000000..296c371
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchMutex.c
@@ -0,0 +1,258 @@
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+
+static BOOL test_mutex_basic(void)
+{
+ HANDLE mutex = NULL;
+ DWORD rc = 0;
+
+ if (!(mutex = CreateMutex(NULL, FALSE, NULL)))
+ {
+ printf("%s: CreateMutex failed\n", __func__);
+ return FALSE;
+ }
+
+ rc = WaitForSingleObject(mutex, INFINITE);
+
+ if (rc != WAIT_OBJECT_0)
+ {
+ printf("%s: WaitForSingleObject on mutex failed with %" PRIu32 "\n", __func__, rc);
+ return FALSE;
+ }
+
+ if (!ReleaseMutex(mutex))
+ {
+ printf("%s: ReleaseMutex failed\n", __func__);
+ return FALSE;
+ }
+
+ if (ReleaseMutex(mutex))
+ {
+ printf("%s: ReleaseMutex unexpectedly succeeded on released mutex\n", __func__);
+ return FALSE;
+ }
+
+ if (!CloseHandle(mutex))
+ {
+ printf("%s: CloseHandle on mutex failed\n", __func__);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL test_mutex_recursive(void)
+{
+ HANDLE mutex = NULL;
+ DWORD rc = 0;
+ DWORD cnt = 50;
+
+ if (!(mutex = CreateMutex(NULL, TRUE, NULL)))
+ {
+ printf("%s: CreateMutex failed\n", __func__);
+ return FALSE;
+ }
+
+ for (UINT32 i = 0; i < cnt; i++)
+ {
+ rc = WaitForSingleObject(mutex, INFINITE);
+
+ if (rc != WAIT_OBJECT_0)
+ {
+ printf("%s: WaitForSingleObject #%" PRIu32 " on mutex failed with %" PRIu32 "\n",
+ __func__, i, rc);
+ return FALSE;
+ }
+ }
+
+ for (UINT32 i = 0; i < cnt; i++)
+ {
+ if (!ReleaseMutex(mutex))
+ {
+ printf("%s: ReleaseMutex #%" PRIu32 " failed\n", __func__, i);
+ return FALSE;
+ }
+ }
+
+ if (!ReleaseMutex(mutex))
+ {
+ /* Note: The mutex was initially owned ! */
+ printf("%s: Final ReleaseMutex failed\n", __func__);
+ return FALSE;
+ }
+
+ if (ReleaseMutex(mutex))
+ {
+ printf("%s: ReleaseMutex unexpectedly succeeded on released mutex\n", __func__);
+ return FALSE;
+ }
+
+ if (!CloseHandle(mutex))
+ {
+ printf("%s: CloseHandle on mutex failed\n", __func__);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static HANDLE thread1_mutex1 = NULL;
+static HANDLE thread1_mutex2 = NULL;
+static BOOL thread1_failed = TRUE;
+
+static DWORD WINAPI test_mutex_thread1(LPVOID lpParam)
+{
+ HANDLE hStartEvent = (HANDLE)lpParam;
+ DWORD rc = 0;
+
+ if (WaitForSingleObject(hStartEvent, INFINITE) != WAIT_OBJECT_0)
+ {
+ fprintf(stderr, "%s: failed to wait for start event\n", __func__);
+ return 0;
+ }
+
+ /**
+ * at this point:
+ * thread1_mutex1 is expected to be locked
+ * thread1_mutex2 is expected to be unlocked
+ * defined task:
+ * try to lock thread1_mutex1 (expected to fail)
+ * lock and unlock thread1_mutex2 (expected to work)
+ */
+ rc = WaitForSingleObject(thread1_mutex1, 10);
+
+ if (rc != WAIT_TIMEOUT)
+ {
+ fprintf(stderr,
+ "%s: WaitForSingleObject on thread1_mutex1 unexpectedly returned %" PRIu32
+ " instead of WAIT_TIMEOUT (%u)\n",
+ __func__, rc, WAIT_TIMEOUT);
+ return 0;
+ }
+
+ rc = WaitForSingleObject(thread1_mutex2, 10);
+
+ if (rc != WAIT_OBJECT_0)
+ {
+ fprintf(stderr,
+ "%s: WaitForSingleObject on thread1_mutex2 unexpectedly returned %" PRIu32
+ " instead of WAIT_OBJECT_0\n",
+ __func__, rc);
+ return 0;
+ }
+
+ if (!ReleaseMutex(thread1_mutex2))
+ {
+ fprintf(stderr, "%s: ReleaseMutex failed on thread1_mutex2\n", __func__);
+ return 0;
+ }
+
+ thread1_failed = FALSE;
+ return 0;
+}
+
+static BOOL test_mutex_threading(void)
+{
+ HANDLE hThread = NULL;
+ HANDLE hStartEvent = NULL;
+
+ if (!(thread1_mutex1 = CreateMutex(NULL, TRUE, NULL)))
+ {
+ printf("%s: CreateMutex thread1_mutex1 failed\n", __func__);
+ goto fail;
+ }
+
+ if (!(thread1_mutex2 = CreateMutex(NULL, FALSE, NULL)))
+ {
+ printf("%s: CreateMutex thread1_mutex2 failed\n", __func__);
+ goto fail;
+ }
+
+ if (!(hStartEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ fprintf(stderr, "%s: error creating start event\n", __func__);
+ goto fail;
+ }
+
+ thread1_failed = TRUE;
+
+ if (!(hThread = CreateThread(NULL, 0, test_mutex_thread1, (LPVOID)hStartEvent, 0, NULL)))
+ {
+ fprintf(stderr, "%s: error creating test_mutex_thread_1\n", __func__);
+ goto fail;
+ }
+
+ Sleep(100);
+
+ if (!thread1_failed)
+ {
+ fprintf(stderr, "%s: thread1 premature success\n", __func__);
+ goto fail;
+ }
+
+ SetEvent(hStartEvent);
+
+ if (WaitForSingleObject(hThread, 2000) != WAIT_OBJECT_0)
+ {
+ fprintf(stderr, "%s: thread1 premature success\n", __func__);
+ goto fail;
+ }
+
+ if (thread1_failed)
+ {
+ fprintf(stderr, "%s: thread1 has not reported success\n", __func__);
+ goto fail;
+ }
+
+ /**
+ * - thread1 must not have succeeded to lock thread1_mutex1
+ * - thread1 must have locked and unlocked thread1_mutex2
+ */
+
+ if (!ReleaseMutex(thread1_mutex1))
+ {
+ printf("%s: ReleaseMutex unexpectedly failed on thread1_mutex1\n", __func__);
+ goto fail;
+ }
+
+ if (ReleaseMutex(thread1_mutex2))
+ {
+ printf("%s: ReleaseMutex unexpectedly succeeded on thread1_mutex2\n", __func__);
+ goto fail;
+ }
+
+ CloseHandle(hThread);
+ CloseHandle(hStartEvent);
+ CloseHandle(thread1_mutex1);
+ CloseHandle(thread1_mutex2);
+ return TRUE;
+fail:
+ ReleaseMutex(thread1_mutex1);
+ ReleaseMutex(thread1_mutex2);
+ CloseHandle(thread1_mutex1);
+ CloseHandle(thread1_mutex2);
+ CloseHandle(hStartEvent);
+ CloseHandle(hThread);
+ return FALSE;
+}
+
+int TestSynchMutex(int argc, char* argv[])
+{
+ int rc = 0;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ if (!test_mutex_basic())
+ rc += 1;
+
+ if (!test_mutex_recursive())
+ rc += 2;
+
+ if (!test_mutex_threading())
+ rc += 4;
+
+ printf("TestSynchMutex result %d\n", rc);
+ return rc;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchSemaphore.c b/winpr/libwinpr/synch/test/TestSynchSemaphore.c
new file mode 100644
index 0000000..d44446a
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchSemaphore.c
@@ -0,0 +1,21 @@
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+int TestSynchSemaphore(int argc, char* argv[])
+{
+ HANDLE semaphore = NULL;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ semaphore = CreateSemaphore(NULL, 0, 1, NULL);
+
+ if (!semaphore)
+ {
+ printf("CreateSemaphore failure\n");
+ return -1;
+ }
+
+ CloseHandle(semaphore);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchThread.c b/winpr/libwinpr/synch/test/TestSynchThread.c
new file mode 100644
index 0000000..58f7cb0
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchThread.c
@@ -0,0 +1,131 @@
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+
+static DWORD WINAPI test_thread(LPVOID arg)
+{
+ WINPR_UNUSED(arg);
+ Sleep(100);
+ ExitThread(0);
+ return 0;
+}
+
+int TestSynchThread(int argc, char* argv[])
+{
+ DWORD rc = 0;
+ HANDLE thread = NULL;
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ thread = CreateThread(NULL, 0, test_thread, NULL, 0, NULL);
+
+ if (!thread)
+ {
+ printf("CreateThread failure\n");
+ return -1;
+ }
+
+ /* TryJoin should now fail. */
+ rc = WaitForSingleObject(thread, 0);
+
+ if (WAIT_TIMEOUT != rc)
+ {
+ printf("Timed WaitForSingleObject on running thread failed with %" PRIu32 "\n", rc);
+ return -3;
+ }
+
+ /* Join the thread */
+ rc = WaitForSingleObject(thread, INFINITE);
+
+ if (WAIT_OBJECT_0 != rc)
+ {
+ printf("WaitForSingleObject on thread failed with %" PRIu32 "\n", rc);
+ return -2;
+ }
+
+ /* TimedJoin should now succeed. */
+ rc = WaitForSingleObject(thread, 0);
+
+ if (WAIT_OBJECT_0 != rc)
+ {
+ printf("Timed WaitForSingleObject on dead thread failed with %" PRIu32 "\n", rc);
+ return -5;
+ }
+
+ /* check that WaitForSingleObject works multiple times on a terminated thread */
+ for (int i = 0; i < 4; i++)
+ {
+ rc = WaitForSingleObject(thread, 0);
+ if (WAIT_OBJECT_0 != rc)
+ {
+ printf("Timed WaitForSingleObject on dead thread failed with %" PRIu32 "\n", rc);
+ return -6;
+ }
+ }
+
+ if (!CloseHandle(thread))
+ {
+ printf("CloseHandle failed!");
+ return -1;
+ }
+
+ thread = CreateThread(NULL, 0, test_thread, NULL, 0, NULL);
+
+ if (!thread)
+ {
+ printf("CreateThread failure\n");
+ return -1;
+ }
+
+ /* TryJoin should now fail. */
+ rc = WaitForSingleObject(thread, 10);
+
+ if (WAIT_TIMEOUT != rc)
+ {
+ printf("Timed WaitForSingleObject on running thread failed with %" PRIu32 "\n", rc);
+ return -3;
+ }
+
+ /* Join the thread */
+ rc = WaitForSingleObject(thread, INFINITE);
+
+ if (WAIT_OBJECT_0 != rc)
+ {
+ printf("WaitForSingleObject on thread failed with %" PRIu32 "\n", rc);
+ return -2;
+ }
+
+ /* TimedJoin should now succeed. */
+ rc = WaitForSingleObject(thread, 0);
+
+ if (WAIT_OBJECT_0 != rc)
+ {
+ printf("Timed WaitForSingleObject on dead thread failed with %" PRIu32 "\n", rc);
+ return -5;
+ }
+
+ if (!CloseHandle(thread))
+ {
+ printf("CloseHandle failed!");
+ return -1;
+ }
+
+ /* Thread detach test */
+ thread = CreateThread(NULL, 0, test_thread, NULL, 0, NULL);
+
+ if (!thread)
+ {
+ printf("CreateThread failure\n");
+ return -1;
+ }
+
+ if (!CloseHandle(thread))
+ {
+ printf("CloseHandle failed!");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchTimerQueue.c b/winpr/libwinpr/synch/test/TestSynchTimerQueue.c
new file mode 100644
index 0000000..08cc957
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchTimerQueue.c
@@ -0,0 +1,125 @@
+
+#include <winpr/crt.h>
+#include <winpr/sysinfo.h>
+#include <winpr/file.h>
+#include <winpr/synch.h>
+
+#define FIRE_COUNT 5
+#define TIMER_COUNT 5
+
+struct apc_data
+{
+ DWORD TimerId;
+ DWORD FireCount;
+ DWORD DueTime;
+ DWORD Period;
+ UINT32 StartTime;
+ DWORD MaxFireCount;
+ HANDLE CompletionEvent;
+};
+typedef struct apc_data APC_DATA;
+
+static VOID CALLBACK TimerRoutine(PVOID lpParam, BOOLEAN TimerOrWaitFired)
+{
+ UINT32 TimerTime = 0;
+ APC_DATA* apcData = NULL;
+ UINT32 expectedTime = 0;
+ UINT32 CurrentTime = GetTickCount();
+
+ WINPR_UNUSED(TimerOrWaitFired);
+
+ if (!lpParam)
+ return;
+
+ apcData = (APC_DATA*)lpParam;
+
+ TimerTime = CurrentTime - apcData->StartTime;
+ expectedTime = apcData->DueTime + (apcData->Period * apcData->FireCount);
+
+ apcData->FireCount++;
+
+ printf("TimerRoutine: TimerId: %" PRIu32 " FireCount: %" PRIu32 " ActualTime: %" PRIu32
+ " ExpectedTime: %" PRIu32 " Discrepancy: %" PRIu32 "\n",
+ apcData->TimerId, apcData->FireCount, TimerTime, expectedTime, TimerTime - expectedTime);
+
+ Sleep(11);
+
+ if (apcData->FireCount == apcData->MaxFireCount)
+ {
+ SetEvent(apcData->CompletionEvent);
+ }
+}
+
+int TestSynchTimerQueue(int argc, char* argv[])
+{
+ HANDLE hTimerQueue = NULL;
+ HANDLE hTimers[TIMER_COUNT];
+ APC_DATA apcData[TIMER_COUNT];
+
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+
+ hTimerQueue = CreateTimerQueue();
+
+ if (!hTimerQueue)
+ {
+ printf("CreateTimerQueue failed (%" PRIu32 ")\n", GetLastError());
+ return -1;
+ }
+
+ for (DWORD index = 0; index < TIMER_COUNT; index++)
+ {
+ apcData[index].TimerId = index;
+ apcData[index].StartTime = GetTickCount();
+ apcData[index].DueTime = (index * 10) + 50;
+ apcData[index].Period = 100;
+ apcData[index].FireCount = 0;
+ apcData[index].MaxFireCount = FIRE_COUNT;
+
+ if (!(apcData[index].CompletionEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ printf("Failed to create apcData[%" PRIu32 "] event (%" PRIu32 ")\n", index,
+ GetLastError());
+ return -1;
+ }
+
+ if (!CreateTimerQueueTimer(&hTimers[index], hTimerQueue, TimerRoutine, &apcData[index],
+ apcData[index].DueTime, apcData[index].Period, 0))
+ {
+ printf("CreateTimerQueueTimer failed (%" PRIu32 ")\n", GetLastError());
+ return -1;
+ }
+ }
+
+ for (DWORD index = 0; index < TIMER_COUNT; index++)
+ {
+ if (WaitForSingleObject(apcData[index].CompletionEvent, 2000) != WAIT_OBJECT_0)
+ {
+ printf("Failed to wait for timer queue timer #%" PRIu32 " (%" PRIu32 ")\n", index,
+ GetLastError());
+ return -1;
+ }
+ }
+
+ for (DWORD index = 0; index < TIMER_COUNT; index++)
+ {
+ /**
+ * Note: If the CompletionEvent parameter is INVALID_HANDLE_VALUE, the function waits
+ * for any running timer callback functions to complete before returning.
+ */
+ if (!DeleteTimerQueueTimer(hTimerQueue, hTimers[index], INVALID_HANDLE_VALUE))
+ {
+ printf("DeleteTimerQueueTimer failed (%" PRIu32 ")\n", GetLastError());
+ return -1;
+ }
+ CloseHandle(apcData[index].CompletionEvent);
+ }
+
+ if (!DeleteTimerQueue(hTimerQueue))
+ {
+ printf("DeleteTimerQueue failed (%" PRIu32 ")\n", GetLastError());
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchWaitableTimer.c b/winpr/libwinpr/synch/test/TestSynchWaitableTimer.c
new file mode 100644
index 0000000..f61bbc1
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchWaitableTimer.c
@@ -0,0 +1,83 @@
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+
+int TestSynchWaitableTimer(int argc, char* argv[])
+{
+ DWORD status = 0;
+ HANDLE timer = NULL;
+ LONG period = 0;
+ LARGE_INTEGER due;
+ int result = -1;
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ timer = CreateWaitableTimer(NULL, FALSE, NULL);
+
+ if (!timer)
+ {
+ printf("CreateWaitableTimer failure\n");
+ goto out;
+ }
+
+ due.QuadPart = -1500000LL; /* 0.15 seconds */
+
+ if (!SetWaitableTimer(timer, &due, 0, NULL, NULL, 0))
+ {
+ printf("SetWaitableTimer failure\n");
+ goto out;
+ }
+
+ status = WaitForSingleObject(timer, INFINITE);
+
+ if (status != WAIT_OBJECT_0)
+ {
+ printf("WaitForSingleObject(timer, INFINITE) failure\n");
+ goto out;
+ }
+
+ printf("Timer Signaled\n");
+ status = WaitForSingleObject(timer, 200);
+
+ if (status != WAIT_TIMEOUT)
+ {
+ printf("WaitForSingleObject(timer, 200) failure: Actual: 0x%08" PRIX32
+ ", Expected: 0x%08X\n",
+ status, WAIT_TIMEOUT);
+ goto out;
+ }
+
+ due.QuadPart = 0;
+ period = 120; /* 0.12 seconds */
+
+ if (!SetWaitableTimer(timer, &due, period, NULL, NULL, 0))
+ {
+ printf("SetWaitableTimer failure\n");
+ goto out;
+ }
+
+ if (WaitForSingleObject(timer, INFINITE) != WAIT_OBJECT_0)
+ {
+ printf("WaitForSingleObject(timer, INFINITE) failure\n");
+ goto out;
+ }
+
+ printf("Timer Signaled\n");
+
+ if (!SetWaitableTimer(timer, &due, period, NULL, NULL, 0))
+ {
+ printf("SetWaitableTimer failure\n");
+ goto out;
+ }
+
+ if (WaitForMultipleObjects(1, &timer, FALSE, INFINITE) != WAIT_OBJECT_0)
+ {
+ printf("WaitForMultipleObjects(timer, INFINITE) failure\n");
+ goto out;
+ }
+
+ printf("Timer Signaled\n");
+ result = 0;
+out:
+ CloseHandle(timer);
+ return result;
+}
diff --git a/winpr/libwinpr/synch/test/TestSynchWaitableTimerAPC.c b/winpr/libwinpr/synch/test/TestSynchWaitableTimerAPC.c
new file mode 100644
index 0000000..cf1f677
--- /dev/null
+++ b/winpr/libwinpr/synch/test/TestSynchWaitableTimerAPC.c
@@ -0,0 +1,92 @@
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/sysinfo.h>
+
+static int g_Count = 0;
+static HANDLE g_Event = NULL;
+
+struct apc_data
+{
+ UINT32 StartTime;
+};
+typedef struct apc_data APC_DATA;
+
+static VOID CALLBACK TimerAPCProc(LPVOID lpArg, DWORD dwTimerLowValue, DWORD dwTimerHighValue)
+{
+ APC_DATA* apcData = NULL;
+ UINT32 CurrentTime = GetTickCount();
+ WINPR_UNUSED(dwTimerLowValue);
+ WINPR_UNUSED(dwTimerHighValue);
+
+ if (!lpArg)
+ return;
+
+ apcData = (APC_DATA*)lpArg;
+ printf("TimerAPCProc: time: %" PRIu32 "\n", CurrentTime - apcData->StartTime);
+ g_Count++;
+
+ if (g_Count >= 5)
+ {
+ SetEvent(g_Event);
+ }
+}
+
+int TestSynchWaitableTimerAPC(int argc, char* argv[])
+{
+ int status = -1;
+ DWORD rc = 0;
+ HANDLE hTimer = NULL;
+ BOOL bSuccess = 0;
+ LARGE_INTEGER due;
+ APC_DATA apcData = { 0 };
+ WINPR_UNUSED(argc);
+ WINPR_UNUSED(argv);
+ g_Event = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+ if (!g_Event)
+ {
+ printf("Failed to create event\n");
+ goto cleanup;
+ }
+
+ hTimer = CreateWaitableTimer(NULL, FALSE, NULL);
+ if (!hTimer)
+ goto cleanup;
+
+ due.QuadPart = -1000 * 100LL; /* 0.1 seconds */
+ apcData.StartTime = GetTickCount();
+ bSuccess = SetWaitableTimer(hTimer, &due, 10, TimerAPCProc, &apcData, FALSE);
+
+ if (!bSuccess)
+ goto cleanup;
+
+ /* nothing shall happen after 0.12 second, because thread is not in alertable state */
+ rc = WaitForSingleObject(g_Event, 120);
+ if (rc != WAIT_TIMEOUT)
+ goto cleanup;
+
+ for (;;)
+ {
+ rc = WaitForSingleObjectEx(g_Event, INFINITE, TRUE);
+ if (rc == WAIT_OBJECT_0)
+ break;
+
+ if (rc == WAIT_IO_COMPLETION)
+ continue;
+
+ printf("Failed to wait for completion event (%" PRIu32 ")\n", GetLastError());
+ goto cleanup;
+ }
+
+ status = 0;
+cleanup:
+
+ if (hTimer)
+ CloseHandle(hTimer);
+
+ if (g_Event)
+ CloseHandle(g_Event);
+
+ return status;
+}
diff --git a/winpr/libwinpr/synch/timer.c b/winpr/libwinpr/synch/timer.c
new file mode 100644
index 0000000..8238a88
--- /dev/null
+++ b/winpr/libwinpr/synch/timer.c
@@ -0,0 +1,1093 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Synchronization Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * 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/crt.h>
+#include <winpr/file.h>
+#include <winpr/assert.h>
+#include <winpr/sysinfo.h>
+
+#include <winpr/synch.h>
+
+#ifndef _WIN32
+#include <unistd.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <signal.h>
+#endif
+
+#include "event.h"
+#include "synch.h"
+
+#ifndef _WIN32
+
+#include "../handle/handle.h"
+#include "../thread/thread.h"
+
+#include "../log.h"
+#define TAG WINPR_TAG("synch.timer")
+
+static BOOL TimerCloseHandle(HANDLE handle);
+
+static BOOL TimerIsHandled(HANDLE handle)
+{
+ return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_TIMER, FALSE);
+}
+
+static int TimerGetFd(HANDLE handle)
+{
+ WINPR_TIMER* timer = (WINPR_TIMER*)handle;
+
+ if (!TimerIsHandled(handle))
+ return -1;
+
+ return timer->fd;
+}
+
+static DWORD TimerCleanupHandle(HANDLE handle)
+{
+ SSIZE_T length = 0;
+ UINT64 expirations = 0;
+ WINPR_TIMER* timer = (WINPR_TIMER*)handle;
+
+ if (!TimerIsHandled(handle))
+ return WAIT_FAILED;
+
+ if (timer->bManualReset)
+ return WAIT_OBJECT_0;
+
+#ifdef TIMER_IMPL_TIMERFD
+ do
+ {
+ length = read(timer->fd, (void*)&expirations, sizeof(UINT64));
+ } while (length < 0 && errno == EINTR);
+
+ if (length != 8)
+ {
+ if (length < 0)
+ {
+ char ebuffer[256] = { 0 };
+ switch (errno)
+ {
+ case ETIMEDOUT:
+ case EAGAIN:
+ return WAIT_TIMEOUT;
+
+ default:
+ break;
+ }
+
+ WLog_ERR(TAG, "timer read() failure [%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ }
+ else
+ {
+ WLog_ERR(TAG, "timer read() failure - incorrect number of bytes read");
+ }
+
+ return WAIT_FAILED;
+ }
+#elif defined(TIMER_IMPL_POSIX) || defined(TIMER_IMPL_DISPATCH)
+ if (!winpr_event_reset(&timer->event))
+ {
+ WLog_ERR(TAG, "timer reset() failure");
+ return WAIT_FAILED;
+ }
+#endif
+
+ return WAIT_OBJECT_0;
+}
+
+typedef struct
+{
+ WINPR_APC_ITEM apcItem;
+ WINPR_TIMER* timer;
+} TimerDeleter;
+
+static void TimerPostDelete_APC(LPVOID arg)
+{
+ TimerDeleter* deleter = (TimerDeleter*)arg;
+ WINPR_ASSERT(deleter);
+ free(deleter->timer);
+ deleter->apcItem.markedForFree = TRUE;
+ deleter->apcItem.markedForRemove = TRUE;
+}
+
+BOOL TimerCloseHandle(HANDLE handle)
+{
+ WINPR_TIMER* timer = NULL;
+ timer = (WINPR_TIMER*)handle;
+
+ if (!TimerIsHandled(handle))
+ return FALSE;
+
+#ifdef TIMER_IMPL_TIMERFD
+ if (timer->fd != -1)
+ close(timer->fd);
+#endif
+
+#ifdef TIMER_IMPL_POSIX
+ timer_delete(timer->tid);
+#endif
+
+#ifdef TIMER_IMPL_DISPATCH
+ dispatch_release(timer->queue);
+ dispatch_release(timer->source);
+#endif
+
+#if defined(TIMER_IMPL_POSIX) || defined(TIMER_IMPL_DISPATCH)
+ winpr_event_uninit(&timer->event);
+#endif
+
+ free(timer->name);
+ if (timer->apcItem.linked)
+ {
+ TimerDeleter* deleter = NULL;
+ WINPR_APC_ITEM* apcItem = NULL;
+
+ switch (apc_remove(&timer->apcItem))
+ {
+ case APC_REMOVE_OK:
+ break;
+ case APC_REMOVE_DELAY_FREE:
+ {
+ WINPR_THREAD* thread = winpr_GetCurrentThread();
+ if (!thread)
+ return FALSE;
+
+ deleter = calloc(1, sizeof(*deleter));
+ if (!deleter)
+ {
+ WLog_ERR(TAG, "unable to allocate a timer deleter");
+ return TRUE;
+ }
+
+ deleter->timer = timer;
+ apcItem = &deleter->apcItem;
+ apcItem->type = APC_TYPE_HANDLE_FREE;
+ apcItem->alwaysSignaled = TRUE;
+ apcItem->completion = TimerPostDelete_APC;
+ apcItem->completionArgs = deleter;
+ apc_register(thread, apcItem);
+ return TRUE;
+ }
+ case APC_REMOVE_ERROR:
+ default:
+ WLog_ERR(TAG, "unable to remove timer from APC list");
+ break;
+ }
+ }
+
+ free(timer);
+ return TRUE;
+}
+
+#ifdef TIMER_IMPL_POSIX
+
+static void WaitableTimerSignalHandler(int signum, siginfo_t* siginfo, void* arg)
+{
+ WINPR_TIMER* timer = siginfo->si_value.sival_ptr;
+ UINT64 data = 1;
+ WINPR_UNUSED(arg);
+
+ if (!timer || (signum != SIGALRM))
+ return;
+
+ if (!winpr_event_set(&timer->event))
+ WLog_ERR(TAG, "error when notifying event");
+}
+
+static INIT_ONCE TimerSignalHandler_InitOnce = INIT_ONCE_STATIC_INIT;
+
+static BOOL InstallTimerSignalHandler(PINIT_ONCE InitOnce, PVOID Parameter, PVOID* Context)
+{
+ struct sigaction action;
+ sigemptyset(&action.sa_mask);
+ sigaddset(&action.sa_mask, SIGALRM);
+ action.sa_flags = SA_RESTART | SA_SIGINFO;
+ action.sa_sigaction = WaitableTimerSignalHandler;
+ sigaction(SIGALRM, &action, NULL);
+ return TRUE;
+}
+#endif
+
+#ifdef TIMER_IMPL_DISPATCH
+static void WaitableTimerHandler(void* arg)
+{
+ UINT64 data = 1;
+ WINPR_TIMER* timer = (WINPR_TIMER*)arg;
+
+ if (!timer)
+ return;
+
+ if (!winpr_event_set(&timer->event))
+ WLog_ERR(TAG, "failed to write to pipe");
+
+ if (timer->lPeriod == 0)
+ {
+ if (timer->running)
+ dispatch_suspend(timer->source);
+
+ timer->running = FALSE;
+ }
+}
+#endif
+
+static int InitializeWaitableTimer(WINPR_TIMER* timer)
+{
+ int result = 0;
+
+#ifdef TIMER_IMPL_TIMERFD
+ timer->fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
+ if (timer->fd <= 0)
+ return -1;
+#elif defined(TIMER_IMPL_POSIX)
+ struct sigevent sigev = { 0 };
+ InitOnceExecuteOnce(&TimerSignalHandler_InitOnce, InstallTimerSignalHandler, NULL, NULL);
+ sigev.sigev_notify = SIGEV_SIGNAL;
+ sigev.sigev_signo = SIGALRM;
+ sigev.sigev_value.sival_ptr = (void*)timer;
+
+ if ((timer_create(CLOCK_MONOTONIC, &sigev, &(timer->tid))) != 0)
+ {
+ WLog_ERR(TAG, "timer_create");
+ return -1;
+ }
+#elif !defined(TIMER_IMPL_DISPATCH)
+ WLog_ERR(TAG, "os specific implementation is missing");
+ result = -1;
+#endif
+
+ timer->bInit = TRUE;
+ return result;
+}
+
+static BOOL timer_drain_fd(int fd)
+{
+ UINT64 expr = 0;
+ SSIZE_T ret = 0;
+
+ do
+ {
+ ret = read(fd, &expr, sizeof(expr));
+ } while (ret < 0 && errno == EINTR);
+
+ return ret >= 0;
+}
+
+static HANDLE_OPS ops = { TimerIsHandled,
+ TimerCloseHandle,
+ TimerGetFd,
+ TimerCleanupHandle,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL };
+
+/**
+ * Waitable Timer
+ */
+
+HANDLE CreateWaitableTimerA(LPSECURITY_ATTRIBUTES lpTimerAttributes, BOOL bManualReset,
+ LPCSTR lpTimerName)
+{
+ HANDLE handle = NULL;
+ WINPR_TIMER* timer = NULL;
+
+ if (lpTimerAttributes)
+ WLog_WARN(TAG, "[%s] does not support lpTimerAttributes", lpTimerName);
+
+ timer = (WINPR_TIMER*)calloc(1, sizeof(WINPR_TIMER));
+
+ if (timer)
+ {
+ WINPR_HANDLE_SET_TYPE_AND_MODE(timer, HANDLE_TYPE_TIMER, WINPR_FD_READ);
+ handle = (HANDLE)timer;
+ timer->fd = -1;
+ timer->lPeriod = 0;
+ timer->bManualReset = bManualReset;
+ timer->pfnCompletionRoutine = NULL;
+ timer->lpArgToCompletionRoutine = NULL;
+ timer->bInit = FALSE;
+
+ if (lpTimerName)
+ timer->name = strdup(lpTimerName);
+
+ timer->common.ops = &ops;
+#if defined(TIMER_IMPL_DISPATCH) || defined(TIMER_IMPL_POSIX)
+ if (!winpr_event_init(&timer->event))
+ goto fail;
+ timer->fd = timer->event.fds[0];
+#endif
+
+#if defined(TIMER_IMPL_DISPATCH)
+ timer->queue = dispatch_queue_create(TAG, DISPATCH_QUEUE_SERIAL);
+
+ if (!timer->queue)
+ goto fail;
+
+ timer->source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, timer->queue);
+
+ if (!timer->source)
+ goto fail;
+
+ dispatch_set_context(timer->source, timer);
+ dispatch_source_set_event_handler_f(timer->source, WaitableTimerHandler);
+#endif
+ }
+
+ return handle;
+
+#if defined(TIMER_IMPL_DISPATCH) || defined(TIMER_IMPL_POSIX)
+fail:
+ TimerCloseHandle(handle);
+ return NULL;
+#endif
+}
+
+HANDLE CreateWaitableTimerW(LPSECURITY_ATTRIBUTES lpTimerAttributes, BOOL bManualReset,
+ LPCWSTR lpTimerName)
+{
+ HANDLE handle = NULL;
+ LPSTR name = NULL;
+
+ if (lpTimerName)
+ {
+ name = ConvertWCharToUtf8Alloc(lpTimerName, NULL);
+ if (!name)
+ return NULL;
+ }
+
+ handle = CreateWaitableTimerA(lpTimerAttributes, bManualReset, name);
+ free(name);
+ return handle;
+}
+
+HANDLE CreateWaitableTimerExA(LPSECURITY_ATTRIBUTES lpTimerAttributes, LPCSTR lpTimerName,
+ DWORD dwFlags, DWORD dwDesiredAccess)
+{
+ BOOL bManualReset = (dwFlags & CREATE_WAITABLE_TIMER_MANUAL_RESET) ? TRUE : FALSE;
+
+ if (dwDesiredAccess != 0)
+ WLog_WARN(TAG, "[%s] does not support dwDesiredAccess 0x%08" PRIx32, lpTimerName,
+ dwDesiredAccess);
+
+ return CreateWaitableTimerA(lpTimerAttributes, bManualReset, lpTimerName);
+}
+
+HANDLE CreateWaitableTimerExW(LPSECURITY_ATTRIBUTES lpTimerAttributes, LPCWSTR lpTimerName,
+ DWORD dwFlags, DWORD dwDesiredAccess)
+{
+ HANDLE handle = NULL;
+ LPSTR name = NULL;
+
+ if (lpTimerName)
+ {
+ name = ConvertWCharToUtf8Alloc(lpTimerName, NULL);
+ if (!name)
+ return NULL;
+ }
+
+ handle = CreateWaitableTimerExA(lpTimerAttributes, name, dwFlags, dwDesiredAccess);
+ free(name);
+ return handle;
+}
+
+static void timerAPC(LPVOID arg)
+{
+ WINPR_TIMER* timer = (WINPR_TIMER*)arg;
+ WINPR_ASSERT(timer);
+ if (!timer->lPeriod)
+ {
+ /* this is a one time shot timer with a completion, let's remove us from
+ the APC list */
+ switch (apc_remove(&timer->apcItem))
+ {
+ case APC_REMOVE_OK:
+ case APC_REMOVE_DELAY_FREE:
+ break;
+ case APC_REMOVE_ERROR:
+ default:
+ WLog_ERR(TAG, "error removing the APC routine");
+ }
+ }
+
+ if (timer->pfnCompletionRoutine)
+ timer->pfnCompletionRoutine(timer->lpArgToCompletionRoutine, 0, 0);
+
+#ifdef TIMER_IMPL_TIMERFD
+ while (timer_drain_fd(timer->fd))
+ ;
+#elif defined(TIMER_IMPL_POSIX) || defined(TIMER_IMPL_DISPATCH)
+ winpr_event_reset(&timer->event);
+#endif
+}
+
+BOOL SetWaitableTimer(HANDLE hTimer, const LARGE_INTEGER* lpDueTime, LONG lPeriod,
+ PTIMERAPCROUTINE pfnCompletionRoutine, LPVOID lpArgToCompletionRoutine,
+ BOOL fResume)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+ WINPR_TIMER* timer = NULL;
+ LONGLONG seconds = 0;
+ LONGLONG nanoseconds = 0;
+ int status = 0;
+
+ if (!winpr_Handle_GetInfo(hTimer, &Type, &Object))
+ return FALSE;
+
+ if (Type != HANDLE_TYPE_TIMER)
+ return FALSE;
+
+ if (!lpDueTime)
+ return FALSE;
+
+ if (lPeriod < 0)
+ return FALSE;
+
+ if (fResume)
+ {
+ WLog_ERR(TAG, "does not support fResume");
+ return FALSE;
+ }
+
+ timer = (WINPR_TIMER*)Object;
+ timer->lPeriod = lPeriod; /* milliseconds */
+ timer->pfnCompletionRoutine = pfnCompletionRoutine;
+ timer->lpArgToCompletionRoutine = lpArgToCompletionRoutine;
+
+ if (!timer->bInit)
+ {
+ if (InitializeWaitableTimer(timer) < 0)
+ return FALSE;
+ }
+
+#if defined(TIMER_IMPL_TIMERFD) || defined(TIMER_IMPL_POSIX)
+ ZeroMemory(&(timer->timeout), sizeof(struct itimerspec));
+
+ if (lpDueTime->QuadPart < 0)
+ {
+ LONGLONG due = lpDueTime->QuadPart * (-1);
+ /* due time is in 100 nanosecond intervals */
+ seconds = (due / 10000000);
+ nanoseconds = ((due % 10000000) * 100);
+ }
+ else if (lpDueTime->QuadPart == 0)
+ {
+ seconds = nanoseconds = 0;
+ }
+ else
+ {
+ WLog_ERR(TAG, "absolute time not implemented");
+ return FALSE;
+ }
+
+ if (lPeriod > 0)
+ {
+ timer->timeout.it_interval.tv_sec = (lPeriod / 1000); /* seconds */
+ timer->timeout.it_interval.tv_nsec = ((lPeriod % 1000) * 1000000); /* nanoseconds */
+ }
+
+ if (lpDueTime->QuadPart != 0)
+ {
+ timer->timeout.it_value.tv_sec = seconds; /* seconds */
+ timer->timeout.it_value.tv_nsec = nanoseconds; /* nanoseconds */
+ }
+ else
+ {
+ timer->timeout.it_value.tv_sec = timer->timeout.it_interval.tv_sec; /* seconds */
+ timer->timeout.it_value.tv_nsec = timer->timeout.it_interval.tv_nsec; /* nanoseconds */
+ }
+
+#ifdef TIMER_IMPL_TIMERFD
+ status = timerfd_settime(timer->fd, 0, &(timer->timeout), NULL);
+ if (status)
+ {
+ WLog_ERR(TAG, "timerfd_settime failure: %d", status);
+ return FALSE;
+ }
+#else
+ status = timer_settime(timer->tid, 0, &(timer->timeout), NULL);
+ if (status != 0)
+ {
+ WLog_ERR(TAG, "timer_settime failure");
+ return FALSE;
+ }
+#endif
+#endif
+
+#ifdef TIMER_IMPL_DISPATCH
+ if (lpDueTime->QuadPart < 0)
+ {
+ LONGLONG due = lpDueTime->QuadPart * (-1);
+ /* due time is in 100 nanosecond intervals */
+ seconds = (due / 10000000);
+ nanoseconds = due * 100;
+ }
+ else if (lpDueTime->QuadPart == 0)
+ {
+ seconds = nanoseconds = 0;
+ }
+ else
+ {
+ WLog_ERR(TAG, "absolute time not implemented");
+ return FALSE;
+ }
+
+ if (!winpr_event_reset(&timer->event))
+ {
+ WLog_ERR(TAG, "error when resetting timer event");
+ }
+
+ {
+ if (timer->running)
+ dispatch_suspend(timer->source);
+
+ dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, nanoseconds);
+ uint64_t interval = DISPATCH_TIME_FOREVER;
+
+ if (lPeriod > 0)
+ interval = lPeriod * 1000000;
+
+ dispatch_source_set_timer(timer->source, start, interval, 0);
+ dispatch_resume(timer->source);
+ timer->running = TRUE;
+ }
+#endif
+
+ if (pfnCompletionRoutine)
+ {
+ WINPR_APC_ITEM* apcItem = &timer->apcItem;
+
+ /* install our APC routine that will call the completion */
+ apcItem->type = APC_TYPE_TIMER;
+ apcItem->alwaysSignaled = FALSE;
+ apcItem->pollFd = timer->fd;
+ apcItem->pollMode = WINPR_FD_READ;
+ apcItem->completion = timerAPC;
+ apcItem->completionArgs = timer;
+
+ if (!apcItem->linked)
+ {
+ WINPR_THREAD* thread = winpr_GetCurrentThread();
+ if (!thread)
+ return FALSE;
+
+ apc_register(thread, apcItem);
+ }
+ }
+ else
+ {
+ if (timer->apcItem.linked)
+ {
+ apc_remove(&timer->apcItem);
+ }
+ }
+ return TRUE;
+}
+
+BOOL SetWaitableTimerEx(HANDLE hTimer, const LARGE_INTEGER* lpDueTime, LONG lPeriod,
+ PTIMERAPCROUTINE pfnCompletionRoutine, LPVOID lpArgToCompletionRoutine,
+ PREASON_CONTEXT WakeContext, ULONG TolerableDelay)
+{
+ return SetWaitableTimer(hTimer, lpDueTime, lPeriod, pfnCompletionRoutine,
+ lpArgToCompletionRoutine, FALSE);
+}
+
+HANDLE OpenWaitableTimerA(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCSTR lpTimerName)
+{
+ /* TODO: Implement */
+ WLog_ERR(TAG, "not implemented");
+ return NULL;
+}
+
+HANDLE OpenWaitableTimerW(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCWSTR lpTimerName)
+{
+ /* TODO: Implement */
+ WLog_ERR(TAG, "not implemented");
+ return NULL;
+}
+
+BOOL CancelWaitableTimer(HANDLE hTimer)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+
+ if (!winpr_Handle_GetInfo(hTimer, &Type, &Object))
+ return FALSE;
+
+ if (Type != HANDLE_TYPE_TIMER)
+ return FALSE;
+
+#if defined(__APPLE__)
+ {
+ WINPR_TIMER* timer = (WINPR_TIMER*)Object;
+ if (timer->running)
+ dispatch_suspend(timer->source);
+
+ timer->running = FALSE;
+ }
+#endif
+ return TRUE;
+}
+
+/*
+ * Returns inner file descriptor for usage with select()
+ * This file descriptor is not usable on Windows
+ */
+
+int GetTimerFileDescriptor(HANDLE hTimer)
+{
+#ifndef _WIN32
+ WINPR_HANDLE* hdl = NULL;
+ ULONG type = 0;
+
+ if (!winpr_Handle_GetInfo(hTimer, &type, &hdl) || type != HANDLE_TYPE_TIMER)
+ {
+ WLog_ERR(TAG, "GetTimerFileDescriptor: hTimer is not an timer");
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return -1;
+ }
+
+ return winpr_Handle_getFd(hTimer);
+#else
+ return -1;
+#endif
+}
+
+/**
+ * Timer-Queue Timer
+ */
+
+/**
+ * Design, Performance, and Optimization of Timer Strategies for Real-time ORBs:
+ * http://www.cs.wustl.edu/~schmidt/Timer_Queue.html
+ */
+
+static void timespec_add_ms(struct timespec* tspec, UINT32 ms)
+{
+ INT64 ns = 0;
+ WINPR_ASSERT(tspec);
+ ns = tspec->tv_nsec + (ms * 1000000LL);
+ tspec->tv_sec += (ns / 1000000000LL);
+ tspec->tv_nsec = (ns % 1000000000LL);
+}
+
+static void timespec_gettimeofday(struct timespec* tspec)
+{
+ struct timeval tval;
+ WINPR_ASSERT(tspec);
+ gettimeofday(&tval, NULL);
+ tspec->tv_sec = tval.tv_sec;
+ tspec->tv_nsec = tval.tv_usec * 1000;
+}
+
+static INT64 timespec_compare(const struct timespec* tspec1, const struct timespec* tspec2)
+{
+ WINPR_ASSERT(tspec1);
+ WINPR_ASSERT(tspec2);
+ if (tspec1->tv_sec == tspec2->tv_sec)
+ return (tspec1->tv_nsec - tspec2->tv_nsec);
+ else
+ return (tspec1->tv_sec - tspec2->tv_sec);
+}
+
+static void timespec_copy(struct timespec* dst, struct timespec* src)
+{
+ WINPR_ASSERT(dst);
+ WINPR_ASSERT(src);
+ dst->tv_sec = src->tv_sec;
+ dst->tv_nsec = src->tv_nsec;
+}
+
+static void InsertTimerQueueTimer(WINPR_TIMER_QUEUE_TIMER** pHead, WINPR_TIMER_QUEUE_TIMER* timer)
+{
+ WINPR_TIMER_QUEUE_TIMER* node = NULL;
+
+ WINPR_ASSERT(pHead);
+ WINPR_ASSERT(timer);
+
+ if (!(*pHead))
+ {
+ *pHead = timer;
+ timer->next = NULL;
+ return;
+ }
+
+ node = *pHead;
+
+ while (node->next)
+ {
+ if (timespec_compare(&(timer->ExpirationTime), &(node->ExpirationTime)) > 0)
+ {
+ if (timespec_compare(&(timer->ExpirationTime), &(node->next->ExpirationTime)) < 0)
+ break;
+ }
+
+ node = node->next;
+ }
+
+ if (node->next)
+ {
+ timer->next = node->next->next;
+ node->next = timer;
+ }
+ else
+ {
+ node->next = timer;
+ timer->next = NULL;
+ }
+}
+
+static void RemoveTimerQueueTimer(WINPR_TIMER_QUEUE_TIMER** pHead, WINPR_TIMER_QUEUE_TIMER* timer)
+{
+ BOOL found = FALSE;
+ WINPR_TIMER_QUEUE_TIMER* node = NULL;
+ WINPR_TIMER_QUEUE_TIMER* prevNode = NULL;
+
+ WINPR_ASSERT(pHead);
+ WINPR_ASSERT(timer);
+ if (timer == *pHead)
+ {
+ *pHead = timer->next;
+ timer->next = NULL;
+ return;
+ }
+
+ node = *pHead;
+ prevNode = NULL;
+
+ while (node)
+ {
+ if (node == timer)
+ {
+ found = TRUE;
+ break;
+ }
+
+ prevNode = node;
+ node = node->next;
+ }
+
+ if (found)
+ {
+ if (prevNode)
+ {
+ prevNode->next = timer->next;
+ }
+
+ timer->next = NULL;
+ }
+}
+
+static int FireExpiredTimerQueueTimers(WINPR_TIMER_QUEUE* timerQueue)
+{
+ struct timespec CurrentTime;
+ WINPR_TIMER_QUEUE_TIMER* node = NULL;
+
+ WINPR_ASSERT(timerQueue);
+
+ if (!timerQueue->activeHead)
+ return 0;
+
+ timespec_gettimeofday(&CurrentTime);
+ node = timerQueue->activeHead;
+
+ while (node)
+ {
+ if (timespec_compare(&CurrentTime, &(node->ExpirationTime)) >= 0)
+ {
+ node->Callback(node->Parameter, TRUE);
+ node->FireCount++;
+ timerQueue->activeHead = node->next;
+ node->next = NULL;
+
+ if (node->Period)
+ {
+ timespec_add_ms(&(node->ExpirationTime), node->Period);
+ InsertTimerQueueTimer(&(timerQueue->activeHead), node);
+ }
+ else
+ {
+ InsertTimerQueueTimer(&(timerQueue->inactiveHead), node);
+ }
+
+ node = timerQueue->activeHead;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static void* TimerQueueThread(void* arg)
+{
+ int status = 0;
+ struct timespec timeout;
+ WINPR_TIMER_QUEUE* timerQueue = (WINPR_TIMER_QUEUE*)arg;
+
+ WINPR_ASSERT(timerQueue);
+ while (1)
+ {
+ pthread_mutex_lock(&(timerQueue->cond_mutex));
+ timespec_gettimeofday(&timeout);
+
+ if (!timerQueue->activeHead)
+ {
+ timespec_add_ms(&timeout, 50);
+ }
+ else
+ {
+ if (timespec_compare(&timeout, &(timerQueue->activeHead->ExpirationTime)) < 0)
+ {
+ timespec_copy(&timeout, &(timerQueue->activeHead->ExpirationTime));
+ }
+ }
+
+ status = pthread_cond_timedwait(&(timerQueue->cond), &(timerQueue->cond_mutex), &timeout);
+ FireExpiredTimerQueueTimers(timerQueue);
+ pthread_mutex_unlock(&(timerQueue->cond_mutex));
+
+ if ((status != ETIMEDOUT) && (status != 0))
+ break;
+
+ if (timerQueue->bCancelled)
+ break;
+ }
+
+ return NULL;
+}
+
+static int StartTimerQueueThread(WINPR_TIMER_QUEUE* timerQueue)
+{
+ WINPR_ASSERT(timerQueue);
+ pthread_cond_init(&(timerQueue->cond), NULL);
+ pthread_mutex_init(&(timerQueue->cond_mutex), NULL);
+ pthread_mutex_init(&(timerQueue->mutex), NULL);
+ pthread_attr_init(&(timerQueue->attr));
+ timerQueue->param.sched_priority = sched_get_priority_max(SCHED_FIFO);
+ pthread_attr_setschedparam(&(timerQueue->attr), &(timerQueue->param));
+ pthread_attr_setschedpolicy(&(timerQueue->attr), SCHED_FIFO);
+ pthread_create(&(timerQueue->thread), &(timerQueue->attr), TimerQueueThread, timerQueue);
+ return 0;
+}
+
+HANDLE CreateTimerQueue(void)
+{
+ HANDLE handle = NULL;
+ WINPR_TIMER_QUEUE* timerQueue = NULL;
+ timerQueue = (WINPR_TIMER_QUEUE*)calloc(1, sizeof(WINPR_TIMER_QUEUE));
+
+ if (timerQueue)
+ {
+ WINPR_HANDLE_SET_TYPE_AND_MODE(timerQueue, HANDLE_TYPE_TIMER_QUEUE, WINPR_FD_READ);
+ handle = (HANDLE)timerQueue;
+ timerQueue->activeHead = NULL;
+ timerQueue->inactiveHead = NULL;
+ timerQueue->bCancelled = FALSE;
+ StartTimerQueueThread(timerQueue);
+ }
+
+ return handle;
+}
+
+BOOL DeleteTimerQueueEx(HANDLE TimerQueue, HANDLE CompletionEvent)
+{
+ void* rvalue = NULL;
+ WINPR_TIMER_QUEUE* timerQueue = NULL;
+ WINPR_TIMER_QUEUE_TIMER* node = NULL;
+ WINPR_TIMER_QUEUE_TIMER* nextNode = NULL;
+
+ if (!TimerQueue)
+ return FALSE;
+
+ timerQueue = (WINPR_TIMER_QUEUE*)TimerQueue;
+ /* Cancel and delete timer queue timers */
+ pthread_mutex_lock(&(timerQueue->cond_mutex));
+ timerQueue->bCancelled = TRUE;
+ pthread_cond_signal(&(timerQueue->cond));
+ pthread_mutex_unlock(&(timerQueue->cond_mutex));
+ pthread_join(timerQueue->thread, &rvalue);
+ /**
+ * Quote from MSDN regarding CompletionEvent:
+ * If this parameter is INVALID_HANDLE_VALUE, the function waits for
+ * all callback functions to complete before returning.
+ * If this parameter is NULL, the function marks the timer for
+ * deletion and returns immediately.
+ *
+ * Note: The current WinPR implementation implicitly waits for any
+ * callback functions to complete (see pthread_join above)
+ */
+ {
+ /* Move all active timers to the inactive timer list */
+ node = timerQueue->activeHead;
+
+ while (node)
+ {
+ InsertTimerQueueTimer(&(timerQueue->inactiveHead), node);
+ node = node->next;
+ }
+
+ timerQueue->activeHead = NULL;
+ /* Once all timers are inactive, free them */
+ node = timerQueue->inactiveHead;
+
+ while (node)
+ {
+ nextNode = node->next;
+ free(node);
+ node = nextNode;
+ }
+
+ timerQueue->inactiveHead = NULL;
+ }
+ /* Delete timer queue */
+ pthread_cond_destroy(&(timerQueue->cond));
+ pthread_mutex_destroy(&(timerQueue->cond_mutex));
+ pthread_mutex_destroy(&(timerQueue->mutex));
+ pthread_attr_destroy(&(timerQueue->attr));
+ free(timerQueue);
+
+ if (CompletionEvent && (CompletionEvent != INVALID_HANDLE_VALUE))
+ SetEvent(CompletionEvent);
+
+ return TRUE;
+}
+
+BOOL DeleteTimerQueue(HANDLE TimerQueue)
+{
+ return DeleteTimerQueueEx(TimerQueue, NULL);
+}
+
+BOOL CreateTimerQueueTimer(PHANDLE phNewTimer, HANDLE TimerQueue, WAITORTIMERCALLBACK Callback,
+ PVOID Parameter, DWORD DueTime, DWORD Period, ULONG Flags)
+{
+ struct timespec CurrentTime;
+ WINPR_TIMER_QUEUE* timerQueue = NULL;
+ WINPR_TIMER_QUEUE_TIMER* timer = NULL;
+
+ if (!TimerQueue)
+ return FALSE;
+
+ timespec_gettimeofday(&CurrentTime);
+ timerQueue = (WINPR_TIMER_QUEUE*)TimerQueue;
+ timer = (WINPR_TIMER_QUEUE_TIMER*)malloc(sizeof(WINPR_TIMER_QUEUE_TIMER));
+
+ if (!timer)
+ return FALSE;
+
+ WINPR_HANDLE_SET_TYPE_AND_MODE(timer, HANDLE_TYPE_TIMER_QUEUE_TIMER, WINPR_FD_READ);
+ *((UINT_PTR*)phNewTimer) = (UINT_PTR)(HANDLE)timer;
+ timespec_copy(&(timer->StartTime), &CurrentTime);
+ timespec_add_ms(&(timer->StartTime), DueTime);
+ timespec_copy(&(timer->ExpirationTime), &(timer->StartTime));
+ timer->Flags = Flags;
+ timer->DueTime = DueTime;
+ timer->Period = Period;
+ timer->Callback = Callback;
+ timer->Parameter = Parameter;
+ timer->timerQueue = (WINPR_TIMER_QUEUE*)TimerQueue;
+ timer->FireCount = 0;
+ timer->next = NULL;
+ pthread_mutex_lock(&(timerQueue->cond_mutex));
+ InsertTimerQueueTimer(&(timerQueue->activeHead), timer);
+ pthread_cond_signal(&(timerQueue->cond));
+ pthread_mutex_unlock(&(timerQueue->cond_mutex));
+ return TRUE;
+}
+
+BOOL ChangeTimerQueueTimer(HANDLE TimerQueue, HANDLE Timer, ULONG DueTime, ULONG Period)
+{
+ struct timespec CurrentTime;
+ WINPR_TIMER_QUEUE* timerQueue = NULL;
+ WINPR_TIMER_QUEUE_TIMER* timer = NULL;
+
+ if (!TimerQueue || !Timer)
+ return FALSE;
+
+ timespec_gettimeofday(&CurrentTime);
+ timerQueue = (WINPR_TIMER_QUEUE*)TimerQueue;
+ timer = (WINPR_TIMER_QUEUE_TIMER*)Timer;
+ pthread_mutex_lock(&(timerQueue->cond_mutex));
+ RemoveTimerQueueTimer(&(timerQueue->activeHead), timer);
+ RemoveTimerQueueTimer(&(timerQueue->inactiveHead), timer);
+ timer->DueTime = DueTime;
+ timer->Period = Period;
+ timer->next = NULL;
+ timespec_copy(&(timer->StartTime), &CurrentTime);
+ timespec_add_ms(&(timer->StartTime), DueTime);
+ timespec_copy(&(timer->ExpirationTime), &(timer->StartTime));
+ InsertTimerQueueTimer(&(timerQueue->activeHead), timer);
+ pthread_cond_signal(&(timerQueue->cond));
+ pthread_mutex_unlock(&(timerQueue->cond_mutex));
+ return TRUE;
+}
+
+BOOL DeleteTimerQueueTimer(HANDLE TimerQueue, HANDLE Timer, HANDLE CompletionEvent)
+{
+ WINPR_TIMER_QUEUE* timerQueue = NULL;
+ WINPR_TIMER_QUEUE_TIMER* timer = NULL;
+
+ if (!TimerQueue || !Timer)
+ return FALSE;
+
+ timerQueue = (WINPR_TIMER_QUEUE*)TimerQueue;
+ timer = (WINPR_TIMER_QUEUE_TIMER*)Timer;
+ pthread_mutex_lock(&(timerQueue->cond_mutex));
+ /**
+ * Quote from MSDN regarding CompletionEvent:
+ * If this parameter is INVALID_HANDLE_VALUE, the function waits for
+ * all callback functions to complete before returning.
+ * If this parameter is NULL, the function marks the timer for
+ * deletion and returns immediately.
+ *
+ * Note: The current WinPR implementation implicitly waits for any
+ * callback functions to complete (see cond_mutex usage)
+ */
+ RemoveTimerQueueTimer(&(timerQueue->activeHead), timer);
+ pthread_cond_signal(&(timerQueue->cond));
+ pthread_mutex_unlock(&(timerQueue->cond_mutex));
+ free(timer);
+
+ if (CompletionEvent && (CompletionEvent != INVALID_HANDLE_VALUE))
+ SetEvent(CompletionEvent);
+
+ return TRUE;
+}
+
+#endif
diff --git a/winpr/libwinpr/synch/wait.c b/winpr/libwinpr/synch/wait.c
new file mode 100644
index 0000000..3bef657
--- /dev/null
+++ b/winpr/libwinpr/synch/wait.c
@@ -0,0 +1,583 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Synchronization Functions
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 Hardening <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>
+
+#ifdef WINPR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <winpr/assert.h>
+#include <errno.h>
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/platform.h>
+#include <winpr/sysinfo.h>
+
+#include "synch.h"
+#include "pollset.h"
+#include "../thread/thread.h"
+#include <winpr/thread.h>
+#include <winpr/debug.h>
+
+#include "../log.h"
+#define TAG WINPR_TAG("sync.wait")
+
+/**
+ * WaitForSingleObject
+ * WaitForSingleObjectEx
+ * WaitForMultipleObjectsEx
+ * SignalObjectAndWait
+ */
+
+#ifndef _WIN32
+
+#include <stdlib.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+
+#include "../handle/handle.h"
+
+#include "../pipe/pipe.h"
+
+/* clock_gettime is not implemented on OSX prior to 10.12 */
+#if defined(__MACH__) && defined(__APPLE__)
+
+#include <mach/mach_time.h>
+
+#ifndef CLOCK_REALTIME
+#define CLOCK_REALTIME 0
+#endif
+
+#ifndef CLOCK_MONOTONIC
+#define CLOCK_MONOTONIC 0
+#endif
+
+/* clock_gettime is not implemented on OSX prior to 10.12 */
+int _mach_clock_gettime(int clk_id, struct timespec* t);
+
+int _mach_clock_gettime(int clk_id, struct timespec* t)
+{
+ UINT64 time = 0;
+ double seconds = 0.0;
+ double nseconds = 0.0;
+ mach_timebase_info_data_t timebase = { 0 };
+ mach_timebase_info(&timebase);
+ time = mach_absolute_time();
+ nseconds = ((double)time * (double)timebase.numer) / ((double)timebase.denom);
+ seconds = ((double)time * (double)timebase.numer) / ((double)timebase.denom * 1e9);
+ t->tv_sec = seconds;
+ t->tv_nsec = nseconds;
+ return 0;
+}
+
+/* if clock_gettime is declared, then __CLOCK_AVAILABILITY will be defined */
+#ifdef __CLOCK_AVAILABILITY
+/* If we compiled with Mac OSX 10.12 or later, then clock_gettime will be declared
+ * * but it may be NULL at runtime. So we need to check before using it. */
+int _mach_safe_clock_gettime(int clk_id, struct timespec* t);
+
+int _mach_safe_clock_gettime(int clk_id, struct timespec* t)
+{
+ if (clock_gettime)
+ {
+ return clock_gettime(clk_id, t);
+ }
+
+ return _mach_clock_gettime(clk_id, t);
+}
+
+#define clock_gettime _mach_safe_clock_gettime
+#else
+#define clock_gettime _mach_clock_gettime
+#endif
+
+#endif
+
+/**
+ * Drop in replacement for pthread_mutex_timedlock
+ * http://code.google.com/p/android/issues/detail?id=7807
+ * http://aleksmaus.blogspot.ca/2011/12/missing-pthreadmutextimedlock-on.html
+ */
+#if !defined(WINPR_HAVE_PTHREAD_MUTEX_TIMEDLOCK)
+#include <pthread.h>
+
+static long long ts_difftime(const struct timespec* o, const struct timespec* n)
+{
+ long long oldValue = o->tv_sec * 1000000000LL + o->tv_nsec;
+ long long newValue = n->tv_sec * 1000000000LL + n->tv_nsec;
+ return newValue - oldValue;
+}
+
+#ifdef ANDROID
+#if (__ANDROID_API__ >= 21)
+#define CONST_NEEDED const
+#else
+#define CONST_NEEDED
+#endif
+#define STATIC_NEEDED
+#else /* ANDROID */
+#define CONST_NEEDED const
+#define STATIC_NEEDED static
+#endif
+
+STATIC_NEEDED int pthread_mutex_timedlock(pthread_mutex_t* mutex,
+ CONST_NEEDED struct timespec* timeout)
+{
+ struct timespec timenow = { 0 };
+ struct timespec sleepytime = { 0 };
+ unsigned long long diff = 0;
+ int retcode = -1;
+ /* This is just to avoid a completely busy wait */
+ clock_gettime(CLOCK_MONOTONIC, &timenow);
+ diff = ts_difftime(&timenow, timeout);
+ sleepytime.tv_sec = diff / 1000000000LL;
+ sleepytime.tv_nsec = diff % 1000000000LL;
+
+ while ((retcode = pthread_mutex_trylock(mutex)) == EBUSY)
+ {
+ clock_gettime(CLOCK_MONOTONIC, &timenow);
+
+ if (ts_difftime(timeout, &timenow) >= 0)
+ {
+ return ETIMEDOUT;
+ }
+
+ nanosleep(&sleepytime, NULL);
+ }
+
+ return retcode;
+}
+#endif
+
+static void ts_add_ms(struct timespec* ts, DWORD dwMilliseconds)
+{
+ ts->tv_sec += dwMilliseconds / 1000L;
+ ts->tv_nsec += (dwMilliseconds % 1000L) * 1000000L;
+ ts->tv_sec += ts->tv_nsec / 1000000000L;
+ ts->tv_nsec = ts->tv_nsec % 1000000000L;
+}
+
+DWORD WaitForSingleObjectEx(HANDLE hHandle, DWORD dwMilliseconds, BOOL bAlertable)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+ WINPR_POLL_SET pollset = { 0 };
+
+ if (!winpr_Handle_GetInfo(hHandle, &Type, &Object))
+ {
+ WLog_ERR(TAG, "invalid hHandle.");
+ SetLastError(ERROR_INVALID_HANDLE);
+ return WAIT_FAILED;
+ }
+
+ if (Type == HANDLE_TYPE_PROCESS && winpr_Handle_getFd(hHandle) == -1)
+ {
+ /* note: if we have pidfd support (under linux and we have managed to associate a
+ * pidfd with our process), we use the regular method with pollset below.
+ * If not (on other platforms) we do a waitpid */
+ WINPR_PROCESS* process = (WINPR_PROCESS*)Object;
+
+ do
+ {
+ DWORD status = 0;
+ DWORD waitDelay = 0;
+ int ret = waitpid(process->pid, &(process->status), WNOHANG);
+ if (ret == process->pid)
+ {
+ process->dwExitCode = (DWORD)process->status;
+ return WAIT_OBJECT_0;
+ }
+ else if (ret < 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "waitpid failure [%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_INTERNAL_ERROR);
+ return WAIT_FAILED;
+ }
+
+ /* sleep by slices of 50ms */
+ waitDelay = (dwMilliseconds < 50) ? dwMilliseconds : 50;
+
+ status = SleepEx(waitDelay, bAlertable);
+ if (status != 0)
+ return status;
+
+ dwMilliseconds -= waitDelay;
+
+ } while (dwMilliseconds > 50);
+
+ return WAIT_TIMEOUT;
+ }
+
+ if (Type == HANDLE_TYPE_MUTEX)
+ {
+ WINPR_MUTEX* mutex = (WINPR_MUTEX*)Object;
+
+ if (dwMilliseconds != INFINITE)
+ {
+ int status = 0;
+ struct timespec timeout = { 0 };
+ clock_gettime(CLOCK_MONOTONIC, &timeout);
+ ts_add_ms(&timeout, dwMilliseconds);
+ status = pthread_mutex_timedlock(&mutex->mutex, &timeout);
+
+ if (ETIMEDOUT == status)
+ return WAIT_TIMEOUT;
+ }
+ else
+ {
+ pthread_mutex_lock(&mutex->mutex);
+ }
+
+ return WAIT_OBJECT_0;
+ }
+ else
+ {
+ int status = -1;
+ WINPR_THREAD* thread = NULL;
+ BOOL isSet = FALSE;
+ size_t extraFds = 0;
+ DWORD ret = 0;
+ BOOL autoSignaled = FALSE;
+
+ if (bAlertable)
+ {
+ thread = (WINPR_THREAD*)_GetCurrentThread();
+ if (thread)
+ {
+ /* treat reentrancy, we can't switch to alertable state when we're already
+ treating completions */
+ if (thread->apc.treatingCompletions)
+ bAlertable = FALSE;
+ else
+ extraFds = thread->apc.length;
+ }
+ else
+ {
+ /* called from a non WinPR thread */
+ bAlertable = FALSE;
+ }
+ }
+
+ int fd = winpr_Handle_getFd(Object);
+ if (fd < 0)
+ {
+ WLog_ERR(TAG, "winpr_Handle_getFd did not return a fd!");
+ SetLastError(ERROR_INVALID_HANDLE);
+ return WAIT_FAILED;
+ }
+
+ if (!pollset_init(&pollset, 1 + extraFds))
+ {
+ WLog_ERR(TAG, "unable to initialize pollset");
+ SetLastError(ERROR_INTERNAL_ERROR);
+ return WAIT_FAILED;
+ }
+
+ if (!pollset_add(&pollset, fd, Object->Mode))
+ {
+ WLog_ERR(TAG, "unable to add fd in pollset");
+ goto out;
+ }
+
+ if (bAlertable && !apc_collectFds(thread, &pollset, &autoSignaled))
+ {
+ WLog_ERR(TAG, "unable to collect APC fds");
+ goto out;
+ }
+
+ if (!autoSignaled)
+ {
+ status = pollset_poll(&pollset, dwMilliseconds);
+ if (status < 0)
+ {
+ char ebuffer[256] = { 0 };
+ WLog_ERR(TAG, "pollset_poll() failure [%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ goto out;
+ }
+ }
+
+ ret = WAIT_TIMEOUT;
+ if (bAlertable && apc_executeCompletions(thread, &pollset, 1))
+ ret = WAIT_IO_COMPLETION;
+
+ isSet = pollset_isSignaled(&pollset, 0);
+ pollset_uninit(&pollset);
+
+ if (!isSet)
+ return ret;
+
+ return winpr_Handle_cleanup(Object);
+ }
+
+out:
+ pollset_uninit(&pollset);
+ SetLastError(ERROR_INTERNAL_ERROR);
+ return WAIT_FAILED;
+}
+
+DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds)
+{
+ return WaitForSingleObjectEx(hHandle, dwMilliseconds, FALSE);
+}
+
+DWORD WaitForMultipleObjectsEx(DWORD nCount, const HANDLE* lpHandles, BOOL bWaitAll,
+ DWORD dwMilliseconds, BOOL bAlertable)
+{
+ DWORD signalled = 0;
+ DWORD polled = 0;
+ DWORD poll_map[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ BOOL signalled_handles[MAXIMUM_WAIT_OBJECTS] = { FALSE };
+ int fd = -1;
+ int status = -1;
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+ WINPR_THREAD* thread = NULL;
+ WINPR_POLL_SET pollset = { 0 };
+ DWORD ret = WAIT_FAILED;
+ size_t extraFds = 0;
+ UINT64 now = 0;
+ UINT64 dueTime = 0;
+
+ if (!nCount || (nCount > MAXIMUM_WAIT_OBJECTS))
+ {
+ WLog_ERR(TAG, "invalid handles count(%" PRIu32 ")", nCount);
+ return WAIT_FAILED;
+ }
+
+ if (bAlertable)
+ {
+ thread = winpr_GetCurrentThread();
+ if (thread)
+ {
+ /* treat reentrancy, we can't switch to alertable state when we're already
+ treating completions */
+ if (thread->apc.treatingCompletions)
+ bAlertable = FALSE;
+ else
+ extraFds = thread->apc.length;
+ }
+ else
+ {
+ /* most probably we're not called from WinPR thread, so we can't have any APC */
+ bAlertable = FALSE;
+ }
+ }
+
+ if (!pollset_init(&pollset, nCount + extraFds))
+ {
+ WLog_ERR(TAG, "unable to initialize pollset for nCount=%" PRIu32 " extraCount=%" PRIu32 "",
+ nCount, extraFds);
+ return WAIT_FAILED;
+ }
+
+ signalled = 0;
+
+ now = GetTickCount64();
+ if (dwMilliseconds != INFINITE)
+ dueTime = now + dwMilliseconds;
+ else
+ dueTime = 0xFFFFFFFFFFFFFFFF;
+
+ do
+ {
+ BOOL autoSignaled = FALSE;
+ polled = 0;
+
+ /* first collect file descriptors to poll */
+ DWORD index = 0;
+ for (; index < nCount; index++)
+ {
+ if (bWaitAll)
+ {
+ if (signalled_handles[index])
+ continue;
+
+ poll_map[polled] = index;
+ }
+
+ if (!winpr_Handle_GetInfo(lpHandles[index], &Type, &Object))
+ {
+ WLog_ERR(TAG, "invalid event file descriptor at %" PRIu32, index);
+ winpr_log_backtrace(TAG, WLOG_ERROR, 20);
+ SetLastError(ERROR_INVALID_HANDLE);
+ goto out;
+ }
+
+ fd = winpr_Handle_getFd(Object);
+ if (fd == -1)
+ {
+ WLog_ERR(TAG, "invalid file descriptor at %" PRIu32, index);
+ winpr_log_backtrace(TAG, WLOG_ERROR, 20);
+ SetLastError(ERROR_INVALID_HANDLE);
+ goto out;
+ }
+
+ if (!pollset_add(&pollset, fd, Object->Mode))
+ {
+ WLog_ERR(TAG, "unable to register fd in pollset at %" PRIu32, index);
+ winpr_log_backtrace(TAG, WLOG_ERROR, 20);
+ SetLastError(ERROR_INVALID_HANDLE);
+ goto out;
+ }
+
+ polled++;
+ }
+
+ /* treat file descriptors of the APC if needed */
+ if (bAlertable && !apc_collectFds(thread, &pollset, &autoSignaled))
+ {
+ WLog_ERR(TAG, "unable to register APC fds");
+ winpr_log_backtrace(TAG, WLOG_ERROR, 20);
+ SetLastError(ERROR_INTERNAL_ERROR);
+ goto out;
+ }
+
+ /* poll file descriptors */
+ status = 0;
+ if (!autoSignaled)
+ {
+ DWORD waitTime = 0;
+
+ if (dwMilliseconds == INFINITE)
+ waitTime = INFINITE;
+ else
+ waitTime = (DWORD)(dueTime - now);
+
+ status = pollset_poll(&pollset, waitTime);
+ if (status < 0)
+ {
+ char ebuffer[256] = { 0 };
+#ifdef WINPR_HAVE_POLL_H
+ WLog_ERR(TAG, "poll() handle %" PRIu32 " (%" PRIu32 ") failure [%d] %s", index,
+ nCount, errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+#else
+ WLog_ERR(TAG, "select() handle %" PRIu32 " (%" PRIu32 ") failure [%d] %s", index,
+ nCount, errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+#endif
+ winpr_log_backtrace(TAG, WLOG_ERROR, 20);
+ SetLastError(ERROR_INTERNAL_ERROR);
+ goto out;
+ }
+ }
+
+ /* give priority to the APC queue, to return WAIT_IO_COMPLETION */
+ if (bAlertable && apc_executeCompletions(thread, &pollset, polled))
+ {
+ ret = WAIT_IO_COMPLETION;
+ goto out;
+ }
+
+ /* then treat pollset */
+ if (status)
+ {
+ for (DWORD index = 0; index < polled; index++)
+ {
+ DWORD handlesIndex = 0;
+ BOOL signal_set = FALSE;
+
+ if (bWaitAll)
+ handlesIndex = poll_map[index];
+ else
+ handlesIndex = index;
+
+ signal_set = pollset_isSignaled(&pollset, index);
+ if (signal_set)
+ {
+ DWORD rc = winpr_Handle_cleanup(lpHandles[handlesIndex]);
+ if (rc != WAIT_OBJECT_0)
+ {
+ WLog_ERR(TAG, "error in cleanup function for handle at index=%" PRIu32,
+ handlesIndex);
+ ret = rc;
+ goto out;
+ }
+
+ if (bWaitAll)
+ {
+ signalled_handles[handlesIndex] = TRUE;
+
+ /* Continue checks from last position. */
+ for (; signalled < nCount; signalled++)
+ {
+ if (!signalled_handles[signalled])
+ break;
+ }
+ }
+ else
+ {
+ ret = (WAIT_OBJECT_0 + handlesIndex);
+ goto out;
+ }
+
+ if (signalled >= nCount)
+ {
+ ret = WAIT_OBJECT_0;
+ goto out;
+ }
+ }
+ }
+ }
+
+ if (bAlertable && thread->apc.length > extraFds)
+ {
+ pollset_uninit(&pollset);
+ extraFds = thread->apc.length;
+ if (!pollset_init(&pollset, nCount + extraFds))
+ {
+ WLog_ERR(TAG, "unable reallocate pollset");
+ SetLastError(ERROR_INTERNAL_ERROR);
+ return WAIT_FAILED;
+ }
+ }
+ else
+ pollset_reset(&pollset);
+
+ now = GetTickCount64();
+ } while (now < dueTime);
+
+ ret = WAIT_TIMEOUT;
+
+out:
+ pollset_uninit(&pollset);
+ return ret;
+}
+
+DWORD WaitForMultipleObjects(DWORD nCount, const HANDLE* lpHandles, BOOL bWaitAll,
+ DWORD dwMilliseconds)
+{
+ return WaitForMultipleObjectsEx(nCount, lpHandles, bWaitAll, dwMilliseconds, FALSE);
+}
+
+DWORD SignalObjectAndWait(HANDLE hObjectToSignal, HANDLE hObjectToWaitOn, DWORD dwMilliseconds,
+ BOOL bAlertable)
+{
+ if (!SetEvent(hObjectToSignal))
+ return WAIT_FAILED;
+
+ return WaitForSingleObjectEx(hObjectToWaitOn, dwMilliseconds, bAlertable);
+}
+
+#endif