| /* GLIB - Library of useful routines for C programming |
| * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald |
| * |
| * gpoll.c: poll(2) abstraction |
| * Copyright 1998 Owen Taylor |
| * Copyright 2008 Red Hat, Inc. |
| * |
| * SPDX-License-Identifier: LGPL-2.1-or-later |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| /* |
| * Modified by the GLib Team and others 1997-2000. See the AUTHORS |
| * file for a list of people on the GLib Team. See the ChangeLog |
| * files for a list of changes. These files are distributed with |
| * GLib at ftp://ftp.gtk.org/pub/gtk/. |
| */ |
| |
| /* |
| * MT safe |
| */ |
| |
| #include "config.h" |
| #include "glibconfig.h" |
| #include "giochannel.h" |
| |
| /* Uncomment the next line (and the corresponding line in gmain.c) to |
| * enable debugging printouts if the environment variable |
| * G_MAIN_POLL_DEBUG is set to some value. |
| */ |
| /* #define G_MAIN_POLL_DEBUG */ |
| |
| #ifdef _WIN32 |
| /* Always enable debugging printout on Windows, as it is more often |
| * needed there... |
| */ |
| #define G_MAIN_POLL_DEBUG |
| #endif |
| |
| #include <sys/types.h> |
| #include <time.h> |
| #include <stdlib.h> |
| #ifdef HAVE_SYS_TIME_H |
| #include <sys/time.h> |
| #endif /* HAVE_SYS_TIME_H */ |
| #ifdef HAVE_POLL |
| # include <poll.h> |
| |
| /* The poll() emulation on OS/X doesn't handle fds=NULL, nfds=0, |
| * so we prefer our own poll emulation. |
| */ |
| #if defined(_POLL_EMUL_H_) || defined(BROKEN_POLL) |
| #undef HAVE_POLL |
| #endif |
| |
| #endif /* GLIB_HAVE_SYS_POLL_H */ |
| #ifdef G_OS_UNIX |
| #include <unistd.h> |
| #endif /* G_OS_UNIX */ |
| #include <errno.h> |
| |
| #ifdef G_OS_WIN32 |
| #define STRICT |
| #include <windows.h> |
| #include <process.h> |
| #endif /* G_OS_WIN32 */ |
| |
| #include "gpoll.h" |
| |
| #ifdef G_OS_WIN32 |
| #include "gprintf.h" |
| #endif |
| |
| #ifdef G_MAIN_POLL_DEBUG |
| extern gboolean _g_main_poll_debug; |
| #endif |
| |
| #ifdef HAVE_POLL |
| |
| /** |
| * g_poll: |
| * @fds: file descriptors to poll |
| * @nfds: the number of file descriptors in @fds |
| * @timeout: amount of time to wait, in milliseconds, or -1 to wait forever |
| * |
| * Polls @fds, as with the poll() system call, but portably. (On |
| * systems that don't have poll(), it is emulated using select().) |
| * This is used internally by #GMainContext, but it can be called |
| * directly if you need to block until a file descriptor is ready, but |
| * don't want to run the full main loop. |
| * |
| * Each element of @fds is a #GPollFD describing a single file |
| * descriptor to poll. The @fd field indicates the file descriptor, |
| * and the @events field indicates the events to poll for. On return, |
| * the @revents fields will be filled with the events that actually |
| * occurred. |
| * |
| * On POSIX systems, the file descriptors in @fds can be any sort of |
| * file descriptor, but the situation is much more complicated on |
| * Windows. If you need to use g_poll() in code that has to run on |
| * Windows, the easiest solution is to construct all of your |
| * #GPollFDs with g_io_channel_win32_make_pollfd(). |
| * |
| * Returns: the number of entries in @fds whose @revents fields |
| * were filled in, or 0 if the operation timed out, or -1 on error or |
| * if the call was interrupted. |
| * |
| * Since: 2.20 |
| **/ |
| gint |
| g_poll (GPollFD *fds, |
| guint nfds, |
| gint timeout) |
| { |
| return poll ((struct pollfd *)fds, nfds, timeout); |
| } |
| |
| #else /* !HAVE_POLL */ |
| |
| #ifdef G_OS_WIN32 |
| |
| static int |
| poll_rest (GPollFD *msg_fd, |
| GPollFD *stop_fd, |
| HANDLE *handles, |
| GPollFD *handle_to_fd[], |
| gint nhandles, |
| DWORD timeout_ms) |
| { |
| DWORD ready; |
| GPollFD *f; |
| int recursed_result; |
| |
| if (msg_fd != NULL) |
| { |
| /* Wait for either messages or handles |
| * -> Use MsgWaitForMultipleObjectsEx |
| */ |
| if (_g_main_poll_debug) |
| g_print (" MsgWaitForMultipleObjectsEx(%d, %lu)\n", nhandles, timeout_ms); |
| |
| ready = MsgWaitForMultipleObjectsEx (nhandles, handles, timeout_ms, |
| QS_ALLINPUT, MWMO_ALERTABLE); |
| |
| if (ready == WAIT_FAILED) |
| { |
| gchar *emsg = g_win32_error_message (GetLastError ()); |
| g_warning ("MsgWaitForMultipleObjectsEx failed: %s", emsg); |
| g_free (emsg); |
| } |
| } |
| else if (nhandles == 0) |
| { |
| /* No handles to wait for, just the timeout */ |
| if (timeout_ms == INFINITE) |
| ready = WAIT_FAILED; |
| else |
| { |
| /* Wait for the current process to die, more efficient than SleepEx(). */ |
| WaitForSingleObjectEx (GetCurrentProcess (), timeout_ms, TRUE); |
| ready = WAIT_TIMEOUT; |
| } |
| } |
| else |
| { |
| /* Wait for just handles |
| * -> Use WaitForMultipleObjectsEx |
| */ |
| if (_g_main_poll_debug) |
| g_print (" WaitForMultipleObjectsEx(%d, %lu)\n", nhandles, timeout_ms); |
| |
| ready = WaitForMultipleObjectsEx (nhandles, handles, FALSE, timeout_ms, TRUE); |
| if (ready == WAIT_FAILED) |
| { |
| gchar *emsg = g_win32_error_message (GetLastError ()); |
| g_warning ("WaitForMultipleObjectsEx failed: %s", emsg); |
| g_free (emsg); |
| } |
| } |
| |
| if (_g_main_poll_debug) |
| g_print (" wait returns %ld%s\n", |
| ready, |
| (ready == WAIT_FAILED ? " (WAIT_FAILED)" : |
| (ready == WAIT_TIMEOUT ? " (WAIT_TIMEOUT)" : |
| (msg_fd != NULL && ready == WAIT_OBJECT_0 + nhandles ? " (msg)" : "")))); |
| |
| if (ready == WAIT_FAILED) |
| return -1; |
| else if (ready == WAIT_TIMEOUT || |
| ready == WAIT_IO_COMPLETION) |
| return 0; |
| else if (msg_fd != NULL && ready == WAIT_OBJECT_0 + nhandles) |
| { |
| msg_fd->revents |= G_IO_IN; |
| |
| /* If we have a timeout, or no handles to poll, be satisfied |
| * with just noticing we have messages waiting. |
| */ |
| if (timeout_ms != 0 || nhandles == 0) |
| return 1; |
| |
| /* If no timeout and handles to poll, recurse to poll them, |
| * too. |
| */ |
| recursed_result = poll_rest (NULL, stop_fd, handles, handle_to_fd, nhandles, 0); |
| return (recursed_result == -1) ? -1 : 1 + recursed_result; |
| } |
| else if (ready < WAIT_OBJECT_0 + nhandles) |
| { |
| int retval; |
| |
| f = handle_to_fd[ready - WAIT_OBJECT_0]; |
| f->revents = f->events; |
| if (_g_main_poll_debug) |
| g_print (" got event %p\n", (HANDLE) f->fd); |
| |
| /* Do not count the stop_fd */ |
| retval = (f != stop_fd) ? 1 : 0; |
| |
| /* If no timeout and polling several handles, recurse to poll |
| * the rest of them. |
| */ |
| if (timeout_ms == 0 && nhandles > 1) |
| { |
| /* Poll the handles with index > ready */ |
| HANDLE *shorter_handles; |
| GPollFD **shorter_handle_to_fd; |
| gint shorter_nhandles; |
| |
| shorter_handles = &handles[ready - WAIT_OBJECT_0 + 1]; |
| shorter_handle_to_fd = &handle_to_fd[ready - WAIT_OBJECT_0 + 1]; |
| shorter_nhandles = nhandles - (ready - WAIT_OBJECT_0 + 1); |
| |
| recursed_result = poll_rest (NULL, stop_fd, shorter_handles, shorter_handle_to_fd, shorter_nhandles, 0); |
| return (recursed_result == -1) ? -1 : retval + recursed_result; |
| } |
| return retval; |
| } |
| |
| return 0; |
| } |
| |
| typedef struct |
| { |
| HANDLE handles[MAXIMUM_WAIT_OBJECTS]; |
| GPollFD *handle_to_fd[MAXIMUM_WAIT_OBJECTS]; |
| GPollFD *msg_fd; |
| GPollFD *stop_fd; |
| gint nhandles; |
| DWORD timeout_ms; |
| } GWin32PollThreadData; |
| |
| static gint |
| poll_single_thread (GWin32PollThreadData *data) |
| { |
| int retval; |
| |
| /* Polling for several things? */ |
| if (data->nhandles > 1 || (data->nhandles > 0 && data->msg_fd != NULL)) |
| { |
| /* First check if one or several of them are immediately |
| * available |
| */ |
| retval = poll_rest (data->msg_fd, data->stop_fd, data->handles, data->handle_to_fd, data->nhandles, 0); |
| |
| /* If not, and we have a significant timeout, poll again with |
| * timeout then. Note that this will return indication for only |
| * one event, or only for messages. |
| */ |
| if (retval == 0 && (data->timeout_ms == INFINITE || data->timeout_ms > 0)) |
| retval = poll_rest (data->msg_fd, data->stop_fd, data->handles, data->handle_to_fd, data->nhandles, data->timeout_ms); |
| } |
| else |
| { |
| /* Just polling for one thing, so no need to check first if |
| * available immediately |
| */ |
| retval = poll_rest (data->msg_fd, data->stop_fd, data->handles, data->handle_to_fd, data->nhandles, data->timeout_ms); |
| } |
| |
| return retval; |
| } |
| |
| static void |
| fill_poll_thread_data (GPollFD *fds, |
| guint nfds, |
| DWORD timeout_ms, |
| GPollFD *stop_fd, |
| GWin32PollThreadData *data) |
| { |
| GPollFD *f; |
| |
| data->timeout_ms = timeout_ms; |
| |
| if (stop_fd != NULL) |
| { |
| if (_g_main_poll_debug) |
| g_print (" Stop FD: %p", (HANDLE) stop_fd->fd); |
| |
| g_assert (data->nhandles < MAXIMUM_WAIT_OBJECTS); |
| |
| data->stop_fd = stop_fd; |
| data->handle_to_fd[data->nhandles] = stop_fd; |
| data->handles[data->nhandles++] = (HANDLE) stop_fd->fd; |
| } |
| |
| for (f = fds; f < &fds[nfds]; ++f) |
| { |
| if ((data->nhandles == MAXIMUM_WAIT_OBJECTS) || |
| (data->msg_fd != NULL && (data->nhandles == MAXIMUM_WAIT_OBJECTS - 1))) |
| { |
| g_warning ("Too many handles to wait for!"); |
| break; |
| } |
| |
| if (f->fd == G_WIN32_MSG_HANDLE && (f->events & G_IO_IN)) |
| { |
| if (_g_main_poll_debug && data->msg_fd == NULL) |
| g_print (" MSG"); |
| data->msg_fd = f; |
| } |
| else if (f->fd > 0) |
| { |
| if (_g_main_poll_debug) |
| g_print (" %p", (HANDLE) f->fd); |
| data->handle_to_fd[data->nhandles] = f; |
| data->handles[data->nhandles++] = (HANDLE) f->fd; |
| } |
| |
| f->revents = 0; |
| } |
| } |
| |
| static guint __stdcall |
| poll_thread_run (gpointer user_data) |
| { |
| GWin32PollThreadData *data = user_data; |
| |
| /* Docs say that it is safer to call _endthreadex by our own: |
| * https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/endthread-endthreadex |
| */ |
| _endthreadex (poll_single_thread (data)); |
| |
| g_assert_not_reached (); |
| |
| return 0; |
| } |
| |
| /* One slot for a possible msg object or the stop event */ |
| #define MAXIMUM_WAIT_OBJECTS_PER_THREAD (MAXIMUM_WAIT_OBJECTS - 1) |
| |
| gint |
| g_poll (GPollFD *fds, |
| guint nfds, |
| gint timeout) |
| { |
| guint nthreads, threads_remain; |
| HANDLE thread_handles[MAXIMUM_WAIT_OBJECTS]; |
| GWin32PollThreadData *threads_data; |
| GPollFD stop_event = { 0, }; |
| GPollFD *f; |
| guint i, fds_idx = 0; |
| DWORD ready; |
| DWORD thread_retval; |
| int retval; |
| GPollFD *msg_fd = NULL; |
| |
| if (timeout == -1) |
| timeout = INFINITE; |
| |
| /* Simple case without extra threads */ |
| if (nfds <= MAXIMUM_WAIT_OBJECTS) |
| { |
| GWin32PollThreadData data = { 0, }; |
| |
| if (_g_main_poll_debug) |
| g_print ("g_poll: waiting for"); |
| |
| fill_poll_thread_data (fds, nfds, timeout, NULL, &data); |
| |
| if (_g_main_poll_debug) |
| g_print ("\n"); |
| |
| retval = poll_single_thread (&data); |
| if (retval == -1) |
| for (f = fds; f < &fds[nfds]; ++f) |
| f->revents = 0; |
| |
| return retval; |
| } |
| |
| if (_g_main_poll_debug) |
| g_print ("g_poll: polling with threads\n"); |
| |
| nthreads = nfds / MAXIMUM_WAIT_OBJECTS_PER_THREAD; |
| threads_remain = nfds % MAXIMUM_WAIT_OBJECTS_PER_THREAD; |
| if (threads_remain > 0) |
| nthreads++; |
| |
| if (nthreads > MAXIMUM_WAIT_OBJECTS_PER_THREAD) |
| { |
| g_warning ("Too many handles to wait for in threads!"); |
| nthreads = MAXIMUM_WAIT_OBJECTS_PER_THREAD; |
| } |
| |
| #if GLIB_SIZEOF_VOID_P == 8 |
| stop_event.fd = (gint64)CreateEventW (NULL, TRUE, FALSE, NULL); |
| #else |
| stop_event.fd = (gint)CreateEventW (NULL, TRUE, FALSE, NULL); |
| #endif |
| stop_event.events = G_IO_IN; |
| |
| threads_data = g_new0 (GWin32PollThreadData, nthreads); |
| for (i = 0; i < nthreads; i++) |
| { |
| guint thread_fds; |
| guint ignore; |
| |
| if (i == (nthreads - 1) && threads_remain > 0) |
| thread_fds = threads_remain; |
| else |
| thread_fds = MAXIMUM_WAIT_OBJECTS_PER_THREAD; |
| |
| fill_poll_thread_data (fds + fds_idx, thread_fds, timeout, &stop_event, &threads_data[i]); |
| fds_idx += thread_fds; |
| |
| /* We must poll for messages from the same thread, so poll it along with the threads */ |
| if (threads_data[i].msg_fd != NULL) |
| { |
| msg_fd = threads_data[i].msg_fd; |
| threads_data[i].msg_fd = NULL; |
| } |
| |
| thread_handles[i] = (HANDLE) _beginthreadex (NULL, 0, poll_thread_run, &threads_data[i], 0, &ignore); |
| } |
| |
| /* Wait for at least one thread to return */ |
| if (msg_fd != NULL) |
| ready = MsgWaitForMultipleObjectsEx (nthreads, thread_handles, timeout, |
| QS_ALLINPUT, MWMO_ALERTABLE); |
| else |
| ready = WaitForMultipleObjects (nthreads, thread_handles, FALSE, timeout); |
| |
| /* Signal the stop in case any of the threads did not stop yet */ |
| if (!SetEvent ((HANDLE)stop_event.fd)) |
| { |
| gchar *emsg = g_win32_error_message (GetLastError ()); |
| g_warning ("gpoll: failed to signal the stop event: %s", emsg); |
| g_free (emsg); |
| } |
| |
| /* Wait for the rest of the threads to finish */ |
| WaitForMultipleObjects (nthreads, thread_handles, TRUE, INFINITE); |
| |
| /* The return value of all the threads give us all the fds that changed state */ |
| retval = 0; |
| if (msg_fd != NULL && ready == WAIT_OBJECT_0 + nthreads) |
| { |
| msg_fd->revents |= G_IO_IN; |
| retval = 1; |
| } |
| |
| for (i = 0; i < nthreads; i++) |
| { |
| if (GetExitCodeThread (thread_handles[i], &thread_retval)) |
| retval = (retval == -1) ? -1 : ((thread_retval == (DWORD) -1) ? -1 : (int) (retval + thread_retval)); |
| |
| CloseHandle (thread_handles[i]); |
| } |
| |
| if (retval == -1) |
| for (f = fds; f < &fds[nfds]; ++f) |
| f->revents = 0; |
| |
| g_free (threads_data); |
| CloseHandle ((HANDLE)stop_event.fd); |
| |
| return retval; |
| } |
| |
| #else /* !G_OS_WIN32 */ |
| |
| /* The following implementation of poll() comes from the GNU C Library. |
| * Copyright (C) 1994, 1996, 1997 Free Software Foundation, Inc. |
| */ |
| |
| #include <string.h> /* for bzero on BSD systems */ |
| |
| #ifdef HAVE_SYS_SELECT_H |
| #include <sys/select.h> |
| #endif /* HAVE_SYS_SELECT_H */ |
| |
| gint |
| g_poll (GPollFD *fds, |
| guint nfds, |
| gint timeout) |
| { |
| struct timeval tv; |
| fd_set rset, wset, xset; |
| GPollFD *f; |
| int ready; |
| int maxfd = 0; |
| |
| FD_ZERO (&rset); |
| FD_ZERO (&wset); |
| FD_ZERO (&xset); |
| |
| for (f = fds; f < &fds[nfds]; ++f) |
| if (f->fd >= 0) |
| { |
| if (f->events & G_IO_IN) |
| FD_SET (f->fd, &rset); |
| if (f->events & G_IO_OUT) |
| FD_SET (f->fd, &wset); |
| if (f->events & G_IO_PRI) |
| FD_SET (f->fd, &xset); |
| if (f->fd > maxfd && (f->events & (G_IO_IN|G_IO_OUT|G_IO_PRI))) |
| maxfd = f->fd; |
| } |
| |
| tv.tv_sec = timeout / 1000; |
| tv.tv_usec = (timeout % 1000) * 1000; |
| |
| ready = select (maxfd + 1, &rset, &wset, &xset, |
| timeout == -1 ? NULL : &tv); |
| if (ready > 0) |
| for (f = fds; f < &fds[nfds]; ++f) |
| { |
| f->revents = 0; |
| if (f->fd >= 0) |
| { |
| if (FD_ISSET (f->fd, &rset)) |
| f->revents |= G_IO_IN; |
| if (FD_ISSET (f->fd, &wset)) |
| f->revents |= G_IO_OUT; |
| if (FD_ISSET (f->fd, &xset)) |
| f->revents |= G_IO_PRI; |
| } |
| } |
| |
| return ready; |
| } |
| |
| #endif /* !G_OS_WIN32 */ |
| |
| #endif /* !HAVE_POLL */ |