aboutsummaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
authorBrent Cook <busterb@gmail.com>2014-11-20 00:24:20 -0600
committerBrent Cook <bcook@openbsd.org>2014-12-03 17:03:47 -0600
commit1bbde19a7c0db8d25fb496dea3e05e3dbc0bbeaf (patch)
tree0758b56c2bfcae4f5df1db8bf241a17b8d59d8c5 /apps
parent96bf8be098b434ec19146d5724b60179ae56e00a (diff)
downloadportable-1bbde19a7c0db8d25fb496dea3e05e3dbc0bbeaf.tar.gz
portable-1bbde19a7c0db8d25fb496dea3e05e3dbc0bbeaf.tar.bz2
portable-1bbde19a7c0db8d25fb496dea3e05e3dbc0bbeaf.zip
add minimal poll(2) implementation for Windows
This provides sufficient functionality to run openssl(1) from a Windows console. This is based on the original select-based version from from songdongsheng@live.cn. Changes: * use nfds_t directly for iterating the fds. * add WSAGetLastError -> errno mappings * handle POLLHUP and the OOB data cases for revents * handle sparse arrays of fds correctly * KNF style updates * teach poll how to handle file handles as well as sockets This handles the socket/non-socket issue by alternating a loop between WaitForMultipleObjects for non-sockets and and select for sockets. One would think this would be terrible for performance, but as of this writing, poll consumes about 6% of the time doing a bulk transfer between a Linux box and 'openssl.exe s_server'. I tried to implement this all in terms of WaitForMultipleObjects with a select 'poll' at the end to get extra specific socket status. However, the cost of setting up an event handle for each socket, setting the WSAEventSelect attributes, and cleaning them up reliably was pretty high. Since the event handle associated with a socket is also global, creating a new one cancels the previous one or can be disabled externally. In addition, the 'FD_WRITE' status of a socket event handle does not behave in an expected fashion, being triggered by an edge on a write event rather than being level triggered. Another fun horror story is how stdin in windows might be a console, it might be a pipe, it might be something else. If these all worked in the same way, it would be great. But, since a console-stdin can also signal on a mouse or window event, it means we can easily get stuck in a blocking read (you can't make stdin non-blocking) if the non-character events are not filtered out. So, poll does that too. See here for various additional horror stories: http://www.postgresql.org/message-id/4351.1336927207@sss.pgh.pa.us
Diffstat (limited to 'apps')
-rw-r--r--apps/Makefile.am.tpl5
-rw-r--r--apps/poll.c334
2 files changed, 339 insertions, 0 deletions
diff --git a/apps/Makefile.am.tpl b/apps/Makefile.am.tpl
index deae953..16b3d8b 100644
--- a/apps/Makefile.am.tpl
+++ b/apps/Makefile.am.tpl
@@ -14,3 +14,8 @@ if !HAVE_STRTONUM
14openssl_SOURCES += strtonum.c 14openssl_SOURCES += strtonum.c
15endif 15endif
16 16
17if !HAVE_POLL
18if HOST_WIN
19openssl_SOURCES += poll.c
20endif
21endif
diff --git a/apps/poll.c b/apps/poll.c
new file mode 100644
index 0000000..bf30ccf
--- /dev/null
+++ b/apps/poll.c
@@ -0,0 +1,334 @@
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 the openssl(1) program. This is not a replacement for
8 * POSIX.1-2001 poll(2), 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#include <conio.h>
15#include <errno.h>
16#include <io.h>
17#include <poll.h>
18#include <ws2tcpip.h>
19
20static int
21conn_is_closed(int fd)
22{
23 char buf[1];
24 int ret = recv(fd, buf, 1, MSG_PEEK);
25 if (ret == -1) {
26 switch (WSAGetLastError()) {
27 case WSAECONNABORTED:
28 case WSAECONNRESET:
29 case WSAENETRESET:
30 case WSAESHUTDOWN:
31 return 1;
32 }
33 }
34 return 0;
35}
36
37static int
38conn_has_oob_data(int fd)
39{
40 char buf[1];
41 return (recv(fd, buf, 1, MSG_PEEK | MSG_OOB) == 1);
42}
43
44static int
45is_socket(int fd)
46{
47 WSANETWORKEVENTS events;
48 return (WSAEnumNetworkEvents((SOCKET)fd, NULL, &events) == 0);
49}
50
51static int
52compute_select_revents(int fd, short events,
53 fd_set *rfds, fd_set *wfds, fd_set *efds)
54{
55 int rc = 0;
56
57 if ((events & (POLLIN | POLLRDNORM | POLLRDBAND)) &&
58 FD_ISSET(fd, rfds)) {
59 if (conn_is_closed(fd))
60 rc |= POLLHUP;
61 else
62 rc |= POLLIN | POLLRDNORM;
63 }
64
65 if ((events & (POLLOUT | POLLWRNORM | POLLWRBAND)) &&
66 FD_ISSET(fd, wfds))
67 rc |= POLLOUT;
68
69 if (FD_ISSET(fd, efds)) {
70 if (conn_is_closed(fd))
71 rc |= POLLHUP;
72 else if (conn_has_oob_data(fd))
73 rc |= POLLRDBAND | POLLPRI;
74 }
75
76 return rc;
77}
78
79static int
80compute_wait_revents(HANDLE h, short events, int object, int wait_rc)
81{
82 int rc = 0;
83 INPUT_RECORD record;
84 DWORD num_read;
85
86 /*
87 * Assume we can always write to file handles (probably a bad
88 * assumption but works for now, at least it doesn't block).
89 */
90 if (events & (POLLOUT | POLLWRNORM))
91 rc |= POLLOUT;
92
93 /*
94 * Check if this handle was signaled by WaitForMultipleObjects
95 */
96 if (wait_rc >= WAIT_OBJECT_0 && (object == (wait_rc - WAIT_OBJECT_0))
97 && (events & (POLLIN | POLLRDNORM))) {
98
99 /*
100 * Check if this file is stdin, and if so, if it is a console.
101 */
102 if (h == GetStdHandle(STD_INPUT_HANDLE) &&
103 PeekConsoleInput(h, &record, 1, &num_read) == 1) {
104
105 /*
106 * Handle the input console buffer differently,
107 * since it can signal on other events like
108 * window and mouse, but read can still block.
109 */
110 if (record.EventType == KEY_EVENT &&
111 record.Event.KeyEvent.bKeyDown) {
112 rc |= POLLIN;
113 } else {
114 /*
115 * Flush non-character events from the
116 * console buffer.
117 */
118 ReadConsoleInput(h, &record, 1, &num_read);
119 }
120 } else {
121 rc |= POLLIN;
122 }
123 }
124
125 return rc;
126}
127
128static int
129wsa_select_errno(int err)
130{
131 switch (err) {
132 case WSAEINTR:
133 case WSAEINPROGRESS:
134 errno = EINTR;
135 break;
136 case WSAEFAULT:
137 /*
138 * Windows uses WSAEFAULT for both resource allocation failures
139 * and arguments not being contained in the user's address
140 * space. So, we have to choose EFAULT or ENOMEM.
141 */
142 errno = EFAULT;
143 break;
144 case WSAEINVAL:
145 errno = EINVAL;
146 break;
147 case WSANOTINITIALISED:
148 errno = EPERM;
149 break;
150 case WSAENETDOWN:
151 errno = ENOMEM;
152 break;
153 }
154 return -1;
155}
156
157int
158poll(struct pollfd *pfds, nfds_t nfds, int timeout_ms)
159{
160 nfds_t i;
161 int timespent_ms, looptime_ms;
162
163#define FD_IS_SOCKET (1 << 0)
164 int fd_state[FD_SETSIZE];
165 int num_fds;
166
167 /*
168 * select machinery
169 */
170 fd_set rfds, wfds, efds;
171 int rc;
172 int num_sockets;
173
174 /*
175 * wait machinery
176 */
177 DWORD wait_rc;
178 HANDLE handles[FD_SETSIZE];
179 int num_handles;
180
181 if (pfds == NULL) {
182 errno = EINVAL;
183 return -1;
184 }
185
186 if (nfds <= 0) {
187 return 0;
188 }
189
190 FD_ZERO(&rfds);
191 FD_ZERO(&wfds);
192 FD_ZERO(&efds);
193 num_fds = 0;
194 num_sockets = 0;
195 num_handles = 0;
196
197 for (i = 0; i < nfds; i++) {
198 if ((int)pfds[i].fd < 0) {
199 continue;
200 }
201
202 if (is_socket(pfds[i].fd)) {
203 if (num_sockets >= FD_SETSIZE) {
204 errno = EINVAL;
205 return -1;
206 }
207
208 fd_state[num_fds] = FD_IS_SOCKET;
209
210 FD_SET(pfds[i].fd, &efds);
211
212 if (pfds[i].events &
213 (POLLIN | POLLRDNORM | POLLRDBAND)) {
214 FD_SET(pfds[i].fd, &rfds);
215 }
216
217 if (pfds[i].events &
218 (POLLOUT | POLLWRNORM | POLLWRBAND)) {
219 FD_SET(pfds[i].fd, &wfds);
220 }
221 num_sockets++;
222
223 } else {
224 if (num_handles >= FD_SETSIZE) {
225 errno = EINVAL;
226 return -1;
227 }
228
229 handles[num_handles++] =
230 (HANDLE)_get_osfhandle(pfds[i].fd);
231 }
232
233 num_fds++;
234 }
235
236 /*
237 * Determine if the files, pipes, sockets, consoles, etc. have signaled.
238 *
239 * Do this by alternating a loop between WaitForMultipleObjects for
240 * non-sockets and and select for sockets.
241 *
242 * I tried to implement this all in terms of WaitForMultipleObjects
243 * with a select-based 'poll' of the sockets at the end to get extra
244 * specific socket status.
245 *
246 * However, the cost of setting up an event handle for each socket and
247 * cleaning them up reliably was pretty high. Since the event handle
248 * associated with a socket is also global, creating a new one here
249 * cancels one that may exist externally to this function.
250 *
251 * At any rate, even if global socket event handles were not an issue,
252 * the 'FD_WRITE' status of a socket event handle does not behave in an
253 * expected fashion, being triggered by an edge on a write buffer rather
254 * than simply triggering if there is space available.
255 */
256 timespent_ms = 0;
257 wait_rc = 0;
258
259 if (timeout_ms < 0) {
260 timeout_ms = INFINITE;
261 }
262 looptime_ms = timeout_ms > 100 ? 100 : timeout_ms;
263
264 do {
265 struct timeval tv = {0, looptime_ms * 1000};
266
267 /*
268 * Check if any file handles have signaled
269 */
270 if (num_handles) {
271 wait_rc = WaitForMultipleObjects(num_handles, handles, FALSE, 0);
272 if (wait_rc == WAIT_FAILED) {
273 /*
274 * The documentation for WaitForMultipleObjects
275 * does not specify what values GetLastError
276 * may return here. Rather than enumerate
277 * badness like for wsa_select_errno, assume a
278 * general errno value.
279 */
280 errno = ENOMEM;
281 return 0;
282 }
283 }
284
285 /*
286 * If we signaled on a file handle, don't wait on the sockets.
287 */
288 if (wait_rc >= WAIT_OBJECT_0)
289 tv.tv_usec = 0;
290
291 /*
292 * Check if any sockets have signaled
293 */
294 rc = select(0, &rfds, &wfds, &efds, &tv);
295 if (rc == SOCKET_ERROR) {
296 return wsa_select_errno(WSAGetLastError());
297 }
298
299 if (wait_rc >= WAIT_OBJECT_0 || (num_sockets && rc > 0))
300 break;
301
302 timespent_ms += looptime_ms;
303
304 } while (timespent_ms < timeout_ms);
305
306 rc = 0;
307 num_handles = 0;
308 num_fds = 0;
309 for (i = 0; i < nfds; i++) {
310 pfds[i].revents = 0;
311
312 if ((int)pfds[i].fd < 0)
313 continue;
314
315 if (fd_state[num_fds] & FD_IS_SOCKET) {
316 pfds[i].revents = compute_select_revents(pfds[i].fd,
317 pfds[i].events, &rfds, &wfds, &efds);
318
319 } else {
320 pfds[i].revents = compute_wait_revents(
321 handles[num_handles], pfds[i].events, num_handles,
322 wait_rc);
323 num_handles++;
324 }
325
326 num_fds++;
327
328 if (pfds[i].revents)
329 rc++;
330 }
331
332 return rc;
333}
334