On Tue, Jan 28, 2020 at 01:11:14PM +0000, Daniel P. Berrangé wrote:
> We need to be able to create event loop watches using the
> GSource API for sockets. GIOChannel is able todo this, but
> we don't want to use the GIOChannel APIs for reading/writing,
> and testing shows just using its GSource APIs is unreliable
> on Windows.
>
> This patch thus creates a standalone helper API for creating
> a GSource for a socket file descriptor. This impl is derived
> from code in QEMU's io/channel-watch.c file that was written
> by myself & Paolo Bonzini & thus under Red Hat copyright.
>
> Signed-off-by: Daniel P. Berrangé <berrange(a)redhat.com>
> ---
> build-aux/syntax-check.mk | 3 +
> src/util/Makefile.inc.am | 2 +
> src/util/vireventglibwatch.c | 248 +++++++++++++++++++++++++++++++++++
> src/util/vireventglibwatch.h | 48 +++++++
> 4 files changed, 301 insertions(+)
> create mode 100644 src/util/vireventglibwatch.c
> create mode 100644 src/util/vireventglibwatch.h
>
> diff --git a/src/util/vireventglibwatch.c b/src/util/vireventglibwatch.c
> new file mode 100644
> index 0000000000..f7b087e2ec
> --- /dev/null
> +++ b/src/util/vireventglibwatch.c
> @@ -0,0 +1,248 @@
> +/*
> + * vireventglibwatch.c: GSource impl for sockets
> + *
> + * Copyright (C) 2015-2020 Red Hat, Inc.
> + *
> + * 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/>.
> + */
> +
> +#include <config.h>
> +
> +#include "vireventglibwatch.h"
> +
> +#ifndef WIN32
> +typedef struct virEventGLibFDSource virEventGLibFDSource;
> +struct virEventGLibFDSource {
> + GSource parent;
> + GPollFD pollfd;
> + int fd;
> + GIOCondition condition;
> +};
> +
> +
> +static gboolean
> +virEventGLibFDSourcePrepare(GSource *source G_GNUC_UNUSED,
> + gint *timeout)
> +{
> + *timeout = -1;
> +
> + return FALSE;
> +}
> +
> +
> +static gboolean
> +virEventGLibFDSourceCheck(GSource *source)
> +{
> + virEventGLibFDSource *ssource = (virEventGLibFDSource *)source;
> +
> + return ssource->pollfd.revents & ssource->condition;
> +}
> +
> +
> +static gboolean
> +virEventGLibFDSourceDispatch(GSource *source,
> + GSourceFunc callback,
> + gpointer user_data)
> +{
> + virEventGLibSocketFunc func = (virEventGLibSocketFunc)callback;
> + virEventGLibFDSource *ssource = (virEventGLibFDSource *)source;
> +
> + return (*func)(ssource->fd,
> + ssource->pollfd.revents & ssource->condition,
> + user_data);
> +}
> +
> +
> +static void
> +virEventGLibFDSourceFinalize(GSource *source G_GNUC_UNUSED)
> +{
> +}
> +
> +
> +GSourceFuncs virEventGLibFDSourceFuncs = {
> + .prepare = virEventGLibFDSourcePrepare,
> + .check = virEventGLibFDSourceCheck,
> + .dispatch = virEventGLibFDSourceDispatch,
> + .finalize = virEventGLibFDSourceFinalize
> +};
> +
> +
> +GSource *virEventGLibCreateSocketWatch(int fd,
> + GIOCondition condition)
> +{
> + GSource *source;
> + virEventGLibFDSource *ssource;
> +
> + source = g_source_new(&virEventGLibFDSourceFuncs,
> + sizeof(virEventGLibFDSource));
> + ssource = (virEventGLibFDSource *)source;
> +
> + ssource->condition = condition;
> + ssource->fd = fd;
> +
> + ssource->pollfd.fd = fd;
> + ssource->pollfd.events = condition;
> +
> + g_source_add_poll(source, &ssource->pollfd);
> +
> + return source;
> +}
> +
> +#else /* WIN32 */
> +
> +# define WIN32_LEAN_AND_MEAN
> +# include <winsock2.h>
> +
> +typedef struct virEventGLibSocketSource virEventGLibSocketSource;
> +struct virEventGLibSocketSource {
> + GSource parent;
> + GPollFD pollfd;
> + int fd;
> + SOCKET socket;
> + HANDLE event;
> + int revents;
> + GIOCondition condition;
> +};
> +
> +
> +static gboolean
> +virEventGLibSocketSourcePrepare(GSource *source G_GNUC_UNUSED,
> + gint *timeout)
> +{
> + *timeout = -1;
> +
> + return FALSE;
> +}
> +
> +
> +/*
> + * NB, this impl only works when the socket is in non-blocking
> + * mode on Win32
> + */
> +static gboolean
> +virEventGLibSocketSourceCheck(GSource *source)
> +{
> + static struct timeval tv0;
> +
> + virEventGLibSocketSource *ssource = (virEventGLibSocketSource *)source;
> + WSANETWORKEVENTS ev;
> + fd_set rfds, wfds, xfds;
> +
> + if (!ssource->condition)
> + return 0;
> +
> + WSAEnumNetworkEvents(ssource->socket, ssource->event, &ev);
> +
> + FD_ZERO(&rfds);
> + FD_ZERO(&wfds);
> + FD_ZERO(&xfds);
> + if (ssource->condition & G_IO_IN)
> + FD_SET(ssource->socket, &rfds);
> + if (ssource->condition & G_IO_OUT)
> + FD_SET(ssource->socket, &wfds);
> + if (ssource->condition & G_IO_PRI)
> + FD_SET(ssource->socket, &xfds);
> +
> + ssource->revents = 0;
> + if (select(0, &rfds, &wfds, &xfds, &tv0) == 0)
> + return 0;
> +
> + if (FD_ISSET(ssource->socket, &rfds))
> + ssource->revents |= G_IO_IN;
> +
> + if (FD_ISSET(ssource->socket, &wfds))
> + ssource->revents |= G_IO_OUT;
> +
> + if (FD_ISSET(ssource->socket, &xfds))
> + ssource->revents |= G_IO_PRI;
> +
> + return ssource->revents;
> +}
> +
> +
> +static gboolean
> +virEventGLibSocketSourceDispatch(GSource *source,
> + GSourceFunc callback,
> + gpointer user_data)
> +{
> + virEventGLibSocketFunc func = (virEventGLibSocketFunc)callback;
> + virEventGLibSocketSource *ssource = (virEventGLibSocketSource *)source;
> +
> + return (*func)(ssource->fd, ssource->revents, user_data);
> +}
> +
> +
> +static void
> +virEventGLibSocketSourceFinalize(GSource *source)
> +{
> + virEventGLibSocketSource *ssource = (virEventGLibSocketSource *)source;
> +
> + WSAEventSelect(ssource->socket, NULL, 0);
> +}
> +
> +
> +GSourceFuncs virEventGLibSocketSourceFuncs = {
> + .prepare = virEventGLibSocketSourcePrepare,
> + .check = virEventGLibSocketSourceCheck,
> + .dispatch = virEventGLibSocketSourceDispatch,
> + .finalize = virEventGLibSocketSourceFinalize
> +};
> +
> +
> +GSource *virEventGLibCreateSocketWatch(int fd,
> + GIOCondition condition)
> +{
> + GSource *source;
> + virEventGLibSocketSource *ssource;
> +
> + source = g_source_new(&virEventGLibSocketSourceFuncs,
> + sizeof(virEventGLibSocketSource));
> + ssource = (virEventGLibSocketSource *)source;
> +
> + ssource->condition = condition;
> + ssource->fd = fd;
> + ssource->socket = _get_osfhandle(fd);
> + ssource->event = CreateEvent(NULL, FALSE, FALSE, NULL);
Based on going through QEMU code and windows documentation it looks
like we have to free the event using CloseHandle().
QEMU is doing it in io/channel.c in qio_channel_finalize() which is
probably called by unrefing ssource->ioc in
qio_channel_fd_pair_source_finalize().
That would mean we have to call CloseHandle() in
virEventGLibSocketSourceFinalize() to make sure we will not leak any
resources.