| 1 | /* Emulation for select(2) |
| 2 | Contributed by Paolo Bonzini. |
| 3 | |
| 4 | Copyright 2008-2014 Free Software Foundation, Inc. |
| 5 | |
| 6 | This file is part of gnulib. |
| 7 | |
| 8 | This program is free software; you can redistribute it and/or modify |
| 9 | it under the terms of the GNU Lesser General Public License as published by |
| 10 | the Free Software Foundation; either version 2, or (at your option) |
| 11 | any later version. |
| 12 | |
| 13 | This program is distributed in the hope that it will be useful, |
| 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 16 | GNU Lesser General Public License for more details. |
| 17 | |
| 18 | You should have received a copy of the GNU Lesser General Public License along |
| 19 | with this program; if not, see <http://www.gnu.org/licenses/>. */ |
| 20 | |
| 21 | #include <config.h> |
| 22 | #include <alloca.h> |
| 23 | #include <assert.h> |
| 24 | |
| 25 | #if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ |
| 26 | /* Native Windows. */ |
| 27 | |
| 28 | #include <sys/types.h> |
| 29 | #include <errno.h> |
| 30 | #include <limits.h> |
| 31 | |
| 32 | #include <winsock2.h> |
| 33 | #include <windows.h> |
| 34 | #include <io.h> |
| 35 | #include <stdio.h> |
| 36 | #include <conio.h> |
| 37 | #include <time.h> |
| 38 | |
| 39 | /* Get the overridden 'struct timeval'. */ |
| 40 | #include <sys/time.h> |
| 41 | |
| 42 | #include "msvc-nothrow.h" |
| 43 | |
| 44 | #undef select |
| 45 | |
| 46 | struct bitset { |
| 47 | unsigned char in[FD_SETSIZE / CHAR_BIT]; |
| 48 | unsigned char out[FD_SETSIZE / CHAR_BIT]; |
| 49 | }; |
| 50 | |
| 51 | /* Declare data structures for ntdll functions. */ |
| 52 | typedef struct _FILE_PIPE_LOCAL_INFORMATION { |
| 53 | ULONG NamedPipeType; |
| 54 | ULONG NamedPipeConfiguration; |
| 55 | ULONG MaximumInstances; |
| 56 | ULONG CurrentInstances; |
| 57 | ULONG InboundQuota; |
| 58 | ULONG ReadDataAvailable; |
| 59 | ULONG OutboundQuota; |
| 60 | ULONG WriteQuotaAvailable; |
| 61 | ULONG NamedPipeState; |
| 62 | ULONG NamedPipeEnd; |
| 63 | } FILE_PIPE_LOCAL_INFORMATION, *PFILE_PIPE_LOCAL_INFORMATION; |
| 64 | |
| 65 | typedef struct _IO_STATUS_BLOCK |
| 66 | { |
| 67 | union { |
| 68 | DWORD Status; |
| 69 | PVOID Pointer; |
| 70 | } u; |
| 71 | ULONG_PTR Information; |
| 72 | } IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; |
| 73 | |
| 74 | typedef enum _FILE_INFORMATION_CLASS { |
| 75 | FilePipeLocalInformation = 24 |
| 76 | } FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS; |
| 77 | |
| 78 | typedef DWORD (WINAPI *PNtQueryInformationFile) |
| 79 | (HANDLE, IO_STATUS_BLOCK *, VOID *, ULONG, FILE_INFORMATION_CLASS); |
| 80 | |
| 81 | #ifndef PIPE_BUF |
| 82 | #define PIPE_BUF 512 |
| 83 | #endif |
| 84 | |
| 85 | /* Optimized test whether a HANDLE refers to a console. |
| 86 | See <http://lists.gnu.org/archive/html/bug-gnulib/2009-08/msg00065.html>. */ |
| 87 | #define IsConsoleHandle(h) (((intptr_t) (h) & 3) == 3) |
| 88 | |
| 89 | static BOOL |
| 90 | IsSocketHandle (HANDLE h) |
| 91 | { |
| 92 | WSANETWORKEVENTS ev; |
| 93 | |
| 94 | if (IsConsoleHandle (h)) |
| 95 | return FALSE; |
| 96 | |
| 97 | /* Under Wine, it seems that getsockopt returns 0 for pipes too. |
| 98 | WSAEnumNetworkEvents instead distinguishes the two correctly. */ |
| 99 | ev.lNetworkEvents = 0xDEADBEEF; |
| 100 | WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev); |
| 101 | return ev.lNetworkEvents != 0xDEADBEEF; |
| 102 | } |
| 103 | |
| 104 | /* Compute output fd_sets for libc descriptor FD (whose Windows handle is |
| 105 | H). */ |
| 106 | |
| 107 | static int |
| 108 | windows_poll_handle (HANDLE h, int fd, |
| 109 | struct bitset *rbits, |
| 110 | struct bitset *wbits, |
| 111 | struct bitset *xbits) |
| 112 | { |
| 113 | BOOL read, write, except; |
| 114 | int i, ret; |
| 115 | INPUT_RECORD *irbuffer; |
| 116 | DWORD avail, nbuffer; |
| 117 | BOOL bRet; |
| 118 | IO_STATUS_BLOCK iosb; |
| 119 | FILE_PIPE_LOCAL_INFORMATION fpli; |
| 120 | static PNtQueryInformationFile NtQueryInformationFile; |
| 121 | static BOOL once_only; |
| 122 | |
| 123 | read = write = except = FALSE; |
| 124 | switch (GetFileType (h)) |
| 125 | { |
| 126 | case FILE_TYPE_DISK: |
| 127 | read = TRUE; |
| 128 | write = TRUE; |
| 129 | break; |
| 130 | |
| 131 | case FILE_TYPE_PIPE: |
| 132 | if (!once_only) |
| 133 | { |
| 134 | NtQueryInformationFile = (PNtQueryInformationFile) |
| 135 | GetProcAddress (GetModuleHandle ("ntdll.dll"), |
| 136 | "NtQueryInformationFile"); |
| 137 | once_only = TRUE; |
| 138 | } |
| 139 | |
| 140 | if (PeekNamedPipe (h, NULL, 0, NULL, &avail, NULL) != 0) |
| 141 | { |
| 142 | if (avail) |
| 143 | read = TRUE; |
| 144 | } |
| 145 | else if (GetLastError () == ERROR_BROKEN_PIPE) |
| 146 | ; |
| 147 | |
| 148 | else |
| 149 | { |
| 150 | /* It was the write-end of the pipe. Check if it is writable. |
| 151 | If NtQueryInformationFile fails, optimistically assume the pipe is |
| 152 | writable. This could happen on Windows 9x, where |
| 153 | NtQueryInformationFile is not available, or if we inherit a pipe |
| 154 | that doesn't permit FILE_READ_ATTRIBUTES access on the write end |
| 155 | (I think this should not happen since Windows XP SP2; WINE seems |
| 156 | fine too). Otherwise, ensure that enough space is available for |
| 157 | atomic writes. */ |
| 158 | memset (&iosb, 0, sizeof (iosb)); |
| 159 | memset (&fpli, 0, sizeof (fpli)); |
| 160 | |
| 161 | if (!NtQueryInformationFile |
| 162 | || NtQueryInformationFile (h, &iosb, &fpli, sizeof (fpli), |
| 163 | FilePipeLocalInformation) |
| 164 | || fpli.WriteQuotaAvailable >= PIPE_BUF |
| 165 | || (fpli.OutboundQuota < PIPE_BUF && |
| 166 | fpli.WriteQuotaAvailable == fpli.OutboundQuota)) |
| 167 | write = TRUE; |
| 168 | } |
| 169 | break; |
| 170 | |
| 171 | case FILE_TYPE_CHAR: |
| 172 | write = TRUE; |
| 173 | if (!(rbits->in[fd / CHAR_BIT] & (1 << (fd & (CHAR_BIT - 1))))) |
| 174 | break; |
| 175 | |
| 176 | ret = WaitForSingleObject (h, 0); |
| 177 | if (ret == WAIT_OBJECT_0) |
| 178 | { |
| 179 | if (!IsConsoleHandle (h)) |
| 180 | { |
| 181 | read = TRUE; |
| 182 | break; |
| 183 | } |
| 184 | |
| 185 | nbuffer = avail = 0; |
| 186 | bRet = GetNumberOfConsoleInputEvents (h, &nbuffer); |
| 187 | |
| 188 | /* Screen buffers handles are filtered earlier. */ |
| 189 | assert (bRet); |
| 190 | if (nbuffer == 0) |
| 191 | { |
| 192 | except = TRUE; |
| 193 | break; |
| 194 | } |
| 195 | |
| 196 | irbuffer = (INPUT_RECORD *) alloca (nbuffer * sizeof (INPUT_RECORD)); |
| 197 | bRet = PeekConsoleInput (h, irbuffer, nbuffer, &avail); |
| 198 | if (!bRet || avail == 0) |
| 199 | { |
| 200 | except = TRUE; |
| 201 | break; |
| 202 | } |
| 203 | |
| 204 | for (i = 0; i < avail; i++) |
| 205 | if (irbuffer[i].EventType == KEY_EVENT) |
| 206 | read = TRUE; |
| 207 | } |
| 208 | break; |
| 209 | |
| 210 | default: |
| 211 | ret = WaitForSingleObject (h, 0); |
| 212 | write = TRUE; |
| 213 | if (ret == WAIT_OBJECT_0) |
| 214 | read = TRUE; |
| 215 | |
| 216 | break; |
| 217 | } |
| 218 | |
| 219 | ret = 0; |
| 220 | if (read && (rbits->in[fd / CHAR_BIT] & (1 << (fd & (CHAR_BIT - 1))))) |
| 221 | { |
| 222 | rbits->out[fd / CHAR_BIT] |= (1 << (fd & (CHAR_BIT - 1))); |
| 223 | ret++; |
| 224 | } |
| 225 | |
| 226 | if (write && (wbits->in[fd / CHAR_BIT] & (1 << (fd & (CHAR_BIT - 1))))) |
| 227 | { |
| 228 | wbits->out[fd / CHAR_BIT] |= (1 << (fd & (CHAR_BIT - 1))); |
| 229 | ret++; |
| 230 | } |
| 231 | |
| 232 | if (except && (xbits->in[fd / CHAR_BIT] & (1 << (fd & (CHAR_BIT - 1))))) |
| 233 | { |
| 234 | xbits->out[fd / CHAR_BIT] |= (1 << (fd & (CHAR_BIT - 1))); |
| 235 | ret++; |
| 236 | } |
| 237 | |
| 238 | return ret; |
| 239 | } |
| 240 | |
| 241 | int |
| 242 | rpl_select (int nfds, fd_set *rfds, fd_set *wfds, fd_set *xfds, |
| 243 | struct timeval *timeout) |
| 244 | #undef timeval |
| 245 | { |
| 246 | static struct timeval tv0; |
| 247 | static HANDLE hEvent; |
| 248 | HANDLE h, handle_array[FD_SETSIZE + 2]; |
| 249 | fd_set handle_rfds, handle_wfds, handle_xfds; |
| 250 | struct bitset rbits, wbits, xbits; |
| 251 | unsigned char anyfds_in[FD_SETSIZE / CHAR_BIT]; |
| 252 | DWORD ret, wait_timeout, nhandles, nsock, nbuffer; |
| 253 | MSG msg; |
| 254 | int i, fd, rc; |
| 255 | |
| 256 | if (nfds > FD_SETSIZE) |
| 257 | nfds = FD_SETSIZE; |
| 258 | |
| 259 | if (!timeout) |
| 260 | wait_timeout = INFINITE; |
| 261 | else |
| 262 | { |
| 263 | wait_timeout = timeout->tv_sec * 1000 + timeout->tv_usec / 1000; |
| 264 | |
| 265 | /* select is also used as a portable usleep. */ |
| 266 | if (!rfds && !wfds && !xfds) |
| 267 | { |
| 268 | Sleep (wait_timeout); |
| 269 | return 0; |
| 270 | } |
| 271 | } |
| 272 | |
| 273 | if (!hEvent) |
| 274 | hEvent = CreateEvent (NULL, FALSE, FALSE, NULL); |
| 275 | |
| 276 | handle_array[0] = hEvent; |
| 277 | nhandles = 1; |
| 278 | nsock = 0; |
| 279 | |
| 280 | /* Copy descriptors to bitsets. At the same time, eliminate |
| 281 | bits in the "wrong" direction for console input buffers |
| 282 | and screen buffers, because screen buffers are waitable |
| 283 | and they will block until a character is available. */ |
| 284 | memset (&rbits, 0, sizeof (rbits)); |
| 285 | memset (&wbits, 0, sizeof (wbits)); |
| 286 | memset (&xbits, 0, sizeof (xbits)); |
| 287 | memset (anyfds_in, 0, sizeof (anyfds_in)); |
| 288 | if (rfds) |
| 289 | for (i = 0; i < rfds->fd_count; i++) |
| 290 | { |
| 291 | fd = rfds->fd_array[i]; |
| 292 | h = (HANDLE) _get_osfhandle (fd); |
| 293 | if (IsConsoleHandle (h) |
| 294 | && !GetNumberOfConsoleInputEvents (h, &nbuffer)) |
| 295 | continue; |
| 296 | |
| 297 | rbits.in[fd / CHAR_BIT] |= 1 << (fd & (CHAR_BIT - 1)); |
| 298 | anyfds_in[fd / CHAR_BIT] |= 1 << (fd & (CHAR_BIT - 1)); |
| 299 | } |
| 300 | else |
| 301 | rfds = (fd_set *) alloca (sizeof (fd_set)); |
| 302 | |
| 303 | if (wfds) |
| 304 | for (i = 0; i < wfds->fd_count; i++) |
| 305 | { |
| 306 | fd = wfds->fd_array[i]; |
| 307 | h = (HANDLE) _get_osfhandle (fd); |
| 308 | if (IsConsoleHandle (h) |
| 309 | && GetNumberOfConsoleInputEvents (h, &nbuffer)) |
| 310 | continue; |
| 311 | |
| 312 | wbits.in[fd / CHAR_BIT] |= 1 << (fd & (CHAR_BIT - 1)); |
| 313 | anyfds_in[fd / CHAR_BIT] |= 1 << (fd & (CHAR_BIT - 1)); |
| 314 | } |
| 315 | else |
| 316 | wfds = (fd_set *) alloca (sizeof (fd_set)); |
| 317 | |
| 318 | if (xfds) |
| 319 | for (i = 0; i < xfds->fd_count; i++) |
| 320 | { |
| 321 | fd = xfds->fd_array[i]; |
| 322 | xbits.in[fd / CHAR_BIT] |= 1 << (fd & (CHAR_BIT - 1)); |
| 323 | anyfds_in[fd / CHAR_BIT] |= 1 << (fd & (CHAR_BIT - 1)); |
| 324 | } |
| 325 | else |
| 326 | xfds = (fd_set *) alloca (sizeof (fd_set)); |
| 327 | |
| 328 | /* Zero all the fd_sets, including the application's. */ |
| 329 | FD_ZERO (rfds); |
| 330 | FD_ZERO (wfds); |
| 331 | FD_ZERO (xfds); |
| 332 | FD_ZERO (&handle_rfds); |
| 333 | FD_ZERO (&handle_wfds); |
| 334 | FD_ZERO (&handle_xfds); |
| 335 | |
| 336 | /* Classify handles. Create fd sets for sockets, poll the others. */ |
| 337 | for (i = 0; i < nfds; i++) |
| 338 | { |
| 339 | if ((anyfds_in[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1)))) == 0) |
| 340 | continue; |
| 341 | |
| 342 | h = (HANDLE) _get_osfhandle (i); |
| 343 | if (!h) |
| 344 | { |
| 345 | errno = EBADF; |
| 346 | return -1; |
| 347 | } |
| 348 | |
| 349 | if (IsSocketHandle (h)) |
| 350 | { |
| 351 | int requested = FD_CLOSE; |
| 352 | |
| 353 | /* See above; socket handles are mapped onto select, but we |
| 354 | need to map descriptors to handles. */ |
| 355 | if (rbits.in[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1)))) |
| 356 | { |
| 357 | requested |= FD_READ | FD_ACCEPT; |
| 358 | FD_SET ((SOCKET) h, rfds); |
| 359 | FD_SET ((SOCKET) h, &handle_rfds); |
| 360 | } |
| 361 | if (wbits.in[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1)))) |
| 362 | { |
| 363 | requested |= FD_WRITE | FD_CONNECT; |
| 364 | FD_SET ((SOCKET) h, wfds); |
| 365 | FD_SET ((SOCKET) h, &handle_wfds); |
| 366 | } |
| 367 | if (xbits.in[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1)))) |
| 368 | { |
| 369 | requested |= FD_OOB; |
| 370 | FD_SET ((SOCKET) h, xfds); |
| 371 | FD_SET ((SOCKET) h, &handle_xfds); |
| 372 | } |
| 373 | |
| 374 | WSAEventSelect ((SOCKET) h, hEvent, requested); |
| 375 | nsock++; |
| 376 | } |
| 377 | else |
| 378 | { |
| 379 | handle_array[nhandles++] = h; |
| 380 | |
| 381 | /* Poll now. If we get an event, do not wait below. */ |
| 382 | if (wait_timeout != 0 |
| 383 | && windows_poll_handle (h, i, &rbits, &wbits, &xbits)) |
| 384 | wait_timeout = 0; |
| 385 | } |
| 386 | } |
| 387 | |
| 388 | /* Place a sentinel at the end of the array. */ |
| 389 | handle_array[nhandles] = NULL; |
| 390 | |
| 391 | restart: |
| 392 | if (wait_timeout == 0 || nsock == 0) |
| 393 | rc = 0; |
| 394 | else |
| 395 | { |
| 396 | /* See if we need to wait in the loop below. If any select is ready, |
| 397 | do MsgWaitForMultipleObjects anyway to dispatch messages, but |
| 398 | no need to call select again. */ |
| 399 | rc = select (0, &handle_rfds, &handle_wfds, &handle_xfds, &tv0); |
| 400 | if (rc == 0) |
| 401 | { |
| 402 | /* Restore the fd_sets for the other select we do below. */ |
| 403 | memcpy (&handle_rfds, rfds, sizeof (fd_set)); |
| 404 | memcpy (&handle_wfds, wfds, sizeof (fd_set)); |
| 405 | memcpy (&handle_xfds, xfds, sizeof (fd_set)); |
| 406 | } |
| 407 | else |
| 408 | wait_timeout = 0; |
| 409 | } |
| 410 | |
| 411 | for (;;) |
| 412 | { |
| 413 | ret = MsgWaitForMultipleObjects (nhandles, handle_array, FALSE, |
| 414 | wait_timeout, QS_ALLINPUT); |
| 415 | |
| 416 | if (ret == WAIT_OBJECT_0 + nhandles) |
| 417 | { |
| 418 | /* new input of some other kind */ |
| 419 | BOOL bRet; |
| 420 | while ((bRet = PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) != 0) |
| 421 | { |
| 422 | TranslateMessage (&msg); |
| 423 | DispatchMessage (&msg); |
| 424 | } |
| 425 | } |
| 426 | else |
| 427 | break; |
| 428 | } |
| 429 | |
| 430 | /* If we haven't done it yet, check the status of the sockets. */ |
| 431 | if (rc == 0 && nsock > 0) |
| 432 | rc = select (0, &handle_rfds, &handle_wfds, &handle_xfds, &tv0); |
| 433 | |
| 434 | if (nhandles > 1) |
| 435 | { |
| 436 | /* Count results that are not counted in the return value of select. */ |
| 437 | nhandles = 1; |
| 438 | for (i = 0; i < nfds; i++) |
| 439 | { |
| 440 | if ((anyfds_in[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1)))) == 0) |
| 441 | continue; |
| 442 | |
| 443 | h = (HANDLE) _get_osfhandle (i); |
| 444 | if (h == handle_array[nhandles]) |
| 445 | { |
| 446 | /* Not a socket. */ |
| 447 | nhandles++; |
| 448 | windows_poll_handle (h, i, &rbits, &wbits, &xbits); |
| 449 | if (rbits.out[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1))) |
| 450 | || wbits.out[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1))) |
| 451 | || xbits.out[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1)))) |
| 452 | rc++; |
| 453 | } |
| 454 | } |
| 455 | |
| 456 | if (rc == 0 && wait_timeout == INFINITE) |
| 457 | { |
| 458 | /* Sleep 1 millisecond to avoid busy wait and retry with the |
| 459 | original fd_sets. */ |
| 460 | memcpy (&handle_rfds, rfds, sizeof (fd_set)); |
| 461 | memcpy (&handle_wfds, wfds, sizeof (fd_set)); |
| 462 | memcpy (&handle_xfds, xfds, sizeof (fd_set)); |
| 463 | SleepEx (1, TRUE); |
| 464 | goto restart; |
| 465 | } |
| 466 | } |
| 467 | |
| 468 | /* Now fill in the results. */ |
| 469 | FD_ZERO (rfds); |
| 470 | FD_ZERO (wfds); |
| 471 | FD_ZERO (xfds); |
| 472 | nhandles = 1; |
| 473 | for (i = 0; i < nfds; i++) |
| 474 | { |
| 475 | if ((anyfds_in[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1)))) == 0) |
| 476 | continue; |
| 477 | |
| 478 | h = (HANDLE) _get_osfhandle (i); |
| 479 | if (h != handle_array[nhandles]) |
| 480 | { |
| 481 | /* Perform handle->descriptor mapping. */ |
| 482 | WSAEventSelect ((SOCKET) h, NULL, 0); |
| 483 | if (FD_ISSET (h, &handle_rfds)) |
| 484 | FD_SET (i, rfds); |
| 485 | if (FD_ISSET (h, &handle_wfds)) |
| 486 | FD_SET (i, wfds); |
| 487 | if (FD_ISSET (h, &handle_xfds)) |
| 488 | FD_SET (i, xfds); |
| 489 | } |
| 490 | else |
| 491 | { |
| 492 | /* Not a socket. */ |
| 493 | nhandles++; |
| 494 | if (rbits.out[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1)))) |
| 495 | FD_SET (i, rfds); |
| 496 | if (wbits.out[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1)))) |
| 497 | FD_SET (i, wfds); |
| 498 | if (xbits.out[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1)))) |
| 499 | FD_SET (i, xfds); |
| 500 | } |
| 501 | } |
| 502 | |
| 503 | return rc; |
| 504 | } |
| 505 | |
| 506 | #else /* ! Native Windows. */ |
| 507 | |
| 508 | #include <sys/select.h> |
| 509 | #include <stddef.h> /* NULL */ |
| 510 | #include <errno.h> |
| 511 | #include <unistd.h> |
| 512 | |
| 513 | #undef select |
| 514 | |
| 515 | int |
| 516 | rpl_select (int nfds, fd_set *rfds, fd_set *wfds, fd_set *xfds, |
| 517 | struct timeval *timeout) |
| 518 | { |
| 519 | int i; |
| 520 | |
| 521 | /* FreeBSD 8.2 has a bug: it does not always detect invalid fds. */ |
| 522 | if (nfds < 0 || nfds > FD_SETSIZE) |
| 523 | { |
| 524 | errno = EINVAL; |
| 525 | return -1; |
| 526 | } |
| 527 | for (i = 0; i < nfds; i++) |
| 528 | { |
| 529 | if (((rfds && FD_ISSET (i, rfds)) |
| 530 | || (wfds && FD_ISSET (i, wfds)) |
| 531 | || (xfds && FD_ISSET (i, xfds))) |
| 532 | && dup2 (i, i) != i) |
| 533 | return -1; |
| 534 | } |
| 535 | |
| 536 | /* Interix 3.5 has a bug: it does not support nfds == 0. */ |
| 537 | if (nfds == 0) |
| 538 | { |
| 539 | nfds = 1; |
| 540 | rfds = NULL; |
| 541 | wfds = NULL; |
| 542 | xfds = NULL; |
| 543 | } |
| 544 | return select (nfds, rfds, wfds, xfds, timeout); |
| 545 | } |
| 546 | |
| 547 | #endif |