poll_compat.c (6908B)
1 /* 2 * Public domain 3 * 4 * poll(2) emulation for Windows 5 * 6 * This emulates just-enough poll functionality on Windows to work in the 7 * context of this program. This is not a replacement for POSIX.1-2001 poll(2), 8 * though it may come closer than I care to admit. 9 * 10 * Dongsheng Song <dongsheng.song@gmail.com> 11 * Brent Cook <bcook@openbsd.org> 12 */ 13 14 #ifdef _WIN32 15 16 #include <conio.h> 17 #include <errno.h> 18 #include <io.h> 19 #include <ws2tcpip.h> 20 21 #include "poll_compat.h" 22 23 static int 24 conn_is_closed(int fd) 25 { 26 char buf[1]; 27 int ret = recv(fd, buf, 1, MSG_PEEK); 28 if (ret == -1) { 29 switch (WSAGetLastError()) { 30 case WSAECONNABORTED: 31 case WSAECONNRESET: 32 case WSAENETRESET: 33 case WSAESHUTDOWN: 34 return 1; 35 } 36 } 37 return 0; 38 } 39 40 static int 41 conn_has_oob_data(int fd) 42 { 43 char buf[1]; 44 return (recv(fd, buf, 1, MSG_PEEK | MSG_OOB) == 1); 45 } 46 47 static int 48 is_socket(int fd) 49 { 50 WSANETWORKEVENTS events; 51 return (WSAEnumNetworkEvents((SOCKET)fd, NULL, &events) == 0); 52 } 53 54 static int 55 compute_select_revents(int fd, short events, 56 fd_set *rfds, fd_set *wfds, fd_set *efds) 57 { 58 int rc = 0; 59 60 if ((events & (POLLIN | POLLRDNORM | POLLRDBAND)) && 61 FD_ISSET(fd, rfds)) { 62 if (conn_is_closed(fd)) 63 rc |= POLLHUP; 64 else 65 rc |= POLLIN | POLLRDNORM; 66 } 67 68 if ((events & (POLLOUT | POLLWRNORM | POLLWRBAND)) && 69 FD_ISSET(fd, wfds)) 70 rc |= POLLOUT; 71 72 if (FD_ISSET(fd, efds)) { 73 if (conn_is_closed(fd)) 74 rc |= POLLHUP; 75 else if (conn_has_oob_data(fd)) 76 rc |= POLLRDBAND | POLLPRI; 77 } 78 79 return rc; 80 } 81 82 static int 83 compute_wait_revents(HANDLE h, short events, int object, int wait_rc) 84 { 85 int rc = 0; 86 INPUT_RECORD record; 87 DWORD num_read; 88 89 /* 90 * Assume we can always write to file handles (probably a bad 91 * assumption but works for now, at least it doesn't block). 92 */ 93 if (events & (POLLOUT | POLLWRNORM)) 94 rc |= POLLOUT; 95 96 /* 97 * Check if this handle was signaled by WaitForMultipleObjects 98 */ 99 if (wait_rc >= WAIT_OBJECT_0 && (object == (wait_rc - WAIT_OBJECT_0)) 100 && (events & (POLLIN | POLLRDNORM))) { 101 102 /* 103 * Check if this file is stdin, and if so, if it is a console. 104 */ 105 if (h == GetStdHandle(STD_INPUT_HANDLE) && 106 PeekConsoleInput(h, &record, 1, &num_read) == 1) { 107 108 /* 109 * Handle the input console buffer differently, 110 * since it can signal on other events like 111 * window and mouse, but read can still block. 112 */ 113 if (record.EventType == KEY_EVENT && 114 record.Event.KeyEvent.bKeyDown) { 115 rc |= POLLIN; 116 } else { 117 /* 118 * Flush non-character events from the 119 * console buffer. 120 */ 121 ReadConsoleInput(h, &record, 1, &num_read); 122 } 123 } else { 124 rc |= POLLIN; 125 } 126 } 127 128 return rc; 129 } 130 131 static int 132 wsa_select_errno(int err) 133 { 134 switch (err) { 135 case WSAEINTR: 136 case WSAEINPROGRESS: 137 errno = EINTR; 138 break; 139 case WSAEFAULT: 140 /* 141 * Windows uses WSAEFAULT for both resource allocation failures 142 * and arguments not being contained in the user's address 143 * space. So, we have to choose EFAULT or ENOMEM. 144 */ 145 errno = EFAULT; 146 break; 147 case WSAEINVAL: 148 errno = EINVAL; 149 break; 150 case WSANOTINITIALISED: 151 errno = EPERM; 152 break; 153 case WSAENETDOWN: 154 errno = ENOMEM; 155 break; 156 } 157 return -1; 158 } 159 160 int 161 poll(struct pollfd *pfds, nfds_t nfds, int timeout_ms) 162 { 163 nfds_t i; 164 int timespent_ms, looptime_ms; 165 166 #define FD_IS_SOCKET (1 << 0) 167 int fd_state[FD_SETSIZE]; 168 int num_fds; 169 170 /* 171 * select machinery 172 */ 173 fd_set rfds, wfds, efds; 174 int rc; 175 int num_sockets; 176 177 /* 178 * wait machinery 179 */ 180 DWORD wait_rc; 181 HANDLE handles[FD_SETSIZE]; 182 int num_handles; 183 184 if (pfds == NULL) { 185 errno = EINVAL; 186 return -1; 187 } 188 189 if (nfds <= 0) { 190 return 0; 191 } 192 193 FD_ZERO(&rfds); 194 FD_ZERO(&wfds); 195 FD_ZERO(&efds); 196 num_fds = 0; 197 num_sockets = 0; 198 num_handles = 0; 199 200 for (i = 0; i < nfds; i++) { 201 if ((int)pfds[i].fd < 0) { 202 continue; 203 } 204 205 if (is_socket(pfds[i].fd)) { 206 if (num_sockets >= FD_SETSIZE) { 207 errno = EINVAL; 208 return -1; 209 } 210 211 fd_state[num_fds] = FD_IS_SOCKET; 212 213 FD_SET(pfds[i].fd, &efds); 214 215 if (pfds[i].events & 216 (POLLIN | POLLRDNORM | POLLRDBAND)) { 217 FD_SET(pfds[i].fd, &rfds); 218 } 219 220 if (pfds[i].events & 221 (POLLOUT | POLLWRNORM | POLLWRBAND)) { 222 FD_SET(pfds[i].fd, &wfds); 223 } 224 num_sockets++; 225 226 } else { 227 if (num_handles >= FD_SETSIZE) { 228 errno = EINVAL; 229 return -1; 230 } 231 232 handles[num_handles++] = 233 (HANDLE)_get_osfhandle(pfds[i].fd); 234 } 235 236 num_fds++; 237 } 238 239 /* 240 * Determine if the files, pipes, sockets, consoles, etc. have signaled. 241 * 242 * Do this by alternating a loop between WaitForMultipleObjects for 243 * non-sockets and and select for sockets. 244 * 245 * I tried to implement this all in terms of WaitForMultipleObjects 246 * with a select-based 'poll' of the sockets at the end to get extra 247 * specific socket status. 248 * 249 * However, the cost of setting up an event handle for each socket and 250 * cleaning them up reliably was pretty high. Since the event handle 251 * associated with a socket is also global, creating a new one here 252 * cancels one that may exist externally to this function. 253 * 254 * At any rate, even if global socket event handles were not an issue, 255 * the 'FD_WRITE' status of a socket event handle does not behave in an 256 * expected fashion, being triggered by an edge on a write buffer rather 257 * than simply triggering if there is space available. 258 */ 259 timespent_ms = 0; 260 wait_rc = 0; 261 262 if (timeout_ms < 0) { 263 timeout_ms = INFINITE; 264 } 265 looptime_ms = timeout_ms > 100 ? 100 : timeout_ms; 266 267 do { 268 struct timeval tv = {0, looptime_ms * 1000}; 269 270 /* 271 * Check if any file handles have signaled 272 */ 273 if (num_handles) { 274 wait_rc = WaitForMultipleObjects(num_handles, handles, FALSE, 0); 275 if (wait_rc == WAIT_FAILED) { 276 /* 277 * The documentation for WaitForMultipleObjects 278 * does not specify what values GetLastError 279 * may return here. Rather than enumerate 280 * badness like for wsa_select_errno, assume a 281 * general errno value. 282 */ 283 errno = ENOMEM; 284 return 0; 285 } 286 } 287 288 /* 289 * If we signaled on a file handle, don't wait on the sockets. 290 */ 291 if (wait_rc >= WAIT_OBJECT_0) 292 tv.tv_usec = 0; 293 294 /* 295 * Check if any sockets have signaled 296 */ 297 rc = select(0, &rfds, &wfds, &efds, &tv); 298 if (rc == SOCKET_ERROR) { 299 return wsa_select_errno(WSAGetLastError()); 300 } 301 302 if (wait_rc >= WAIT_OBJECT_0 || (num_sockets && rc > 0)) 303 break; 304 305 timespent_ms += looptime_ms; 306 307 } while (timespent_ms < timeout_ms); 308 309 rc = 0; 310 num_handles = 0; 311 num_fds = 0; 312 for (i = 0; i < nfds; i++) { 313 pfds[i].revents = 0; 314 315 if ((int)pfds[i].fd < 0) 316 continue; 317 318 if (fd_state[num_fds] & FD_IS_SOCKET) { 319 pfds[i].revents = compute_select_revents(pfds[i].fd, 320 pfds[i].events, &rfds, &wfds, &efds); 321 322 } else { 323 pfds[i].revents = compute_wait_revents( 324 handles[num_handles], pfds[i].events, num_handles, 325 wait_rc); 326 num_handles++; 327 } 328 329 num_fds++; 330 331 if (pfds[i].revents) 332 rc++; 333 } 334 335 return rc; 336 } 337 338 #endif // _WIN32