235 lines
8.0 KiB
C
235 lines
8.0 KiB
C
/*
|
|
* Author: Bryan Berns <berns@uwalumni.com>
|
|
*
|
|
* Partial replacement for WaitForMultipleObjectsEx that handles more than 64
|
|
* objects. This is tuned for OpenSSH use in (no need for 'wait-all' scenarios).
|
|
* This is only safe to use for objects whose transitional state is not
|
|
* automatically lost just by calling a WaitForMultipleObjects* or
|
|
* WaitForSingleObjects*.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met :
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and / or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "signal_internal.h"
|
|
|
|
typedef struct _wait_for_multiple_objects_struct {
|
|
/* synchronization management */
|
|
HANDLE thread_handle;
|
|
HANDLE wait_event;
|
|
|
|
/* native function parameters input and output */
|
|
DWORD num_handles;
|
|
const HANDLE * handles;
|
|
DWORD return_value;
|
|
}
|
|
wait_for_multiple_objects_struct;
|
|
|
|
unsigned __stdcall
|
|
wait_for_multiple_objects_thread(LPVOID lpParam)
|
|
{
|
|
wait_for_multiple_objects_struct *waitstruct =
|
|
(wait_for_multiple_objects_struct *) lpParam;
|
|
|
|
/* wait for bin to complete -- this is alertable for our interrupt cleanup routine */
|
|
waitstruct->return_value = WaitForMultipleObjectsEx(waitstruct->num_handles,
|
|
waitstruct->handles, FALSE, INFINITE, TRUE);
|
|
|
|
/* notify the main thread that an event was found */
|
|
SetEvent(waitstruct->wait_event);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
VOID CALLBACK
|
|
wait_for_multiple_objects_interrupter(_In_ ULONG_PTR dwParam)
|
|
{
|
|
/* we must explicitly exit the thread since the thread could have been received
|
|
* the alert prior to the thread running in which case it is acknowledged when
|
|
* the threads starts running instead of when it is waiting at
|
|
* WaitForMultipleObjectsEx */
|
|
_endthreadex(0);
|
|
}
|
|
|
|
DWORD
|
|
wait_for_multiple_objects_enhanced(_In_ DWORD nCount, _In_ const HANDLE *lpHandles,
|
|
_In_ DWORD dwMilliseconds, _In_ BOOL bAlertable)
|
|
{
|
|
/* number of separate bins / threads required to monitor execution */
|
|
const DWORD bin_size = MAXIMUM_WAIT_OBJECTS;
|
|
const DWORD bins_total = (nCount - 1) / bin_size + 1;
|
|
|
|
DWORD return_value = WAIT_FAILED_ENHANCED;
|
|
HANDLE wait_event = NULL;
|
|
wait_for_multiple_objects_struct wait_bins[(MAXIMUM_WAIT_OBJECTS_ENHANCED - 1) / MAXIMUM_WAIT_OBJECTS + 1] = { 0 };
|
|
DWORD wait_ret;
|
|
|
|
/* protect against too many events */
|
|
if (nCount > MAXIMUM_WAIT_OBJECTS_ENHANCED)
|
|
{
|
|
return WAIT_FAILED_ENHANCED;
|
|
}
|
|
|
|
/* in the event that no events are passed and alterable, just do a sleep and
|
|
* and wait for wakeup call. This differs from the WaitForMultipleObjectsEx
|
|
* call which would return an error if no events are passed to the function. */
|
|
if (nCount == 0 && bAlertable)
|
|
{
|
|
DWORD wait_ret = SleepEx(dwMilliseconds, TRUE);
|
|
if (wait_ret == 0)
|
|
return WAIT_TIMEOUT_ENHANCED;
|
|
else if (wait_ret == WAIT_IO_COMPLETION)
|
|
return WAIT_IO_COMPLETION_ENHANCED;
|
|
else
|
|
return WAIT_FAILED_ENHANCED;
|
|
}
|
|
|
|
/* if less than the normal maximum then just use the built-in function
|
|
* to avoid the overhead of another thread */
|
|
if (nCount <= MAXIMUM_WAIT_OBJECTS) {
|
|
|
|
DWORD wait_ret = WaitForMultipleObjectsEx(nCount, lpHandles,
|
|
FALSE, dwMilliseconds, bAlertable);
|
|
|
|
if (wait_ret == WAIT_IO_COMPLETION) return WAIT_IO_COMPLETION_ENHANCED;
|
|
if (wait_ret == WAIT_TIMEOUT) return WAIT_TIMEOUT_ENHANCED;
|
|
|
|
/* translate normal offset to enhanced offset for abandoned threads */
|
|
if (wait_ret >= WAIT_ABANDONED_0 &&
|
|
wait_ret < WAIT_ABANDONED_0 + MAXIMUM_WAIT_OBJECTS) {
|
|
return WAIT_ABANDONED_0_ENHANCED + (wait_ret - WAIT_ABANDONED_0);
|
|
}
|
|
|
|
/* translate normal offset to enhanced offset for signaled threads */
|
|
if (wait_ret >= WAIT_OBJECT_0 &&
|
|
wait_ret < WAIT_OBJECT_0 + MAXIMUM_WAIT_OBJECTS) {
|
|
return WAIT_OBJECT_0_ENHANCED + (wait_ret - WAIT_OBJECT_0);
|
|
}
|
|
|
|
return WAIT_FAILED_ENHANCED;
|
|
}
|
|
|
|
/* setup synchronization event to flag when the main thread should wake up */
|
|
wait_event = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
if (wait_event == NULL) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/* initialize each thread that handles up to MAXIMUM_WAIT_OBJECTS each */
|
|
for (DWORD bin = 0; bin < bins_total; bin++) {
|
|
|
|
const int handles_processed = bin * bin_size;
|
|
|
|
wait_bins[bin].return_value = WAIT_FAILED - 1;
|
|
wait_bins[bin].wait_event = wait_event;
|
|
wait_bins[bin].handles = &(lpHandles[handles_processed]);
|
|
wait_bins[bin].num_handles = min(nCount - handles_processed, bin_size);
|
|
|
|
/* create a thread for this bin */
|
|
if ((wait_bins[bin].thread_handle = (HANDLE) _beginthreadex(NULL,
|
|
2048, wait_for_multiple_objects_thread,
|
|
(LPVOID) &(wait_bins[bin]), 0, NULL)) == NULL) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
/* wait for at least one thread to return; this will indicate that return
|
|
* value will have been set in our bin array */
|
|
wait_ret = WaitForSingleObjectEx(wait_event, dwMilliseconds, bAlertable);
|
|
|
|
/* if io alert just skip to end */
|
|
if (wait_ret == WAIT_IO_COMPLETION) {
|
|
return_value = WAIT_IO_COMPLETION_ENHANCED;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* if timeout just skip to end */
|
|
if (wait_ret == WAIT_TIMEOUT) {
|
|
return_value = WAIT_TIMEOUT_ENHANCED;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* unexpected output result */
|
|
if (wait_ret != WAIT_OBJECT_0) {
|
|
return_value = WAIT_FAILED_ENHANCED;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* only looking for one object events */
|
|
for (DWORD bin = 0; bin < bins_total; bin++) {
|
|
|
|
/* skip bins that have have the default, unprocessed status */
|
|
if (wait_bins[bin].return_value == WAIT_FAILED - 1)
|
|
continue;
|
|
|
|
/* return failure if a bin has been processed but returned an
|
|
* invalid or unexpected status */
|
|
if (wait_bins[bin].return_value == WAIT_FAILED ||
|
|
wait_bins[bin].return_value == WAIT_IO_COMPLETION ||
|
|
wait_bins[bin].return_value == WAIT_TIMEOUT)
|
|
{
|
|
return_value = WAIT_FAILED_ENHANCED;
|
|
break;
|
|
}
|
|
|
|
/* translate normal offset to enhanced offset for abandoned threads */
|
|
if (wait_bins[bin].return_value >= WAIT_ABANDONED_0 &&
|
|
wait_bins[bin].return_value < WAIT_ABANDONED_0 + wait_bins[bin].num_handles) {
|
|
return_value = WAIT_ABANDONED_0_ENHANCED +
|
|
bin * bin_size + (wait_bins[bin].return_value - WAIT_ABANDONED_0);
|
|
break;
|
|
}
|
|
|
|
/* translate normal offset to enhanced offset for signaled threads */
|
|
if (wait_bins[bin].return_value >= WAIT_OBJECT_0 &&
|
|
wait_bins[bin].return_value < WAIT_OBJECT_0 + wait_bins[bin].num_handles) {
|
|
return_value = WAIT_OBJECT_0_ENHANCED +
|
|
bin * bin_size + (wait_bins[bin].return_value - WAIT_OBJECT_0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
|
|
/* interrupt any outstanding threads */
|
|
for (DWORD bin = 0; bin < bins_total; bin++) {
|
|
if (wait_bins[bin].thread_handle != NULL) {
|
|
|
|
/* send each thread that is still waiting a signal to wake up;
|
|
* if the thread in not waiting and still has not fully
|
|
* finished executing then it will just ignore the signal */
|
|
if (wait_bins[bin].return_value == (WAIT_FAILED - 1)) {
|
|
QueueUserAPC(wait_for_multiple_objects_interrupter,
|
|
wait_bins[bin].thread_handle, (ULONG_PTR)NULL);
|
|
}
|
|
|
|
/* we must wait for these threads to complete so we can
|
|
* safely cleanup the shared resources */
|
|
WaitForSingleObject(wait_bins[bin].thread_handle, INFINITE);
|
|
CloseHandle(wait_bins[bin].thread_handle);
|
|
}
|
|
}
|
|
|
|
if (wait_event)
|
|
CloseHandle(wait_event);
|
|
return return_value;
|
|
}
|