xref: /php-src/win32/select.c (revision 413844d6)
1 /*
2   +----------------------------------------------------------------------+
3   | Copyright (c) The PHP Group                                          |
4   +----------------------------------------------------------------------+
5   | This source file is subject to version 3.01 of the PHP license,      |
6   | that is bundled with this package in the file LICENSE, and is        |
7   | available through the world-wide-web at the following url:           |
8   | https://www.php.net/license/3_01.txt                                 |
9   | If you did not receive a copy of the PHP license and are unable to   |
10   | obtain it through the world-wide-web, please send a note to          |
11   | license@php.net so we can mail you a copy immediately.               |
12   +----------------------------------------------------------------------+
13   | Author: Wez Furlong <wez@thebrainroom.com>                           |
14   +----------------------------------------------------------------------+
15 */
16 
17 #include "php.h"
18 #include "php_network.h"
19 
20 /* Win32 select() will only work with sockets, so we roll our own implementation here.
21  * - If you supply only sockets, this simply passes through to winsock select().
22  * - If you supply file handles, there is no way to distinguish between
23  *   ready for read/write or OOB, so any set in which the handle is found will
24  *   be marked as ready.
25  * - If you supply a mixture of handles and sockets, the system will interleave
26  *   calls between select() and WaitForMultipleObjects(). The time slicing may
27  *   cause this function call to take up to 100 ms longer than you specified.
28  * - Calling this with NULL sets as a portable way to sleep with sub-second
29  *   accuracy is not supported.
30  * */
php_select(php_socket_t max_fd,fd_set * rfds,fd_set * wfds,fd_set * efds,struct timeval * tv)31 PHPAPI int php_select(php_socket_t max_fd, fd_set *rfds, fd_set *wfds, fd_set *efds, struct timeval *tv)
32 {
33 	ULONGLONG ms_total, limit;
34 	HANDLE handles[MAXIMUM_WAIT_OBJECTS];
35 	int handle_slot_to_fd[MAXIMUM_WAIT_OBJECTS];
36 	int n_handles = 0, i;
37 	fd_set sock_read, sock_write, sock_except;
38 	fd_set aread, awrite, aexcept;
39 	int sock_max_fd = -1;
40 	struct timeval tvslice;
41 	int retcode;
42 
43 	/* As max_fd is unsigned, non socket might overflow. */
44 	if (max_fd > (php_socket_t)INT_MAX) {
45 		return -1;
46 	}
47 
48 #define SAFE_FD_ISSET(fd, set)	(set != NULL && FD_ISSET(fd, set))
49 
50 	/* calculate how long we need to wait in milliseconds */
51 	if (tv == NULL) {
52 		ms_total = INFINITE;
53 	} else {
54 		ms_total = tv->tv_sec * 1000;
55 		ms_total += tv->tv_usec / 1000;
56 	}
57 
58 	FD_ZERO(&sock_read);
59 	FD_ZERO(&sock_write);
60 	FD_ZERO(&sock_except);
61 
62 	/* build an array of handles for non-sockets */
63 	for (i = 0; (uint32_t)i < max_fd; i++) {
64 		if (SAFE_FD_ISSET(i, rfds) || SAFE_FD_ISSET(i, wfds) || SAFE_FD_ISSET(i, efds)) {
65 			handles[n_handles] = (HANDLE)(uintptr_t)_get_osfhandle(i);
66 			if (handles[n_handles] == INVALID_HANDLE_VALUE) {
67 				/* socket */
68 				if (SAFE_FD_ISSET(i, rfds)) {
69 					FD_SET((uint32_t)i, &sock_read);
70 				}
71 				if (SAFE_FD_ISSET(i, wfds)) {
72 					FD_SET((uint32_t)i, &sock_write);
73 				}
74 				if (SAFE_FD_ISSET(i, efds)) {
75 					FD_SET((uint32_t)i, &sock_except);
76 				}
77 				if (i > sock_max_fd) {
78 					sock_max_fd = i;
79 				}
80 			} else {
81 				handle_slot_to_fd[n_handles] = i;
82 				n_handles++;
83 			}
84 		}
85 	}
86 
87 	if (n_handles == 0) {
88 		/* plain sockets only - let winsock handle the whole thing */
89 		return select(-1, rfds, wfds, efds, tv);
90 	}
91 
92 	/* mixture of handles and sockets; lets multiplex between
93 	 * winsock and waiting on the handles */
94 
95 	FD_ZERO(&aread);
96 	FD_ZERO(&awrite);
97 	FD_ZERO(&aexcept);
98 
99 	limit = GetTickCount64() + ms_total;
100 	do {
101 		retcode = 0;
102 
103 		if (sock_max_fd >= 0) {
104 			/* overwrite the zero'd sets here; the select call
105 			 * will clear those that are not active */
106 			aread = sock_read;
107 			awrite = sock_write;
108 			aexcept = sock_except;
109 
110 			tvslice.tv_sec = 0;
111 			tvslice.tv_usec = 100000;
112 
113 			retcode = select(-1, &aread, &awrite, &aexcept, &tvslice);
114 		}
115 		if (n_handles > 0) {
116 			/* check handles */
117 			DWORD wret;
118 
119 			wret = WaitForMultipleObjects(n_handles, handles, FALSE, retcode > 0 ? 0 : 100);
120 
121 			if (wret == WAIT_TIMEOUT) {
122 				/* set retcode to 0; this is the default.
123 				 * select() may have set it to something else,
124 				 * in which case we leave it alone, so this branch
125 				 * does nothing */
126 				;
127 			} else if (wret == WAIT_FAILED) {
128 				if (retcode == 0) {
129 					retcode = -1;
130 				}
131 			} else {
132 				if (retcode < 0) {
133 					retcode = 0;
134 				}
135 				for (i = 0; i < n_handles; i++) {
136 					if (WAIT_OBJECT_0 == WaitForSingleObject(handles[i], 0)) {
137 						if (SAFE_FD_ISSET(handle_slot_to_fd[i], rfds)) {
138 							FD_SET((uint32_t)handle_slot_to_fd[i], &aread);
139 						}
140 						if (SAFE_FD_ISSET(handle_slot_to_fd[i], wfds)) {
141 							FD_SET((uint32_t)handle_slot_to_fd[i], &awrite);
142 						}
143 						if (SAFE_FD_ISSET(handle_slot_to_fd[i], efds)) {
144 							FD_SET((uint32_t)handle_slot_to_fd[i], &aexcept);
145 						}
146 						retcode++;
147 					}
148 				}
149 			}
150 		}
151 	} while (retcode == 0 && (ms_total == INFINITE || GetTickCount64() < limit));
152 
153 	if (rfds) {
154 		*rfds = aread;
155 	}
156 	if (wfds) {
157 		*wfds = awrite;
158 	}
159 	if (efds) {
160 		*efds = aexcept;
161 	}
162 
163 	return retcode;
164 }
165