diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
commit | a9bcc81f821d7c66f623779fa5147e728eb3c388 (patch) | |
tree | 98676963bcdd537ae5908a067a8eb110b93486a6 /winpr/libwinpr/synch | |
parent | Initial commit. (diff) | |
download | freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip |
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'winpr/libwinpr/synch')
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 |