Added epoll(7) support to SILC Scheduler.
[crypto.git] / lib / silcutil / unix / silcunixschedule.c
1 /*
2
3   silcunixschedule.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 1998 - 2006 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; version 2 of the License.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18 */
19 /* $Id$ */
20
21 #include "silc.h"
22
23 #if defined(HAVE_EPOLL_WAIT)
24 #include <sys/epoll.h>
25 #elif defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
26 #include <poll.h>
27 #endif
28
29 const SilcScheduleOps schedule_ops;
30
31 /* Internal context. */
32 typedef struct {
33 #if defined(HAVE_EPOLL_WAIT)
34   struct epoll_event *fds;
35   SilcUInt32 fds_count;
36   int epfd;
37 #elif defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
38   struct rlimit nofile;
39   struct pollfd *fds;
40   SilcUInt32 fds_count;
41 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
42   void *app_context;
43   int wakeup_pipe[2];
44   SilcTask wakeup_task;
45   sigset_t signals;
46   sigset_t signals_blocked;
47 } *SilcUnixScheduler;
48
49 typedef struct {
50   SilcUInt32 sig;
51   SilcTaskCallback callback;
52   void *context;
53   SilcBool call;
54   SilcSchedule schedule;
55 } SilcUnixSignal;
56
57 #define SIGNAL_COUNT 32
58 SilcUnixSignal signal_call[SIGNAL_COUNT];
59
60 #if defined(HAVE_EPOLL_WAIT)
61
62 /* Linux's fast epoll system (level triggered) */
63
64 int silc_epoll(SilcSchedule schedule, void *context)
65 {
66   SilcUnixScheduler internal = context;
67   SilcTaskFd task;
68   struct epoll_event *fds = internal->fds;
69   SilcUInt32 fds_count = internal->fds_count;
70   int ret, i, timeout = -1;
71
72   /* Allocate larger fd table if needed */
73   i = silc_hash_table_count(schedule->fd_queue);
74   if (i > fds_count) {
75     fds = silc_realloc(internal->fds, sizeof(*internal->fds) *
76                        (fds_count + (i / 2)));
77     if (silc_likely(fds)) {
78       internal->fds = fds;
79       internal->fds_count = fds_count = fds_count + (i / 2);
80     }
81   }
82
83   if (schedule->has_timeout)
84     timeout = ((schedule->timeout.tv_sec * 1000) +
85                (schedule->timeout.tv_usec / 1000));
86
87   SILC_SCHEDULE_UNLOCK(schedule);
88   ret = epoll_wait(internal->epfd, fds, fds_count, timeout);
89   SILC_SCHEDULE_LOCK(schedule);
90   if (ret <= 0)
91     return ret;
92
93   silc_list_init(schedule->fd_dispatch, struct SilcTaskStruct, next);
94
95   for (i = 0; i < ret; i++) {
96     task = fds[i].data.ptr;
97     task->revents = 0;
98     if (!task->header.valid || !task->events) {
99       epoll_ctl(internal->epfd, EPOLL_CTL_DEL, task->fd, &fds[i]);
100       continue;
101     }
102     if (fds[i].events & EPOLLIN)
103       task->revents |= SILC_TASK_READ;
104     if (fds[i].events & EPOLLOUT)
105       task->revents |= SILC_TASK_WRITE;
106     silc_list_add(schedule->fd_dispatch, task);
107   }
108
109   return ret;
110 }
111
112 #elif defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
113
114 /* Calls normal poll() system call. */
115
116 int silc_poll(SilcSchedule schedule, void *context)
117 {
118   SilcUnixScheduler internal = context;
119   SilcHashTableList htl;
120   SilcTaskFd task;
121   struct pollfd *fds = internal->fds;
122   SilcUInt32 fds_count = internal->fds_count;
123   int fd, ret, i = 0, timeout = -1;
124
125   silc_hash_table_list(schedule->fd_queue, &htl);
126   while (silc_hash_table_get(&htl, (void **)&fd, (void **)&task)) {
127     if (!task->events)
128       continue;
129
130     /* Allocate larger fd table if needed */
131     if (i >= fds_count) {
132       struct rlimit nofile;
133
134       fds = silc_realloc(internal->fds, sizeof(*internal->fds) *
135                          (fds_count + (fds_count / 2)));
136       if (silc_unlikely(!fds))
137         break;
138       internal->fds = fds;
139       internal->fds_count = fds_count = fds_count + (fds_count / 2);
140       internal->nofile.rlim_cur = fds_count;
141       if (fds_count > internal->nofile.rlim_max)
142         internal->nofile.rlim_max = fds_count;
143       if (setrlimit(RLIMIT_NOFILE, &nofile) < 0)
144         break;
145     }
146
147     fds[i].fd = fd;
148     fds[i].events = 0;
149     task->revents = fds[i].revents = 0;
150
151     if (task->events & SILC_TASK_READ)
152       fds[i].events |= (POLLIN | POLLPRI);
153     if (task->events & SILC_TASK_WRITE)
154       fds[i].events |= POLLOUT;
155     i++;
156   }
157   silc_hash_table_list_reset(&htl);
158   silc_list_init(schedule->fd_dispatch, struct SilcTaskStruct, next);
159
160   if (schedule->has_timeout)
161     timeout = ((schedule->timeout.tv_sec * 1000) +
162                (schedule->timeout.tv_usec / 1000));
163
164   fds_count = i;
165   SILC_SCHEDULE_UNLOCK(schedule);
166   ret = poll(fds, fds_count, timeout);
167   SILC_SCHEDULE_LOCK(schedule);
168   if (ret <= 0)
169     return ret;
170
171   for (i = 0; i < fds_count; i++) {
172     if (!fds[i].revents)
173       continue;
174     if (!silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fds[i].fd),
175                               NULL, (void **)&task))
176       continue;
177     if (!task->header.valid || !task->events)
178       continue;
179
180     fd = fds[i].revents;
181     if (fd & (POLLIN | POLLPRI | POLLERR | POLLHUP | POLLNVAL))
182       task->revents |= SILC_TASK_READ;
183     if (fd & POLLOUT)
184       task->revents |= SILC_TASK_WRITE;
185     silc_list_add(schedule->fd_dispatch, task);
186   }
187
188   return ret;
189 }
190
191 #else
192
193 /* Calls normal select() system call. */
194
195 int silc_select(SilcSchedule schedule, void *context)
196 {
197   SilcHashTableList htl;
198   SilcTaskFd task;
199   fd_set in, out;
200   int fd, max_fd = 0, ret;
201
202   FD_ZERO(&in);
203   FD_ZERO(&out);
204
205   silc_hash_table_list(schedule->fd_queue, &htl);
206   while (silc_hash_table_get(&htl, (void **)&fd, (void **)&task)) {
207     if (!task->events)
208       continue;
209
210 #ifdef FD_SETSIZE
211     if (fd >= FD_SETSIZE)
212       break;
213 #endif /* FD_SETSIZE */
214
215     if (fd > max_fd)
216       max_fd = fd;
217
218     if (task->events & SILC_TASK_READ)
219       FD_SET(fd, &in);
220     if (task->events & SILC_TASK_WRITE)
221       FD_SET(fd, &out);
222
223     task->revents = 0;
224   }
225   silc_hash_table_list_reset(&htl);
226   silc_list_init(schedule->fd_dispatch, struct SilcTaskStruct, next);
227
228   SILC_SCHEDULE_UNLOCK(schedule);
229   ret = select(max_fd + 1, &in, &out, NULL, (schedule->has_timeout ?
230                                              &schedule->timeout : NULL));
231   SILC_SCHEDULE_LOCK(schedule);
232   if (ret <= 0)
233     return ret;
234
235   silc_hash_table_list(schedule->fd_queue, &htl);
236   while (silc_hash_table_get(&htl, (void **)&fd, (void **)&task)) {
237     if (!task->header.valid || !task->events)
238       continue;
239
240 #ifdef FD_SETSIZE
241     if (fd >= FD_SETSIZE)
242       break;
243 #endif /* FD_SETSIZE */
244
245     if (FD_ISSET(fd, &in))
246       task->revents |= SILC_TASK_READ;
247     if (FD_ISSET(fd, &out))
248       task->revents |= SILC_TASK_WRITE;
249     silc_list_add(schedule->fd_dispatch, task);
250   }
251   silc_hash_table_list_reset(&htl);
252
253   return ret;
254 }
255
256 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
257
258 /* Schedule `task' with events `event_mask'. Zero `event_mask' unschedules. */
259
260 SilcBool silc_schedule_internal_schedule_fd(SilcSchedule schedule,
261                                             void *context,
262                                             SilcTaskFd task,
263                                             SilcTaskEvent event_mask)
264 {
265 #if defined(HAVE_EPOLL_WAIT)
266   SilcUnixScheduler internal = (SilcUnixScheduler)context;
267   struct epoll_event event;
268
269   event.events = 0;
270   if (task->events & SILC_TASK_READ)
271     event.events |= (EPOLLIN | EPOLLPRI);
272   if (task->events & SILC_TASK_WRITE)
273     event.events |= EPOLLOUT;
274
275   /* Zero mask unschedules task */
276   if (silc_unlikely(!event.events)) {
277     epoll_ctl(internal->epfd, EPOLL_CTL_DEL, task->fd, &event);
278     return TRUE;
279   }
280
281   /* Schedule the task */
282   if (silc_unlikely(!task->scheduled)) {
283     event.data.ptr = task;
284     epoll_ctl(internal->epfd, EPOLL_CTL_ADD, task->fd, &event);
285     task->scheduled = TRUE;
286     return TRUE;
287   }
288
289   /* Schedule for specific mask */
290   event.data.ptr = task;
291   epoll_ctl(internal->epfd, EPOLL_CTL_MOD, task->fd, &event);
292 #endif /* HAVE_EPOLL_WAIT */
293   return TRUE;
294 }
295
296 #ifdef SILC_THREADS
297
298 SILC_TASK_CALLBACK(silc_schedule_wakeup_cb)
299 {
300   SilcUnixScheduler internal = (SilcUnixScheduler)context;
301   unsigned char c;
302
303   SILC_LOG_DEBUG(("Wokeup"));
304
305   read(internal->wakeup_pipe[0], &c, 1);
306 }
307
308 #endif /* SILC_THREADS */
309
310 /* Initializes the platform specific scheduler.  This for example initializes
311    the wakeup mechanism of the scheduler.  In multi-threaded environment
312    the scheduler needs to be wakenup when tasks are added or removed from
313    the task queues.  Returns context to the platform specific scheduler. */
314
315 void *silc_schedule_internal_init(SilcSchedule schedule,
316                                   void *app_context)
317 {
318   SilcUnixScheduler internal;
319   int i;
320
321   internal = silc_calloc(1, sizeof(*internal));
322   if (!internal)
323     return NULL;
324
325 #if defined(HAVE_EPOLL_WAIT)
326   internal->epfd = epoll_create(4);
327   if (internal->epfd < 0)
328     return NULL;
329   internal->fds = silc_calloc(4, sizeof(*internal->fds));
330   if (!internal->fds) {
331     close(internal->epfd);
332     return NULL;
333   }
334   internal->fds_count = 4;
335 #elif defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
336   getrlimit(RLIMIT_NOFILE, &internal->nofile);
337
338   if (schedule->max_tasks > 0) {
339     internal->nofile.rlim_cur = schedule->max_tasks;
340     if (schedule->max_tasks > internal->nofile.rlim_max)
341       internal->nofile.rlim_max = schedule->max_tasks;
342     setrlimit(RLIMIT_NOFILE, &internal->nofile);
343     getrlimit(RLIMIT_NOFILE, &internal->nofile);
344     schedule->max_tasks = internal->nofile.rlim_max;
345   }
346
347   internal->fds = silc_calloc(internal->nofile.rlim_cur,
348                               sizeof(*internal->fds));
349   if (!internal->fds)
350     return NULL;
351   internal->fds_count = internal->nofile.rlim_cur;
352 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
353
354   sigemptyset(&internal->signals);
355
356 #ifdef SILC_THREADS
357   if (pipe(internal->wakeup_pipe)) {
358     SILC_LOG_ERROR(("pipe() fails: %s", strerror(errno)));
359     silc_free(internal);
360     return NULL;
361   }
362
363   internal->wakeup_task =
364     silc_schedule_task_add(schedule, internal->wakeup_pipe[0],
365                            silc_schedule_wakeup_cb, internal,
366                            0, 0, SILC_TASK_FD);
367   if (!internal->wakeup_task) {
368     SILC_LOG_ERROR(("Could not add a wakeup task, threads won't work"));
369     close(internal->wakeup_pipe[0]);
370     close(internal->wakeup_pipe[1]);
371     silc_free(internal);
372     return NULL;
373   }
374 #endif
375
376   internal->app_context = app_context;
377
378   for (i = 0; i < SIGNAL_COUNT; i++) {
379     signal_call[i].sig = 0;
380     signal_call[i].call = FALSE;
381     signal_call[i].schedule = schedule;
382   }
383
384   return (void *)internal;
385 }
386
387 void silc_schedule_internal_signals_block(SilcSchedule schedule,
388                                           void *context);
389 void silc_schedule_internal_signals_unblock(SilcSchedule schedule,
390                                             void *context);
391
392 /* Uninitializes the platform specific scheduler context. */
393
394 void silc_schedule_internal_uninit(SilcSchedule schedule, void *context)
395 {
396   SilcUnixScheduler internal = (SilcUnixScheduler)context;
397
398   if (!internal)
399     return;
400
401 #ifdef SILC_THREADS
402   close(internal->wakeup_pipe[0]);
403   close(internal->wakeup_pipe[1]);
404 #endif
405
406 #if defined(HAVE_EPOLL_WAIT)
407   close(internal->epfd);
408   silc_free(internal->fds);
409 #elif defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
410   silc_free(internal->fds);
411 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
412
413   silc_free(internal);
414 }
415
416 /* Wakes up the scheduler */
417
418 void silc_schedule_internal_wakeup(SilcSchedule schedule, void *context)
419 {
420 #ifdef SILC_THREADS
421   SilcUnixScheduler internal = (SilcUnixScheduler)context;
422
423   if (!internal)
424     return;
425
426   SILC_LOG_DEBUG(("Wakeup"));
427
428   write(internal->wakeup_pipe[1], "!", 1);
429 #endif
430 }
431
432 /* Signal handler */
433
434 static void silc_schedule_internal_sighandler(int signal)
435 {
436   int i;
437
438   for (i = 0; i < SIGNAL_COUNT; i++) {
439     if (signal_call[i].sig == signal) {
440       signal_call[i].call = TRUE;
441       signal_call[i].schedule->signal_tasks = TRUE;
442       SILC_LOG_DEBUG(("Scheduling signal %d to be called",
443                       signal_call[i].sig));
444       break;
445     }
446   }
447 }
448
449 void silc_schedule_internal_signal_register(SilcSchedule schedule,
450                                             void *context,
451                                             SilcUInt32 sig,
452                                             SilcTaskCallback callback,
453                                             void *callback_context)
454 {
455   SilcUnixScheduler internal = (SilcUnixScheduler)context;
456   int i;
457
458   if (!internal)
459     return;
460
461   SILC_LOG_DEBUG(("Registering signal %d", sig));
462
463   silc_schedule_internal_signals_block(schedule, context);
464
465   for (i = 0; i < SIGNAL_COUNT; i++) {
466     if (!signal_call[i].sig) {
467       signal_call[i].sig = sig;
468       signal_call[i].callback = callback;
469       signal_call[i].context = callback_context;
470       signal_call[i].call = FALSE;
471       signal(sig, silc_schedule_internal_sighandler);
472       break;
473     }
474   }
475
476   silc_schedule_internal_signals_unblock(schedule, context);
477   sigaddset(&internal->signals, sig);
478 }
479
480 void silc_schedule_internal_signal_unregister(SilcSchedule schedule,
481                                               void *context,
482                                               SilcUInt32 sig)
483 {
484   SilcUnixScheduler internal = (SilcUnixScheduler)context;
485   int i;
486
487   if (!internal)
488     return;
489
490   SILC_LOG_DEBUG(("Unregistering signal %d", sig));
491
492   silc_schedule_internal_signals_block(schedule, context);
493
494   for (i = 0; i < SIGNAL_COUNT; i++) {
495     if (signal_call[i].sig == sig) {
496       signal_call[i].sig = 0;
497       signal_call[i].callback = NULL;
498       signal_call[i].context = NULL;
499       signal_call[i].call = FALSE;
500       signal(sig, SIG_DFL);
501     }
502   }
503
504   silc_schedule_internal_signals_unblock(schedule, context);
505   sigdelset(&internal->signals, sig);
506 }
507
508 /* Call all signals */
509
510 void silc_schedule_internal_signals_call(SilcSchedule schedule, void *context)
511 {
512   SilcUnixScheduler internal = (SilcUnixScheduler)context;
513   int i;
514
515   SILC_LOG_DEBUG(("Start"));
516
517   if (!internal)
518     return;
519
520   silc_schedule_internal_signals_block(schedule, context);
521
522   for (i = 0; i < SIGNAL_COUNT; i++) {
523     if (signal_call[i].call &&
524         signal_call[i].callback) {
525       SILC_LOG_DEBUG(("Calling signal %d callback",
526                       signal_call[i].sig));
527       signal_call[i].callback(schedule, internal->app_context,
528                               SILC_TASK_INTERRUPT,
529                               signal_call[i].sig,
530                               signal_call[i].context);
531       signal_call[i].call = FALSE;
532     }
533   }
534
535   silc_schedule_internal_signals_unblock(schedule, context);
536 }
537
538 /* Block registered signals in scheduler. */
539
540 void silc_schedule_internal_signals_block(SilcSchedule schedule, void *context)
541 {
542   SilcUnixScheduler internal = (SilcUnixScheduler)context;
543
544   if (!internal)
545     return;
546
547   sigprocmask(SIG_BLOCK, &internal->signals, &internal->signals_blocked);
548 }
549
550 /* Unblock registered signals in schedule. */
551
552 void silc_schedule_internal_signals_unblock(SilcSchedule schedule,
553                                             void *context)
554 {
555   SilcUnixScheduler internal = (SilcUnixScheduler)context;
556
557   if (!internal)
558     return;
559
560   sigprocmask(SIG_SETMASK, &internal->signals_blocked, NULL);
561 }
562
563 const SilcScheduleOps schedule_ops =
564 {
565   silc_schedule_internal_init,
566   silc_schedule_internal_uninit,
567 #if defined(HAVE_EPOLL_WAIT)
568   silc_epoll,
569 #elif defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
570   silc_poll,
571 #else
572   silc_select,
573 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
574   silc_schedule_internal_schedule_fd,
575   silc_schedule_internal_wakeup,
576   silc_schedule_internal_signal_register,
577   silc_schedule_internal_signal_unregister,
578   silc_schedule_internal_signals_call,
579   silc_schedule_internal_signals_block,
580   silc_schedule_internal_signals_unblock,
581 };