598 lines
16 KiB
C
598 lines
16 KiB
C
/* Emulation for select(2)
|
|
Contributed by Paolo Bonzini.
|
|
|
|
Copyright 2008-2022 Free Software Foundation, Inc.
|
|
|
|
This file is part of gnulib.
|
|
|
|
This file 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 file 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 program. If not, see <https://www.gnu.org/licenses/>. */
|
|
|
|
#include <config.h>
|
|
|
|
/* Specification. */
|
|
#include <sys/select.h>
|
|
|
|
#if defined _WIN32 && ! defined __CYGWIN__
|
|
/* Native Windows. */
|
|
|
|
#include <alloca.h>
|
|
#include <assert.h>
|
|
#include <sys/types.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
|
|
#include <winsock2.h>
|
|
#include <windows.h>
|
|
#include <io.h>
|
|
#include <stdio.h>
|
|
#include <conio.h>
|
|
#include <time.h>
|
|
|
|
/* Get the overridden 'struct timeval'. */
|
|
#include <sys/time.h>
|
|
|
|
#if GNULIB_MSVC_NOTHROW
|
|
# include "msvc-nothrow.h"
|
|
#else
|
|
# include <io.h>
|
|
#endif
|
|
|
|
#undef select
|
|
|
|
/* Don't assume that UNICODE is not defined. */
|
|
#undef GetModuleHandle
|
|
#define GetModuleHandle GetModuleHandleA
|
|
#undef PeekConsoleInput
|
|
#define PeekConsoleInput PeekConsoleInputA
|
|
#undef CreateEvent
|
|
#define CreateEvent CreateEventA
|
|
#undef PeekMessage
|
|
#define PeekMessage PeekMessageA
|
|
#undef DispatchMessage
|
|
#define DispatchMessage DispatchMessageA
|
|
|
|
/* Avoid warnings from gcc -Wcast-function-type. */
|
|
#define GetProcAddress \
|
|
(void *) GetProcAddress
|
|
|
|
struct bitset {
|
|
unsigned char in[FD_SETSIZE / CHAR_BIT];
|
|
unsigned char out[FD_SETSIZE / CHAR_BIT];
|
|
};
|
|
|
|
/* 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
|
|
|
|
static BOOL IsConsoleHandle (HANDLE h)
|
|
{
|
|
DWORD mode;
|
|
return GetConsoleMode (h, &mode) != 0;
|
|
}
|
|
|
|
static BOOL
|
|
IsSocketHandle (HANDLE h)
|
|
{
|
|
WSANETWORKEVENTS ev;
|
|
|
|
if (IsConsoleHandle (h))
|
|
return FALSE;
|
|
|
|
/* 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);
|
|
return ev.lNetworkEvents != 0xDEADBEEF;
|
|
}
|
|
|
|
/* Compute output fd_sets for libc descriptor FD (whose Windows handle is
|
|
H). */
|
|
|
|
static int
|
|
windows_poll_handle (HANDLE h, int fd,
|
|
struct bitset *rbits,
|
|
struct bitset *wbits,
|
|
struct bitset *xbits)
|
|
{
|
|
BOOL read, write, except;
|
|
int i, ret;
|
|
INPUT_RECORD *irbuffer;
|
|
DWORD avail, nbuffer;
|
|
BOOL bRet;
|
|
IO_STATUS_BLOCK iosb;
|
|
FILE_PIPE_LOCAL_INFORMATION fpli;
|
|
static PNtQueryInformationFile NtQueryInformationFile;
|
|
static BOOL once_only;
|
|
|
|
read = write = except = FALSE;
|
|
switch (GetFileType (h))
|
|
{
|
|
case FILE_TYPE_DISK:
|
|
read = TRUE;
|
|
write = TRUE;
|
|
break;
|
|
|
|
case FILE_TYPE_PIPE:
|
|
if (!once_only)
|
|
{
|
|
NtQueryInformationFile = (PNtQueryInformationFile)
|
|
GetProcAddress (GetModuleHandle ("ntdll.dll"),
|
|
"NtQueryInformationFile");
|
|
once_only = TRUE;
|
|
}
|
|
|
|
if (PeekNamedPipe (h, NULL, 0, NULL, &avail, NULL) != 0)
|
|
{
|
|
if (avail)
|
|
read = TRUE;
|
|
}
|
|
else if (GetLastError () == ERROR_BROKEN_PIPE)
|
|
;
|
|
|
|
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 Windows 9x, 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 Windows XP 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))
|
|
write = TRUE;
|
|
}
|
|
break;
|
|
|
|
case FILE_TYPE_CHAR:
|
|
write = TRUE;
|
|
if (!(rbits->in[fd / CHAR_BIT] & (1 << (fd & (CHAR_BIT - 1)))))
|
|
break;
|
|
|
|
ret = WaitForSingleObject (h, 0);
|
|
if (ret == WAIT_OBJECT_0)
|
|
{
|
|
if (!IsConsoleHandle (h))
|
|
{
|
|
read = TRUE;
|
|
break;
|
|
}
|
|
|
|
nbuffer = avail = 0;
|
|
bRet = GetNumberOfConsoleInputEvents (h, &nbuffer);
|
|
|
|
/* Screen buffers handles are filtered earlier. */
|
|
assert (bRet);
|
|
if (nbuffer == 0)
|
|
{
|
|
except = TRUE;
|
|
break;
|
|
}
|
|
|
|
irbuffer = (INPUT_RECORD *) alloca (nbuffer * sizeof (INPUT_RECORD));
|
|
bRet = PeekConsoleInput (h, irbuffer, nbuffer, &avail);
|
|
if (!bRet || avail == 0)
|
|
{
|
|
except = TRUE;
|
|
break;
|
|
}
|
|
|
|
for (i = 0; i < avail; i++)
|
|
if (irbuffer[i].EventType == KEY_EVENT)
|
|
read = TRUE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ret = WaitForSingleObject (h, 0);
|
|
write = TRUE;
|
|
if (ret == WAIT_OBJECT_0)
|
|
read = TRUE;
|
|
|
|
break;
|
|
}
|
|
|
|
ret = 0;
|
|
if (read && (rbits->in[fd / CHAR_BIT] & (1 << (fd & (CHAR_BIT - 1)))))
|
|
{
|
|
rbits->out[fd / CHAR_BIT] |= (1 << (fd & (CHAR_BIT - 1)));
|
|
ret++;
|
|
}
|
|
|
|
if (write && (wbits->in[fd / CHAR_BIT] & (1 << (fd & (CHAR_BIT - 1)))))
|
|
{
|
|
wbits->out[fd / CHAR_BIT] |= (1 << (fd & (CHAR_BIT - 1)));
|
|
ret++;
|
|
}
|
|
|
|
if (except && (xbits->in[fd / CHAR_BIT] & (1 << (fd & (CHAR_BIT - 1)))))
|
|
{
|
|
xbits->out[fd / CHAR_BIT] |= (1 << (fd & (CHAR_BIT - 1)));
|
|
ret++;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
rpl_select (int nfds, fd_set *rfds, fd_set *wfds, fd_set *xfds,
|
|
struct timeval *timeout)
|
|
#undef timeval
|
|
{
|
|
static struct timeval tv0;
|
|
static HANDLE hEvent;
|
|
HANDLE h, handle_array[FD_SETSIZE + 2];
|
|
fd_set handle_rfds, handle_wfds, handle_xfds;
|
|
struct bitset rbits, wbits, xbits;
|
|
unsigned char anyfds_in[FD_SETSIZE / CHAR_BIT];
|
|
DWORD ret, wait_timeout, nhandles, nsock, nbuffer;
|
|
MSG msg;
|
|
int i, fd, rc;
|
|
clock_t tend;
|
|
|
|
if (nfds > FD_SETSIZE)
|
|
nfds = FD_SETSIZE;
|
|
|
|
if (!timeout)
|
|
wait_timeout = INFINITE;
|
|
else
|
|
{
|
|
wait_timeout = timeout->tv_sec * 1000 + timeout->tv_usec / 1000;
|
|
|
|
/* select is also used as a portable usleep. */
|
|
if (!rfds && !wfds && !xfds)
|
|
{
|
|
Sleep (wait_timeout);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!hEvent)
|
|
hEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
|
|
|
|
handle_array[0] = hEvent;
|
|
nhandles = 1;
|
|
nsock = 0;
|
|
|
|
/* Copy descriptors to bitsets. At the same time, eliminate
|
|
bits in the "wrong" direction for console input buffers
|
|
and screen buffers, because screen buffers are waitable
|
|
and they will block until a character is available. */
|
|
memset (&rbits, 0, sizeof (rbits));
|
|
memset (&wbits, 0, sizeof (wbits));
|
|
memset (&xbits, 0, sizeof (xbits));
|
|
memset (anyfds_in, 0, sizeof (anyfds_in));
|
|
if (rfds)
|
|
for (i = 0; i < rfds->fd_count; i++)
|
|
{
|
|
fd = rfds->fd_array[i];
|
|
h = (HANDLE) _get_osfhandle (fd);
|
|
if (IsConsoleHandle (h)
|
|
&& !GetNumberOfConsoleInputEvents (h, &nbuffer))
|
|
continue;
|
|
|
|
rbits.in[fd / CHAR_BIT] |= 1 << (fd & (CHAR_BIT - 1));
|
|
anyfds_in[fd / CHAR_BIT] |= 1 << (fd & (CHAR_BIT - 1));
|
|
}
|
|
else
|
|
rfds = (fd_set *) alloca (sizeof (fd_set));
|
|
|
|
if (wfds)
|
|
for (i = 0; i < wfds->fd_count; i++)
|
|
{
|
|
fd = wfds->fd_array[i];
|
|
h = (HANDLE) _get_osfhandle (fd);
|
|
if (IsConsoleHandle (h)
|
|
&& GetNumberOfConsoleInputEvents (h, &nbuffer))
|
|
continue;
|
|
|
|
wbits.in[fd / CHAR_BIT] |= 1 << (fd & (CHAR_BIT - 1));
|
|
anyfds_in[fd / CHAR_BIT] |= 1 << (fd & (CHAR_BIT - 1));
|
|
}
|
|
else
|
|
wfds = (fd_set *) alloca (sizeof (fd_set));
|
|
|
|
if (xfds)
|
|
for (i = 0; i < xfds->fd_count; i++)
|
|
{
|
|
fd = xfds->fd_array[i];
|
|
xbits.in[fd / CHAR_BIT] |= 1 << (fd & (CHAR_BIT - 1));
|
|
anyfds_in[fd / CHAR_BIT] |= 1 << (fd & (CHAR_BIT - 1));
|
|
}
|
|
else
|
|
xfds = (fd_set *) alloca (sizeof (fd_set));
|
|
|
|
/* Zero all the fd_sets, including the application's. */
|
|
FD_ZERO (rfds);
|
|
FD_ZERO (wfds);
|
|
FD_ZERO (xfds);
|
|
FD_ZERO (&handle_rfds);
|
|
FD_ZERO (&handle_wfds);
|
|
FD_ZERO (&handle_xfds);
|
|
|
|
/* Classify handles. Create fd sets for sockets, poll the others. */
|
|
for (i = 0; i < nfds; i++)
|
|
{
|
|
if ((anyfds_in[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1)))) == 0)
|
|
continue;
|
|
|
|
h = (HANDLE) _get_osfhandle (i);
|
|
if (!h)
|
|
{
|
|
errno = EBADF;
|
|
return -1;
|
|
}
|
|
|
|
if (IsSocketHandle (h))
|
|
{
|
|
int requested = FD_CLOSE;
|
|
|
|
/* See above; socket handles are mapped onto select, but we
|
|
need to map descriptors to handles. */
|
|
if (rbits.in[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1))))
|
|
{
|
|
requested |= FD_READ | FD_ACCEPT;
|
|
FD_SET ((SOCKET) h, rfds);
|
|
FD_SET ((SOCKET) h, &handle_rfds);
|
|
}
|
|
if (wbits.in[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1))))
|
|
{
|
|
requested |= FD_WRITE | FD_CONNECT;
|
|
FD_SET ((SOCKET) h, wfds);
|
|
FD_SET ((SOCKET) h, &handle_wfds);
|
|
}
|
|
if (xbits.in[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1))))
|
|
{
|
|
requested |= FD_OOB;
|
|
FD_SET ((SOCKET) h, xfds);
|
|
FD_SET ((SOCKET) h, &handle_xfds);
|
|
}
|
|
|
|
WSAEventSelect ((SOCKET) h, hEvent, requested);
|
|
nsock++;
|
|
}
|
|
else
|
|
{
|
|
handle_array[nhandles++] = h;
|
|
|
|
/* Poll now. If we get an event, do not wait below. */
|
|
if (wait_timeout != 0
|
|
&& windows_poll_handle (h, i, &rbits, &wbits, &xbits))
|
|
wait_timeout = 0;
|
|
}
|
|
}
|
|
|
|
/* Place a sentinel at the end of the array. */
|
|
handle_array[nhandles] = NULL;
|
|
|
|
/* When will the waiting period expire? */
|
|
if (wait_timeout != INFINITE)
|
|
tend = clock () + wait_timeout;
|
|
|
|
restart:
|
|
if (wait_timeout == 0 || nsock == 0)
|
|
rc = 0;
|
|
else
|
|
{
|
|
/* See if we need to wait in the loop below. If any select is ready,
|
|
do MsgWaitForMultipleObjects anyway to dispatch messages, but
|
|
no need to call select again. */
|
|
rc = select (0, &handle_rfds, &handle_wfds, &handle_xfds, &tv0);
|
|
if (rc == 0)
|
|
{
|
|
/* Restore the fd_sets for the other select we do below. */
|
|
memcpy (&handle_rfds, rfds, sizeof (fd_set));
|
|
memcpy (&handle_wfds, wfds, sizeof (fd_set));
|
|
memcpy (&handle_xfds, xfds, sizeof (fd_set));
|
|
}
|
|
else
|
|
wait_timeout = 0;
|
|
}
|
|
|
|
/* How much is left to wait? */
|
|
if (wait_timeout != INFINITE)
|
|
{
|
|
clock_t tnow = clock ();
|
|
if (tend >= tnow)
|
|
wait_timeout = tend - tnow;
|
|
else
|
|
wait_timeout = 0;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
ret = MsgWaitForMultipleObjects (nhandles, handle_array, FALSE,
|
|
wait_timeout, QS_ALLINPUT);
|
|
|
|
if (ret == WAIT_OBJECT_0 + nhandles)
|
|
{
|
|
/* new input of some other kind */
|
|
BOOL bRet;
|
|
while ((bRet = PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) != 0)
|
|
{
|
|
TranslateMessage (&msg);
|
|
DispatchMessage (&msg);
|
|
}
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
/* If we haven't done it yet, check the status of the sockets. */
|
|
if (rc == 0 && nsock > 0)
|
|
rc = select (0, &handle_rfds, &handle_wfds, &handle_xfds, &tv0);
|
|
|
|
if (nhandles > 1)
|
|
{
|
|
/* Count results that are not counted in the return value of select. */
|
|
nhandles = 1;
|
|
for (i = 0; i < nfds; i++)
|
|
{
|
|
if ((anyfds_in[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1)))) == 0)
|
|
continue;
|
|
|
|
h = (HANDLE) _get_osfhandle (i);
|
|
if (h == handle_array[nhandles])
|
|
{
|
|
/* Not a socket. */
|
|
nhandles++;
|
|
windows_poll_handle (h, i, &rbits, &wbits, &xbits);
|
|
if (rbits.out[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1)))
|
|
|| wbits.out[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1)))
|
|
|| xbits.out[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1))))
|
|
rc++;
|
|
}
|
|
}
|
|
|
|
if (rc == 0
|
|
&& (wait_timeout == INFINITE
|
|
/* If NHANDLES > 1, but no bits are set, it means we've
|
|
been told incorrectly that some handle was signaled.
|
|
This happens with anonymous pipes, which always cause
|
|
MsgWaitForMultipleObjects to exit immediately, but no
|
|
data is found ready to be read by windows_poll_handle.
|
|
To avoid a total failure (whereby we return zero and
|
|
don't wait at all), let's poll in a more busy loop. */
|
|
|| (wait_timeout != 0 && nhandles > 1)))
|
|
{
|
|
/* Sleep 1 millisecond to avoid busy wait and retry with the
|
|
original fd_sets. */
|
|
memcpy (&handle_rfds, rfds, sizeof (fd_set));
|
|
memcpy (&handle_wfds, wfds, sizeof (fd_set));
|
|
memcpy (&handle_xfds, xfds, sizeof (fd_set));
|
|
SleepEx (1, TRUE);
|
|
goto restart;
|
|
}
|
|
if (timeout && wait_timeout == 0 && rc == 0)
|
|
timeout->tv_sec = timeout->tv_usec = 0;
|
|
}
|
|
|
|
/* Now fill in the results. */
|
|
FD_ZERO (rfds);
|
|
FD_ZERO (wfds);
|
|
FD_ZERO (xfds);
|
|
nhandles = 1;
|
|
for (i = 0; i < nfds; i++)
|
|
{
|
|
if ((anyfds_in[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1)))) == 0)
|
|
continue;
|
|
|
|
h = (HANDLE) _get_osfhandle (i);
|
|
if (h != handle_array[nhandles])
|
|
{
|
|
/* Perform handle->descriptor mapping. */
|
|
SOCKET s = (SOCKET) h;
|
|
WSAEventSelect (s, NULL, 0);
|
|
if (FD_ISSET (s, &handle_rfds))
|
|
FD_SET (i, rfds);
|
|
if (FD_ISSET (s, &handle_wfds))
|
|
FD_SET (i, wfds);
|
|
if (FD_ISSET (s, &handle_xfds))
|
|
FD_SET (i, xfds);
|
|
}
|
|
else
|
|
{
|
|
/* Not a socket. */
|
|
nhandles++;
|
|
if (rbits.out[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1))))
|
|
FD_SET (i, rfds);
|
|
if (wbits.out[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1))))
|
|
FD_SET (i, wfds);
|
|
if (xbits.out[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1))))
|
|
FD_SET (i, xfds);
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
#else /* ! Native Windows. */
|
|
|
|
#include <stddef.h> /* NULL */
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
|
|
#undef select
|
|
|
|
int
|
|
rpl_select (int nfds, fd_set *rfds, fd_set *wfds, fd_set *xfds,
|
|
struct timeval *timeout)
|
|
{
|
|
int i;
|
|
|
|
/* FreeBSD 8.2 has a bug: it does not always detect invalid fds. */
|
|
if (nfds < 0 || nfds > FD_SETSIZE)
|
|
{
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
for (i = 0; i < nfds; i++)
|
|
{
|
|
if (((rfds && FD_ISSET (i, rfds))
|
|
|| (wfds && FD_ISSET (i, wfds))
|
|
|| (xfds && FD_ISSET (i, xfds)))
|
|
&& dup2 (i, i) != i)
|
|
return -1;
|
|
}
|
|
|
|
/* Interix 3.5 has a bug: it does not support nfds == 0. */
|
|
if (nfds == 0)
|
|
{
|
|
nfds = 1;
|
|
rfds = NULL;
|
|
wfds = NULL;
|
|
xfds = NULL;
|
|
}
|
|
return select (nfds, rfds, wfds, xfds, timeout);
|
|
}
|
|
|
|
#endif
|