poll.c

Cross-platform polling library for C
git clone git://git.finwo.net/lib/poll.c
Log | Files | Refs | README

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