The attached patch finally provides the native implementation of poll for Win32.
Testing under native Windows is definitely needed for this, because Wine has a big tendency to crash on the test: 1) test_tty fails under Wine (so I don't actually know if it works, but the code was taken from GNU Smalltalk and it should be ok). 2) Wine crashes on test_pipe if test_tty is enabled or if it is not placed first. 3) the last assertion in test_pipe crashes Wine. Nevertheless, I was able to obtain good coverage under Wine, which makes me quite positive that the patch works. In addition, the getsockopt(socket, SOL_SOCKET, SO_TYPE, ...) trick to distinguish pipes and sockets does not work under Wine. Instead I used WSAEnumNetworkEvents which works (and there is no reason why it should not under Windows). I also modified the code to *not* use the Winsock select at all (again using WSAEnumNetworkEvents). This is more precise for passive sockets (this could be the problem Yoann reported on the old implementation) and it allows to detect hangups more precisely too via the FD_CLOSE event. Paolo
commit 2837cb39539acec18af3e7b42743eb3ceb826f94 Author: Paolo Bonzini <[EMAIL PROTECTED]> Date: Fri Aug 29 09:12:11 2008 +0200 implement a native version of poll for Win32 2008-09-12 Paolo Bonzini <[EMAIL PROTECTED]> * lib/poll.c: Rewrite. * modules/poll: Depend on alloca. * test/test-poll.c: Add notes on Wine compatibility. diff --git a/ChangeLog b/ChangeLog index d6f5c13..c89123f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,11 @@ 2008-09-12 Paolo Bonzini <[EMAIL PROTECTED]> + * lib/poll.c: Rewrite. + * modules/poll: Depend on alloca. + * test/test-poll.c: Add notes on Wine compatibility. + +2008-09-12 Paolo Bonzini <[EMAIL PROTECTED]> + * lib/sys_socket.in.h: For Win32, map WinSock error codes so that those overlapping with MSVCRT error codes coincide. Do not implement rpl_setsockopt here, instead define prototypes for diff --git a/lib/poll.c b/lib/poll.c index e0714f0..24af782 100644 --- a/lib/poll.c +++ b/lib/poll.c @@ -20,14 +20,25 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <config.h> +#include <alloca.h> #include <sys/types.h> #include "poll.h" #include <errno.h> #include <limits.h> +#include <assert.h> + +#ifdef __MSVCRT__ +#include <windows.h> +#include <winsock2.h> +#include <io.h> +#include <stdio.h> +#include <conio.h> +#else #include <sys/socket.h> #include <sys/select.h> #include <unistd.h> +#endif #ifdef HAVE_SYS_IOCTL_H #include <sys/ioctl.h> @@ -48,12 +59,228 @@ #define MSG_PEEK 0 #endif +#ifdef __MSVCRT__ + +/* Declare data structures for ntdll functions. */ +typedef struct _FILE_PIPE_LOCAL_INFORMATION { + ULONG NamedPipeType; + ULONG NamedPipeConfiguration; + ULONG MaximumInstances; + ULONG CurrentInstances; + ULONG InboundQuota; + ULONG ReadDataAvailable; + ULONG OutboundQuota; + ULONG WriteQuotaAvailable; + ULONG NamedPipeState; + ULONG NamedPipeEnd; +} FILE_PIPE_LOCAL_INFORMATION, *PFILE_PIPE_LOCAL_INFORMATION; + +typedef struct _IO_STATUS_BLOCK +{ + union { + DWORD Status; + PVOID Pointer; + } u; + ULONG_PTR Information; +} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; + +typedef enum _FILE_INFORMATION_CLASS { + FilePipeLocalInformation = 24 +} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS; + +typedef DWORD (WINAPI *PNtQueryInformationFile) + (HANDLE, IO_STATUS_BLOCK *, VOID *, ULONG, FILE_INFORMATION_CLASS); + +#ifndef PIPE_BUF +#define PIPE_BUF 512 +#endif + +/* Compute revents values for file handle H. */ + +static int +win32_compute_revents (HANDLE h, int sought) +{ + int i, ret, happened; + INPUT_RECORD *irbuffer; + DWORD avail, nbuffer; + BOOL bRet; + IO_STATUS_BLOCK iosb; + FILE_PIPE_LOCAL_INFORMATION fpli; + static PNtQueryInformationFile NtQueryInformationFile; + static BOOL once_only; + + switch (GetFileType (h)) + { + case FILE_TYPE_PIPE: + if (!once_only) + { + NtQueryInformationFile = (PNtQueryInformationFile) + GetProcAddress (GetModuleHandle ("ntdll.dll"), + "NtQueryInformationFile"); + once_only = TRUE; + } + + happened = 0; + if (PeekNamedPipe (h, NULL, 0, NULL, &avail, NULL) != 0) + { + if (avail) + happened |= sought & (POLLIN | POLLRDNORM); + } + + else + { + /* It was the write-end of the pipe. Check if it is writable. + If NtQueryInformationFile fails, optimistically assume the pipe is + writable. This could happen on Win9x, where NtQueryInformationFile + is not available, or if we inherit a pipe that doesn't permit + FILE_READ_ATTRIBUTES access on the write end (I think this should + not happen since WinXP SP2; WINE seems fine too). Otherwise, + ensure that enough space is available for atomic writes. */ + memset (&iosb, 0, sizeof (iosb)); + memset (&fpli, 0, sizeof (fpli)); + + if (!NtQueryInformationFile + || NtQueryInformationFile (h, &iosb, &fpli, sizeof (fpli), + FilePipeLocalInformation) + || fpli.WriteQuotaAvailable >= PIPE_BUF + || (fpli.OutboundQuota < PIPE_BUF && + fpli.WriteQuotaAvailable == fpli.OutboundQuota)) + happened |= sought & (POLLOUT | POLLWRNORM | POLLWRBAND); + } + return happened; + + case FILE_TYPE_CHAR: + ret = WaitForSingleObject (h, 0); + if (ret == WAIT_OBJECT_0) + { + nbuffer = avail = 0; + bRet = GetNumberOfConsoleInputEvents (h, &nbuffer); + if (!bRet || nbuffer == 0) + return POLLHUP; + + irbuffer = (INPUT_RECORD *) alloca (nbuffer * sizeof (INPUT_RECORD)); + bRet = PeekConsoleInput (h, irbuffer, nbuffer, &avail); + if (!bRet || avail == 0) + return POLLHUP; + + for (i = 0; i < avail; i++) + if (irbuffer[i].EventType == KEY_EVENT) + return sought & ~(POLLPRI | POLLRDBAND); + } + break; + + default: + ret = WaitForSingleObject (h, 0); + if (ret == WAIT_OBJECT_0) + return sought & ~(POLLPRI | POLLRDBAND); + + break; + } + + return sought & (POLLOUT | POLLWRNORM | POLLWRBAND); +} + +/* Convert fd_sets returned by select into revents values. */ + +static int +win32_compute_revents_socket (SOCKET h, int sought, long lNetworkEvents) +{ + int happened = 0; + + if (lNetworkEvents & FD_ACCEPT) + happened |= (POLLIN | POLLRDNORM) & sought; + + else if (lNetworkEvents & (FD_READ | FD_CLOSE)) + { + int r, error; + + char data[64]; + WSASetLastError (0); + r = recv (h, data, sizeof (data), MSG_PEEK); + error = WSAGetLastError (); + WSASetLastError (0); + + if (r > 0) + happened |= (POLLIN | POLLRDNORM) & sought; + + /* Distinguish hung-up sockets from other errors. */ + else if (r == 0 || error == WSAESHUTDOWN || error == WSAECONNRESET + || error == WSAECONNABORTED || error == WSAENETRESET) + happened |= POLLHUP; + + else if (error != WSAENOTCONN) + happened |= POLLERR; + } + + if (lNetworkEvents & (FD_WRITE | FD_CONNECT)) + happened |= (POLLOUT | POLLWRNORM | POLLWRBAND) & sought; + + if (lNetworkEvents & FD_OOB) + happened |= (POLLPRI | POLLRDBAND) & sought; + + return happened; +} + +#else /* !MinGW */ + +/* Convert select(2) returned fd_sets into poll(2) revents values. */ +static int +compute_revents (int fd, int sought, fd_set *rfds, fd_set *wfds, fd_set *efds) +{ + int happened = 0; + if (FD_ISSET (fd, rfds)) + { + int r; + int socket_errno; + +#if defined __MACH__ && defined __APPLE__ + /* There is a bug in Mac OS X that causes it to ignore MSG_PEEK + for some kinds of descriptors. Detect if this descriptor is a + connected socket, a server socket, or something else using a + 0-byte recv, and use ioctl(2) to detect POLLHUP. */ + r = recv (fd, NULL, 0, MSG_PEEK); + socket_errno = (r < 0) ? errno : 0; + if (r == 0 || socket_errno == ENOTSOCK) + ioctl (fd, FIONREAD, &r); +#else + char data[64]; + r = recv (fd, data, sizeof (data), MSG_PEEK); + socket_errno = (r < 0) ? errno : 0; +#endif + if (r == 0) + happened |= POLLHUP; + + /* If the event happened on an unconnected server socket, + that's fine. */ + else if (r > 0 || ( /* (r == -1) && */ socket_errno == ENOTCONN)) + happened |= (POLLIN | POLLRDNORM) & sought; + + /* Distinguish hung-up sockets from other errors. */ + else if (socket_errno == ESHUTDOWN || socket_errno == ECONNRESET + || socket_errno == ECONNABORTED || socket_errno == ENETRESET) + happened |= POLLHUP; + + else + happened |= POLLERR; + } + + if (FD_ISSET (fd, wfds)) + happened |= (POLLOUT | POLLWRNORM | POLLWRBAND) & sought; + + if (FD_ISSET (fd, efds)) + happened |= (POLLPRI | POLLRDBAND) & sought; + + return happened; +} +#endif /* !MinGW */ + int poll (pfd, nfd, timeout) struct pollfd *pfd; nfds_t nfd; int timeout; { +#ifndef __MSVCRT__ fd_set rfds, wfds, efds; struct timeval tv; struct timeval *ptv; @@ -137,16 +364,11 @@ poll (pfd, nfd, timeout) | POLLWRNORM | POLLWRBAND))) { maxfd = pfd[i].fd; - - /* Windows use a linear array of sockets (of size FD_SETSIZE). The - descriptor value is not used to address the array. */ -#if defined __CYGWIN__ || (!defined _WIN32 && !defined __WIN32__) if (maxfd > FD_SETSIZE) { errno = EOVERFLOW; return -1; } -#endif } } @@ -162,61 +384,142 @@ poll (pfd, nfd, timeout) pfd[i].revents = 0; else { - int happened = 0, sought = pfd[i].events; - if (FD_ISSET (pfd[i].fd, &rfds)) + int happened = compute_revents (pfd[i].fd, pfd[i].events, + &rfds, &wfds, &efds); + if (happened) { - int r; - int socket_errno; + pfd[i].revents = happened; + rc++; + } + } -#if defined __MACH__ && defined __APPLE__ - /* There is a bug in Mac OS X that causes it to ignore MSG_PEEK - for some kinds of descriptors. Detect if this descriptor is a - connected socket, a server socket, or something else using a - 0-byte recv, and use ioctl(2) to detect POLLHUP. */ - r = recv (pfd[i].fd, NULL, 0, MSG_PEEK); - socket_errno = (r < 0) ? errno : 0; - if (r == 0 || socket_errno == ENOTSOCK) - ioctl(pfd[i].fd, FIONREAD, &r); + return rc; #else - char data[64]; - r = recv (pfd[i].fd, data, sizeof (data), MSG_PEEK); - -# ifdef WIN32 - if (r < 0 && GetLastError() == 10057) /* server socket */ - socket_errno = ENOTCONN; - else -# endif - socket_errno = (r < 0) ? errno : 0; -#endif - if (r == 0) - happened |= POLLHUP; + static struct timeval tv0; + struct timeval tv = { 0, 0 }; + struct timeval *ptv; + static HANDLE hEvent; + WSANETWORKEVENTS ev; + HANDLE h, handle_array[FD_SETSIZE + 2]; + DWORD ret, wait_timeout, nhandles; + int nsock; + BOOL bRet; + MSG msg; + char sockbuf[256]; + int rc; + nfds_t i; - /* If the event happened on an unconnected server socket, - that's fine. */ - else if (r > 0 || ( /* (r == -1) && */ socket_errno == ENOTCONN)) - happened |= (POLLIN | POLLRDNORM) & sought; + if (nfd < 0 || timeout < -1) + { + errno = EINVAL; + return -1; + } - /* Distinguish hung-up sockets from other errors. */ - else if (socket_errno == ESHUTDOWN || socket_errno == ECONNRESET - || socket_errno == ECONNABORTED || socket_errno == ENETRESET) - happened |= POLLHUP; + if (!hEvent) + hEvent = CreateEvent (NULL, FALSE, FALSE, NULL); - else - happened |= POLLERR; - } + handle_array[0] = hEvent; + nhandles = 1; + nsock = 0; - if (FD_ISSET (pfd[i].fd, &wfds)) - happened |= (POLLOUT | POLLWRNORM | POLLWRBAND) & sought; + /* Classify socket handles and create fd sets. */ + for (i = 0; i < nfd; i++) + { + size_t optlen = sizeof(sockbuf); + if (pfd[i].fd < 0) + continue; - if (FD_ISSET (pfd[i].fd, &efds)) - happened |= (POLLPRI | POLLRDBAND) & sought; + h = (HANDLE) _get_osfhandle (pfd[i].fd); + assert (h != NULL); - if (happened) - { - pfd[i].revents = happened; - rc++; - } - } + /* Under Wine, it seems that getsockopt returns 0 for pipes too. + WSAEnumNetworkEvents instead distinguishes the two correctly. */ + ev.lNetworkEvents = 0xDEADBEEF; + WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev); + if (ev.lNetworkEvents != 0xDEADBEEF) + { + int requested = FD_CLOSE; + + /* see above; socket handles are mapped onto select. */ + if (pfd[i].events & (POLLIN | POLLRDNORM)) + requested |= FD_READ | FD_ACCEPT; + if (pfd[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND)) + requested |= FD_WRITE | FD_CONNECT; + if (pfd[i].events & (POLLPRI | POLLRDBAND)) + requested |= FD_OOB; + if (requested) + { + WSAEventSelect ((SOCKET) h, hEvent, requested); + nsock++; + } + } + else + { + if (pfd[i].events & (POLLIN | POLLRDNORM | + POLLOUT | POLLWRNORM | POLLWRBAND)) + handle_array[nhandles++] = h; + } + } + + if (timeout == INFTIM) + wait_timeout = INFINITE; + else + wait_timeout = timeout; + + for (;;) + { + ret = MsgWaitForMultipleObjects (nhandles, handle_array, FALSE, + wait_timeout, QS_ALLINPUT); + + if (ret == WAIT_OBJECT_0 + nhandles) + { + /* new input of some other kind */ + while ((bRet = PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) != 0) + { + TranslateMessage (&msg); + DispatchMessage (&msg); + } + } + else + break; + } + + /* Place a sentinel at the end of the array. */ + handle_array[nhandles] = NULL; + nhandles = 1; + for (i = 0; i < nfd; i++) + { + int happened; + + if (pfd[i].fd < 0) + { + pfd[i].revents = 0; + continue; + } + + h = (HANDLE) _get_osfhandle (pfd[i].fd); + if (h != handle_array[nhandles]) + { + /* It's a socket. */ + WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev); + WSAEventSelect ((SOCKET) h, 0, 0); + happened = win32_compute_revents_socket ((SOCKET) h, pfd[i].events, + ev.lNetworkEvents); + } + else + { + /* Not a socket. */ + nhandles++; + happened = win32_compute_revents (h, pfd[i].events); + } + + if (happened) + { + pfd[i].revents = happened; + rc++; + } + } return rc; +#endif } diff --git a/modules/poll b/modules/poll index 3ac7c8c..dab66d4 100644 --- a/modules/poll +++ b/modules/poll @@ -7,6 +7,7 @@ lib/poll.in.h m4/poll.m4 Depends-on: +alloca sys_select sys_time EOVERFLOW diff --git a/tests/test-poll.c b/tests/test-poll.c index d339117..51de360 100644 --- a/tests/test-poll.c +++ b/tests/test-poll.c @@ -344,6 +344,8 @@ test_pipe (void) pipe (fd); test_pair (fd[0], fd[1]); + + /* Wine crashes on the following test. */ close (fd[0]); if ((poll1_wait (fd[1], POLLIN | POLLOUT) & (POLLHUP | POLLERR)) == 0) failed ("expecting POLLHUP after shutdown"); @@ -366,10 +368,11 @@ main () test (test_tty, "TTY"); #endif - result = test (test_connect_first, "Unconnected socket test"); - result += test (test_socket_pair, "Connected sockets test"); + /* Note: Wine crashes if you change the order of the tests. */ + result = test (test_pipe, "Pipe test"); + result += test (test_connect_first, "Unconnected socket test"); result += test (test_accept_first, "General socket test with fork"); - result += test (test_pipe, "Pipe test"); + result += test (test_socket_pair, "Connected sockets test"); exit (result); }