compat: reimplement and make poll() alertable on Windows

This commit is contained in:
Rémi Denis-Courmont 2015-05-09 14:50:11 +03:00
parent 80170ba796
commit 74337dd2f2
1 changed files with 148 additions and 53 deletions

View File

@ -26,41 +26,14 @@
#include <string.h>
#include <errno.h>
#ifdef _WIN32
# ifdef FD_SETSIZE
/* Too late for #undef FD_SETSIZE to work: fd_set is already defined. */
# error Header inclusion order compromised!
# endif
# define FD_SETSIZE 0
# include <winsock2.h>
#else
#ifndef _WIN32
# include <sys/time.h>
# include <sys/select.h>
# include <fcntl.h>
#endif
int (poll) (struct pollfd *fds, unsigned nfds, int timeout)
{
#ifdef _WIN32
size_t setsize = sizeof (fd_set) + nfds * sizeof (SOCKET);
fd_set *rdset = malloc (setsize);
fd_set *wrset = malloc (setsize);
fd_set *exset = malloc (setsize);
if (rdset == NULL || wrset == NULL || exset == NULL)
{
free (rdset);
free (wrset);
free (exset);
errno = ENOMEM;
return -1;
}
/* Winsock FD_SET uses FD_SETSIZE in its expansion */
# undef FD_SETSIZE
# define FD_SETSIZE (nfds)
#else
fd_set rdset[1], wrset[1], exset[1];
#endif
struct timeval tv = { 0, 0 };
int val = -1;
@ -81,22 +54,12 @@ int (poll) (struct pollfd *fds, unsigned nfds, int timeout)
* default, such that it is quite feasible to get fd >= FD_SETSIZE.
* The next instructions will result in a buffer overflow if run on
* a POSIX system, and the later FD_ISSET would perform an undefined
* memory read.
*
* With Winsock, fd_set is a table of integers. This is awfully slow.
* However, FD_SET and FD_ISSET silently and safely discard excess
* entries. Here, overflow cannot happen anyway: fd_set of adequate
* size are allocated.
* Note that Vista has a much nicer WSAPoll(), but Mingw does not
* support it yet.
*/
#ifndef _WIN32
* memory read. */
if ((unsigned)fd >= FD_SETSIZE)
{
errno = EINVAL;
return -1;
}
#endif
if (fds[i].events & POLLRDNORM)
FD_SET (fd, rdset);
if (fds[i].events & POLLWRNORM)
@ -116,22 +79,13 @@ int (poll) (struct pollfd *fds, unsigned nfds, int timeout)
(timeout >= 0) ? &tv : NULL);
if (val == -1)
{
#ifndef _WIN32
if (errno != EBADF)
#else
if (WSAGetLastError () != WSAENOTSOCK)
#endif
return -1;
val = 0;
for (unsigned i = 0; i < nfds; i++)
#ifndef _WIN32
if (fcntl (fds[i].fd, F_GETFD) == -1)
#else
if (getsockopt (fds[i].fd, SOL_SOCKET, SO_REUSEADDR,
&(DWORD){ 0 }, &(int){ sizeof (DWORD) }) != 0)
#endif
{
fds[i].revents = POLLNVAL;
val++;
@ -149,10 +103,151 @@ int (poll) (struct pollfd *fds, unsigned nfds, int timeout)
| (FD_ISSET (fd, wrset) ? POLLWRNORM : 0)
| (FD_ISSET (fd, exset) ? POLLPRI : 0);
}
#ifdef _WIN32
free (exset);
free (wrset);
free (rdset);
#endif
return val;
}
#else
# include <windows.h>
# include <winsock2.h>
int poll(struct pollfd *fds, unsigned nfds, int timeout)
{
DWORD to = (timeout >= 0) ? (DWORD)timeout : INFINITE;
if (nfds == 0)
{ /* WSAWaitForMultipleEvents() does not allow zero events */
if (SleepEx(to, TRUE))
{
errno = EINTR;
return -1;
}
return 0;
}
WSAEVENT *evts = malloc(nfds * sizeof (WSAEVENT));
if (evts == NULL)
return -1; /* ENOMEM */
DWORD ret = WSA_WAIT_FAILED;
for (unsigned i = 0; i < nfds; i++)
{
SOCKET fd = fds[i].fd;
long mask = FD_CLOSE;
fd_set rdset, wrset, exset;
FD_ZERO(&rdset);
FD_ZERO(&wrset);
FD_ZERO(&exset);
FD_SET(fd, &exset);
if (fds[i].events & POLLRDNORM)
{
mask |= FD_READ | FD_ACCEPT;
FD_SET(fd, &rdset);
}
if (fds[i].events & POLLWRNORM)
{
mask |= FD_WRITE | FD_CONNECT;
FD_SET(fd, &wrset);
}
if (fds[i].events & POLLPRI)
mask |= FD_OOB;
fds[i].revents = 0;
evts[i] = WSACreateEvent();
if (evts[i] == WSA_INVALID_EVENT)
{
while (i > 0)
WSACloseEvent(evts[--i]);
free(evts);
errno = ENOMEM;
return -1;
}
if (WSAEventSelect(fds[i].fd, evts[i], mask)
&& WSAGetLastError() == WSAENOTSOCK)
fds[i].revents |= POLLNVAL;
struct timeval tv = { 0, 0 };
/* By its horrible design, WSAEnumNetworkEvents() only enumerates
* events that were not already signaled (i.e. it is edge-triggered).
* WSAPoll() would be better in this respect, but worse in others.
* So use WSAEnumNetworkEvents() after manually checking for pending
* events. */
if (select(0, &rdset, &wrset, &exset, &tv) > 0)
{
if (FD_ISSET(fd, &rdset))
fds[i].revents |= fds[i].events & POLLRDNORM;
if (FD_ISSET(fd, &wrset))
fds[i].revents |= fds[i].events & POLLWRNORM;
if (FD_ISSET(fd, &exset))
/* To add pain to injury, POLLERR and POLLPRI cannot be
* distinguished here. */
fds[i].revents |= POLLERR | (fds[i].events & POLLPRI);
}
if (fds[i].revents != 0 && ret == WSA_WAIT_FAILED)
ret = WSA_WAIT_EVENT_0 + i;
}
if (ret == WSA_WAIT_FAILED)
ret = WSAWaitForMultipleEvents(nfds, evts, FALSE, to, TRUE);
unsigned count = 0;
for (unsigned i = 0; i < nfds; i++)
{
WSANETWORKEVENTS ne;
if (WSAEnumNetworkEvents(fds[i].fd, evts[i], &ne))
memset(&ne, 0, sizeof (ne));
WSACloseEvent(evts[i]);
if (ne.lNetworkEvents & FD_CONNECT)
{
fds[i].revents |= POLLWRNORM;
if (ne.iErrorCode[FD_CONNECT_BIT] != 0)
fds[i].revents |= POLLERR;
}
if (ne.lNetworkEvents & FD_CLOSE)
{
fds[i].revents |= (fds[i].events & POLLRDNORM) | POLLHUP;
if (ne.iErrorCode[FD_CLOSE_BIT] != 0)
fds[i].revents |= POLLERR;
}
if (ne.lNetworkEvents & FD_ACCEPT)
{
fds[i].revents |= POLLRDNORM;
if (ne.iErrorCode[FD_ACCEPT_BIT] != 0)
fds[i].revents |= POLLERR;
}
if (ne.lNetworkEvents & FD_OOB)
{
fds[i].revents |= POLLPRI;
if (ne.iErrorCode[FD_OOB_BIT] != 0)
fds[i].revents |= POLLERR;
}
if (ne.lNetworkEvents & FD_READ)
{
fds[i].revents |= POLLRDNORM;
if (ne.iErrorCode[FD_READ_BIT] != 0)
fds[i].revents |= POLLERR;
}
if (ne.lNetworkEvents & FD_WRITE)
{
fds[i].revents |= POLLWRNORM;
if (ne.iErrorCode[FD_WRITE_BIT] != 0)
fds[i].revents |= POLLERR;
}
count += fds[i].revents != 0;
}
free(evts);
if (count == 0 && ret == WSA_WAIT_IO_COMPLETION)
{
errno = EINTR;
return -1;
}
return count;
}
#endif