1 |
/* |
2 |
* ircd-hybrid: an advanced Internet Relay Chat Daemon(ircd). |
3 |
* |
4 |
* Copyright (C) 2001 Kevin L. Mitchell <klmitch@mit.edu> |
5 |
* Copyright (C) 2013 by the Hybrid Development Team. |
6 |
* |
7 |
* This program is free software; you can redistribute it and/or modify |
8 |
* it under the terms of the GNU General Public License as published by |
9 |
* the Free Software Foundation; either version 2 of the License, or |
10 |
* (at your option) any later version. |
11 |
* |
12 |
* This program is distributed in the hope that it will be useful, |
13 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 |
* GNU General Public License for more details. |
16 |
* |
17 |
* You should have received a copy of the GNU General Public License |
18 |
* along with this program; if not, write to the Free Software |
19 |
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
20 |
* USA |
21 |
*/ |
22 |
|
23 |
/*! \file ioengine_devpoll.c |
24 |
* \brief Solaris /dev/poll event engine. |
25 |
* \version $Id: ioengine_devpoll.c 2297 2013-06-19 11:57:38Z michael $ |
26 |
*/ |
27 |
|
28 |
#include "stdinc.h" |
29 |
#include "ioengine.h" |
30 |
#include "ircd.h" |
31 |
#include "memory.h" |
32 |
#include "log.h" |
33 |
#include "restart.h" |
34 |
|
35 |
#include <errno.h> |
36 |
#include <fcntl.h> |
37 |
#include <sys/devpoll.h> |
38 |
#include <sys/poll.h> |
39 |
#include <sys/socket.h> |
40 |
#include <sys/stat.h> |
41 |
#include <sys/types.h> |
42 |
#include <unistd.h> |
43 |
|
44 |
#define DEVPOLL_ERROR_THRESHOLD 20 /**< after 20 devpoll errors, restart */ |
45 |
#define ERROR_EXPIRE_TIME 3600 /**< expire errors after an hour */ |
46 |
|
47 |
/* Figure out what bits to set for read */ |
48 |
#if defined(POLLMSG) && defined(POLLIN) && defined(POLLRDNORM) |
49 |
# define POLLREADFLAGS (POLLMSG|POLLIN|POLLRDNORM) |
50 |
#elif defined(POLLIN) && defined(POLLRDNORM) |
51 |
# define POLLREADFLAGS (POLLIN|POLLRDNORM) |
52 |
#elif defined(POLLIN) |
53 |
# define POLLREADFLAGS POLLIN |
54 |
#elif defined(POLLRDNORM) |
55 |
# define POLLREADFLAGS POLLRDNORM |
56 |
#endif |
57 |
|
58 |
/* Figure out what bits to set for write */ |
59 |
#if defined(POLLOUT) && defined(POLLWRNORM) |
60 |
# define POLLWRITEFLAGS (POLLOUT|POLLWRNORM) |
61 |
#elif defined(POLLOUT) |
62 |
# define POLLWRITEFLAGS POLLOUT |
63 |
#elif defined(POLLWRNORM) |
64 |
# define POLLWRITEFLAGS POLLWRNORM |
65 |
#endif |
66 |
|
67 |
/** Array of active Socket structures, indexed by file descriptor. */ |
68 |
static struct Socket **sockList; |
69 |
/** Maximum file descriptor supported, plus one. */ |
70 |
static int devpoll_max; |
71 |
/** File descriptor for /dev/poll device. */ |
72 |
static int devpoll_fd; |
73 |
|
74 |
/** Number of recent errors from /dev/poll. */ |
75 |
static int errors; |
76 |
/** Periodic timer to forget errors. */ |
77 |
static struct Timer clear_error; |
78 |
|
79 |
/** Decrement the error count (once per hour). |
80 |
* @param[in] ev Expired timer event (ignored). |
81 |
*/ |
82 |
static void |
83 |
error_clear(struct Event *ev) |
84 |
{ |
85 |
if (!--errors) /* Remove timer when error count reaches 0 */ |
86 |
timer_del(ev_timer(ev)); |
87 |
} |
88 |
|
89 |
/** Initialize the /dev/poll engine. |
90 |
* @param[in] max_sockets Maximum number of file descriptors to support. |
91 |
* @return Non-zero on success, or zero on failure. |
92 |
*/ |
93 |
static int |
94 |
engine_init(int max_sockets) |
95 |
{ |
96 |
int i; |
97 |
|
98 |
if ((devpoll_fd = open("/dev/poll", O_RDWR)) < 0) |
99 |
{ |
100 |
ilog(LOG_TYPE_DEBUG, "/dev/poll engine cannot open device: %m"); |
101 |
return 0; /* engine cannot be initialized; defer */ |
102 |
} |
103 |
|
104 |
/* allocate necessary memory */ |
105 |
sockList = (struct Socket **)MyMalloc(sizeof(struct Socket *) * max_sockets); |
106 |
|
107 |
/* initialize the data */ |
108 |
for (i = 0; i < max_sockets; ++i) |
109 |
sockList[i] = 0; |
110 |
|
111 |
devpoll_max = max_sockets; /* Number of sockets allocated */ |
112 |
|
113 |
return 1; |
114 |
} |
115 |
|
116 |
/** Figure out what events go with a given state. |
117 |
* @param[in] state %Socket state to consider. |
118 |
* @param[in] events User-specified preferred event set. |
119 |
* @return Actual set of preferred events. |
120 |
*/ |
121 |
static unsigned int |
122 |
state_to_events(enum SocketState state, unsigned int events) |
123 |
{ |
124 |
switch (state) |
125 |
{ |
126 |
case SS_CONNECTING: /* connecting socket */ |
127 |
return SOCK_EVENT_WRITABLE; |
128 |
break; |
129 |
|
130 |
case SS_LISTENING: /* listening socket */ |
131 |
case SS_NOTSOCK: /* our signal socket */ |
132 |
return SOCK_EVENT_READABLE; |
133 |
break; |
134 |
|
135 |
case SS_CONNECTED: |
136 |
case SS_DATAGRAM: |
137 |
case SS_CONNECTDG: |
138 |
return events; /* ordinary socket */ |
139 |
break; |
140 |
} |
141 |
|
142 |
/*NOTREACHED*/ |
143 |
return 0; |
144 |
} |
145 |
|
146 |
/** Set the desired events for a socket. |
147 |
* @param[in,out] sock Socket to operate on. |
148 |
* @param[in] events User-specified preferred event set. |
149 |
*/ |
150 |
static void |
151 |
set_events(struct Socket *sock, unsigned int events) |
152 |
{ |
153 |
struct pollfd pfd; |
154 |
|
155 |
pfd.fd = s_fd(sock); |
156 |
|
157 |
if (s_ed_int(sock)) |
158 |
{ |
159 |
/* is one in /dev/poll already? */ |
160 |
pfd.events = POLLREMOVE; /* First, remove old pollfd */ |
161 |
|
162 |
ilog(LOG_TYPE_DEBUG, "devpoll: Removing old entry for socket %d [%p]", |
163 |
s_fd(sock), sock); |
164 |
|
165 |
if (write(devpoll_fd, &pfd, sizeof(pfd)) != sizeof(pfd)) |
166 |
{ |
167 |
event_generate(ET_ERROR, sock, errno); /* report error */ |
168 |
return; |
169 |
} |
170 |
|
171 |
s_ed_int(sock) = 0; /* mark that it's gone */ |
172 |
} |
173 |
|
174 |
if (!(events & SOCK_EVENT_MASK)) /* no events, so stop here */ |
175 |
return; |
176 |
|
177 |
pfd.events = 0; /* Now, set up new pollfd... */ |
178 |
if (events & SOCK_EVENT_READABLE) |
179 |
pfd.events |= POLLREADFLAGS; /* look for readable conditions */ |
180 |
if (events & SOCK_EVENT_WRITABLE) |
181 |
pfd.events |= POLLWRITEFLAGS; /* look for writable conditions */ |
182 |
|
183 |
ilog(LOG_TYPE_DEBUG, "devpoll: Registering interest on %d [%p] (state %s, " |
184 |
"mask [%s])", s_fd(sock), sock, state_to_name(s_state(sock)), |
185 |
sock_flags(s_events(sock))); |
186 |
|
187 |
if (write(devpoll_fd, &pfd, sizeof(pfd)) != sizeof(pfd)) |
188 |
{ |
189 |
event_generate(ET_ERROR, sock, errno); /* report error */ |
190 |
return; |
191 |
} |
192 |
|
193 |
s_ed_int(sock) = 1; /* mark that we've added a pollfd */ |
194 |
} |
195 |
|
196 |
/** Add a socket to the event engine. |
197 |
* @param[in] sock Socket to add to engine. |
198 |
* @return Non-zero on success, or zero on error. |
199 |
*/ |
200 |
static int |
201 |
engine_add(struct Socket* sock) |
202 |
{ |
203 |
assert(0 != sock); |
204 |
assert(0 == sockList[s_fd(sock)]); |
205 |
|
206 |
/* bounds-check... */ |
207 |
if (s_fd(sock) >= devpoll_max) |
208 |
{ |
209 |
ilog(LOG_TYPE_DEBUG, |
210 |
"Attempt to add socket %d (> %d) to event engine", |
211 |
s_fd(sock), devpoll_max); |
212 |
return 0; |
213 |
} |
214 |
|
215 |
sockList[s_fd(sock)] = sock; /* add to list */ |
216 |
|
217 |
ilog(LOG_TYPE_DEBUG, "devpoll: Adding socket %d [%p], state %s, to engine", |
218 |
s_fd(sock), sock, state_to_name(s_state(sock))); |
219 |
|
220 |
/* set the correct events */ |
221 |
set_events(sock, state_to_events(s_state(sock), s_events(sock))); |
222 |
|
223 |
return 1; /* success */ |
224 |
} |
225 |
|
226 |
/** Handle state transition for a socket. |
227 |
* @param[in] sock Socket changing state. |
228 |
* @param[in] new_state New state for socket. |
229 |
*/ |
230 |
static void |
231 |
engine_state(struct Socket* sock, enum SocketState new_state) |
232 |
{ |
233 |
assert(0 != sock); |
234 |
assert(sock == sockList[s_fd(sock)]); |
235 |
|
236 |
ilog(LOG_TYPE_DEBUG, "devpoll: Changing state for socket %p to %s", sock, |
237 |
state_to_name(new_state)); |
238 |
|
239 |
/* set the correct events */ |
240 |
set_events(sock, state_to_events(new_state, s_events(sock))); |
241 |
} |
242 |
|
243 |
/** Handle change to preferred socket events. |
244 |
* @param[in] sock Socket getting new interest list. |
245 |
* @param[in] new_events New set of interesting events for socket. |
246 |
*/ |
247 |
static void |
248 |
engine_events(struct Socket* sock, unsigned int new_events) |
249 |
{ |
250 |
assert(0 != sock); |
251 |
assert(sock == sockList[s_fd(sock)]); |
252 |
|
253 |
ilog(LOG_TYPE_DEBUG,"devpoll: Changing event mask for socket %p to [%s]", |
254 |
sock, sock_flags(new_events)); |
255 |
|
256 |
/* set the correct events */ |
257 |
set_events(sock, state_to_events(s_state(sock), new_events)); |
258 |
} |
259 |
|
260 |
/** Remove a socket from the event engine. |
261 |
* @param[in] sock Socket being destroyed. |
262 |
*/ |
263 |
static void |
264 |
engine_delete(struct Socket* sock) |
265 |
{ |
266 |
assert(0 != sock); |
267 |
assert(sock == sockList[s_fd(sock)]); |
268 |
|
269 |
ilog(LOG_TYPE_DEBUG, "devpoll: Deleting socket %d [%p], state %s", |
270 |
s_fd(sock), sock, state_to_name(s_state(sock))); |
271 |
|
272 |
set_events(sock, 0); /* get rid of the socket */ |
273 |
|
274 |
sockList[s_fd(sock)] = 0; /* zero the socket list entry */ |
275 |
} |
276 |
|
277 |
/** Run engine event loop. |
278 |
* @param[in] gen Lists of generators of various types. |
279 |
*/ |
280 |
static void |
281 |
engine_loop(struct Generators* gen) |
282 |
{ |
283 |
struct dvpoll dopoll; |
284 |
struct pollfd *polls; |
285 |
int polls_count; |
286 |
struct Socket* sock; |
287 |
int nfds; |
288 |
int i; |
289 |
int errcode; |
290 |
size_t codesize; |
291 |
|
292 |
if ((polls_count = 64) < 20) /* XXX */ |
293 |
polls_count = 20; |
294 |
polls = MyMalloc(sizeof(struct pollfd) * polls_count); |
295 |
|
296 |
while (running) |
297 |
{ |
298 |
if ((i = 64 /* XXX */) >= 20 && i != polls_count) |
299 |
{ |
300 |
polls = MyRealloc(polls, sizeof(struct pollfd) * i); |
301 |
polls_count = i; |
302 |
} |
303 |
|
304 |
dopoll.dp_fds = polls; /* Set up the struct dvpoll */ |
305 |
dopoll.dp_nfds = polls_count; |
306 |
|
307 |
/* calculate the proper timeout */ |
308 |
dopoll.dp_timeout = timer_next(gen) ? (timer_next(gen) - CurrentTime) * 1000 : -1; |
309 |
|
310 |
ilog(LOG_TYPE_DEBUG, "devpoll: delay: %Tu (%Tu) %d", timer_next(gen), |
311 |
CurrentTime, dopoll.dp_timeout); |
312 |
|
313 |
/* check for active files */ |
314 |
nfds = ioctl(devpoll_fd, DP_POLL, &dopoll); |
315 |
|
316 |
CurrentTime = time(0); /* set current time... */ |
317 |
|
318 |
if (nfds < 0) |
319 |
{ |
320 |
if (errno != EINTR) |
321 |
{ |
322 |
/* ignore interrupts */ |
323 |
/* Log the poll error */ |
324 |
ilog(LOG_TYPE_DEBUG, "ioctl(DP_POLL) error: %m"); |
325 |
|
326 |
if (!errors++) |
327 |
timer_add(timer_init(&clear_error), error_clear, 0, TT_PERIODIC, ERROR_EXPIRE_TIME); |
328 |
else if (errors > DEVPOLL_ERROR_THRESHOLD) /* Too many errors... */ |
329 |
restart("too many /dev/poll errors"); |
330 |
} |
331 |
|
332 |
/* |
333 |
* Old code did a sleep(1) here; with usage these days, |
334 |
* that may be too expensive |
335 |
*/ |
336 |
continue; |
337 |
} |
338 |
|
339 |
for (i = 0; i < nfds; i++) |
340 |
{ |
341 |
assert(-1 < polls[i].fd); |
342 |
|
343 |
sock = sockList[polls[i].fd]; |
344 |
if (!sock) /* slots may become empty while processing events */ |
345 |
continue; |
346 |
|
347 |
assert(s_fd(sock) == polls[i].fd); |
348 |
|
349 |
gen_ref_inc(sock); /* can't have it going away on us */ |
350 |
|
351 |
ilog(LOG_TYPE_DEBUG, "devpoll: Checking socket %p (fd %d) state %s, " |
352 |
"events %s", sock, s_fd(sock), state_to_name(s_state(sock)), |
353 |
sock_flags(s_events(sock))); |
354 |
|
355 |
if (s_state(sock) != SS_NOTSOCK) |
356 |
{ |
357 |
errcode = 0; /* check for errors on socket */ |
358 |
codesize = sizeof(errcode); |
359 |
|
360 |
if (getsockopt(s_fd(sock), SOL_SOCKET, SO_ERROR, &errcode, &codesize) < 0) |
361 |
errcode = errno; /* work around Solaris implementation */ |
362 |
|
363 |
if (errcode) |
364 |
{ |
365 |
/* an error occurred; generate an event */ |
366 |
ilog(LOG_TYPE_DEBUG, "devpoll: Error %d on fd %d, socket %p", |
367 |
errcode, s_fd(sock), sock); |
368 |
event_generate(ET_ERROR, sock, errcode); |
369 |
gen_ref_dec(sock); /* careful not to leak reference counts */ |
370 |
continue; |
371 |
} |
372 |
} |
373 |
|
374 |
assert(!(polls[i].revents & POLLERR)); |
375 |
|
376 |
#ifdef POLLHUP |
377 |
if (polls[i].revents & POLLHUP) |
378 |
{ |
379 |
/* hang-up on socket */ |
380 |
ilog(LOG_TYPE_DEBUG, "devpoll: EOF from client (POLLHUP)"); |
381 |
event_generate(ET_EOF, sock, 0); |
382 |
nfds--; |
383 |
continue; |
384 |
} |
385 |
#endif /* POLLHUP */ |
386 |
|
387 |
switch (s_state(sock)) |
388 |
{ |
389 |
case SS_CONNECTING: |
390 |
if (polls[i].revents & POLLWRITEFLAGS) |
391 |
{ |
392 |
/* connection completed */ |
393 |
ilog(LOG_TYPE_DEBUG, "devpoll: Connection completed"); |
394 |
event_generate(ET_CONNECT, sock, 0); |
395 |
} |
396 |
break; |
397 |
|
398 |
case SS_LISTENING: |
399 |
if (polls[i].revents & POLLREADFLAGS) |
400 |
{ |
401 |
/* connect. to be accept. */ |
402 |
ilog(LOG_TYPE_DEBUG, "devpoll: Ready for accept"); |
403 |
event_generate(ET_ACCEPT, sock, 0); |
404 |
} |
405 |
break; |
406 |
|
407 |
case SS_NOTSOCK: |
408 |
if (polls[i].revents & POLLREADFLAGS) |
409 |
{ |
410 |
/* data on socket */ |
411 |
/* can't peek; it's not a socket */ |
412 |
ilog(LOG_TYPE_DEBUG, "devpoll: non-socket readable"); |
413 |
event_generate(ET_READ, sock, 0); |
414 |
} |
415 |
break; |
416 |
|
417 |
case SS_CONNECTED: |
418 |
if (polls[i].revents & POLLREADFLAGS) |
419 |
{ |
420 |
/* data on socket */ |
421 |
char c; |
422 |
|
423 |
switch (recv(s_fd(sock), &c, 1, MSG_PEEK)) |
424 |
{ |
425 |
/* check EOF */ |
426 |
case -1: /* error occurred?!? */ |
427 |
if (errno == EAGAIN) |
428 |
{ |
429 |
ilog(LOG_TYPE_DEBUG, "devpoll: Resource temporarily " |
430 |
"unavailable?"); |
431 |
continue; |
432 |
} |
433 |
|
434 |
ilog(LOG_TYPE_DEBUG, "devpoll: Uncaught error!"); |
435 |
event_generate(ET_ERROR, sock, errno); |
436 |
break; |
437 |
|
438 |
case 0: /* EOF from client */ |
439 |
ilog(LOG_TYPE_DEBUG, "devpoll: EOF from client"); |
440 |
event_generate(ET_EOF, sock, 0); |
441 |
break; |
442 |
|
443 |
default: /* some data can be read */ |
444 |
ilog(LOG_TYPE_DEBUG, "devpoll: Data to be read"); |
445 |
event_generate(ET_READ, sock, 0); |
446 |
break; |
447 |
} |
448 |
} |
449 |
|
450 |
if (polls[i].revents & POLLWRITEFLAGS) |
451 |
{ |
452 |
/* socket writable */ |
453 |
ilog(LOG_TYPE_DEBUG, "devpoll: Data can be written"); |
454 |
event_generate(ET_WRITE, sock, 0); |
455 |
} |
456 |
break; |
457 |
|
458 |
case SS_DATAGRAM: case SS_CONNECTDG: |
459 |
if (polls[i].revents & POLLREADFLAGS) |
460 |
{ |
461 |
/* socket readable */ |
462 |
ilog(LOG_TYPE_DEBUG, "devpoll: Datagram to be read"); |
463 |
event_generate(ET_READ, sock, 0); |
464 |
} |
465 |
|
466 |
if (polls[i].revents & POLLWRITEFLAGS) |
467 |
{ |
468 |
/* socket writable */ |
469 |
ilog(LOG_TYPE_DEBUG, "devpoll: Datagram can be written"); |
470 |
event_generate(ET_WRITE, sock, 0); |
471 |
} |
472 |
break; |
473 |
} |
474 |
|
475 |
gen_ref_dec(sock); /* we're done with it */ |
476 |
} |
477 |
|
478 |
timer_run(); /* execute any pending timers */ |
479 |
} |
480 |
} |
481 |
|
482 |
/** Descriptor for /dev/poll event engine. */ |
483 |
struct Engine engine_devpoll = |
484 |
{ |
485 |
"/dev/poll", /* Engine name */ |
486 |
engine_init, /* Engine initialization function */ |
487 |
0, /* Engine signal registration function */ |
488 |
engine_add, /* Engine socket registration function */ |
489 |
engine_state, /* Engine socket state change function */ |
490 |
engine_events, /* Engine socket events mask function */ |
491 |
engine_delete, /* Engine socket deletion function */ |
492 |
engine_loop /* Core engine event loop */ |
493 |
}; |