Added SilcStack suppor to SILC Scheduler API.
[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 - 2007 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 | EPOLLPRI | EPOLLHUP | EPOLLERR))
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   if (!internal)
270     return TRUE;
271
272   SILC_LOG_DEBUG(("Scheduling fd %lu, mask %x", task->fd, event_mask));
273
274   memset(&event, 0, sizeof(event));
275   if (event_mask & SILC_TASK_READ)
276     event.events |= (EPOLLIN | EPOLLPRI);
277   if (event_mask & SILC_TASK_WRITE)
278     event.events |= EPOLLOUT;
279
280   /* Zero mask unschedules task */
281   if (silc_unlikely(!event.events)) {
282     if (epoll_ctl(internal->epfd, EPOLL_CTL_DEL, task->fd, &event)) {
283       SILC_LOG_DEBUG(("epoll_ctl (DEL): %s", strerror(errno)));
284       return FALSE;
285     }
286     return TRUE;
287   }
288
289   /* Schedule the task */
290   if (silc_unlikely(!task->scheduled)) {
291     event.data.ptr = task;
292     if (epoll_ctl(internal->epfd, EPOLL_CTL_ADD, task->fd, &event)) {
293       SILC_LOG_DEBUG(("epoll_ctl (ADD): %s", strerror(errno)));
294       return FALSE;
295     }
296     task->scheduled = TRUE;
297     return TRUE;
298   }
299
300   /* Schedule for specific mask */
301   event.data.ptr = task;
302   if (epoll_ctl(internal->epfd, EPOLL_CTL_MOD, task->fd, &event)) {
303     SILC_LOG_DEBUG(("epoll_ctl (MOD): %s", strerror(errno)));
304     return FALSE;
305   }
306 #endif /* HAVE_EPOLL_WAIT */
307   return TRUE;
308 }
309
310 #ifdef SILC_THREADS
311
312 SILC_TASK_CALLBACK(silc_schedule_wakeup_cb)
313 {
314   SilcUnixScheduler internal = (SilcUnixScheduler)context;
315   unsigned char c;
316
317   SILC_LOG_DEBUG(("Wokeup"));
318
319   (void)read(internal->wakeup_pipe[0], &c, 1);
320 }
321
322 SILC_TASK_CALLBACK(silc_schedule_wakeup_init)
323 {
324   SilcUnixScheduler internal = schedule->internal;
325
326   internal->wakeup_task =
327     silc_schedule_task_add(schedule, internal->wakeup_pipe[0],
328                            silc_schedule_wakeup_cb, internal,
329                            0, 0, SILC_TASK_FD);
330   if (!internal->wakeup_task) {
331     SILC_LOG_WARNING(("Could not add a wakeup task, threads won't work"));
332     close(internal->wakeup_pipe[0]);
333     return;
334   }
335   silc_schedule_internal_schedule_fd(schedule, internal,
336                                      (SilcTaskFd)internal->wakeup_task,
337                                      SILC_TASK_READ);
338 }
339 #endif /* SILC_THREADS */
340
341 /* Initializes the platform specific scheduler.  This for example initializes
342    the wakeup mechanism of the scheduler.  In multi-threaded environment
343    the scheduler needs to be woken up when tasks are added or removed from
344    the task queues.  Returns context to the platform specific scheduler. */
345
346 void *silc_schedule_internal_init(SilcSchedule schedule,
347                                   void *app_context)
348 {
349   SilcUnixScheduler internal;
350   int i;
351
352   internal = silc_scalloc(schedule->stack, 1, sizeof(*internal));
353   if (!internal)
354     return NULL;
355
356 #if defined(HAVE_EPOLL_WAIT)
357   internal->epfd = epoll_create(4);
358   if (internal->epfd < 0) {
359     SILC_LOG_ERROR(("epoll_create() failed: %s", strerror(errno)));
360     return NULL;
361   }
362   internal->fds = silc_calloc(4, sizeof(*internal->fds));
363   if (!internal->fds) {
364     close(internal->epfd);
365     return NULL;
366   }
367   internal->fds_count = 4;
368 #elif defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
369   getrlimit(RLIMIT_NOFILE, &internal->nofile);
370
371   if (schedule->max_tasks > 0) {
372     internal->nofile.rlim_cur = schedule->max_tasks;
373     if (schedule->max_tasks > internal->nofile.rlim_max)
374       internal->nofile.rlim_max = schedule->max_tasks;
375     setrlimit(RLIMIT_NOFILE, &internal->nofile);
376     getrlimit(RLIMIT_NOFILE, &internal->nofile);
377     schedule->max_tasks = internal->nofile.rlim_max;
378   }
379
380   internal->fds = silc_calloc(internal->nofile.rlim_cur,
381                               sizeof(*internal->fds));
382   if (!internal->fds)
383     return NULL;
384   internal->fds_count = internal->nofile.rlim_cur;
385 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
386
387   sigemptyset(&internal->signals);
388
389 #ifdef SILC_THREADS
390   if (pipe(internal->wakeup_pipe)) {
391     SILC_LOG_ERROR(("pipe() fails: %s", strerror(errno)));
392     return NULL;
393   }
394
395   silc_schedule_task_add_timeout(schedule, silc_schedule_wakeup_init,
396                                  internal, 0, 0);
397 #endif /* SILC_THREADS */
398
399   internal->app_context = app_context;
400
401   for (i = 0; i < SIGNAL_COUNT; i++) {
402     signal_call[i].sig = 0;
403     signal_call[i].call = FALSE;
404     signal_call[i].schedule = schedule;
405   }
406
407   return (void *)internal;
408 }
409
410 void silc_schedule_internal_signals_block(SilcSchedule schedule,
411                                           void *context);
412 void silc_schedule_internal_signals_unblock(SilcSchedule schedule,
413                                             void *context);
414
415 /* Uninitializes the platform specific scheduler context. */
416
417 void silc_schedule_internal_uninit(SilcSchedule schedule, void *context)
418 {
419   SilcUnixScheduler internal = (SilcUnixScheduler)context;
420
421   if (!internal)
422     return;
423
424 #ifdef SILC_THREADS
425   close(internal->wakeup_pipe[0]);
426   close(internal->wakeup_pipe[1]);
427 #endif
428
429 #if defined(HAVE_EPOLL_WAIT)
430   close(internal->epfd);
431   silc_free(internal->fds);
432 #elif defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
433   silc_free(internal->fds);
434 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
435 }
436
437 /* Wakes up the scheduler */
438
439 void silc_schedule_internal_wakeup(SilcSchedule schedule, void *context)
440 {
441 #ifdef SILC_THREADS
442   SilcUnixScheduler internal = (SilcUnixScheduler)context;
443
444   if (!internal || !internal->wakeup_task)
445     return;
446
447   SILC_LOG_DEBUG(("Wakeup"));
448
449   (void)write(internal->wakeup_pipe[1], "!", 1);
450 #endif
451 }
452
453 /* Signal handler */
454
455 static void silc_schedule_internal_sighandler(int signal)
456 {
457   int i;
458
459   SILC_LOG_DEBUG(("Start"));
460
461   for (i = 0; i < SIGNAL_COUNT; i++) {
462     if (signal_call[i].sig == signal) {
463       signal_call[i].call = TRUE;
464       signal_call[i].schedule->signal_tasks = TRUE;
465       SILC_LOG_DEBUG(("Scheduling signal %d to be called",
466                       signal_call[i].sig));
467       break;
468     }
469   }
470 }
471
472 void silc_schedule_internal_signal_register(SilcSchedule schedule,
473                                             void *context,
474                                             SilcUInt32 sig,
475                                             SilcTaskCallback callback,
476                                             void *callback_context)
477 {
478   SilcUnixScheduler internal = (SilcUnixScheduler)context;
479   int i;
480
481   if (!internal)
482     return;
483
484   SILC_LOG_DEBUG(("Registering signal %d", sig));
485
486   silc_schedule_internal_signals_block(schedule, context);
487
488   for (i = 0; i < SIGNAL_COUNT; i++) {
489     if (!signal_call[i].sig) {
490       signal_call[i].sig = sig;
491       signal_call[i].callback = callback;
492       signal_call[i].context = callback_context;
493       signal_call[i].call = FALSE;
494       signal(sig, silc_schedule_internal_sighandler);
495       break;
496     }
497   }
498
499   silc_schedule_internal_signals_unblock(schedule, context);
500   sigaddset(&internal->signals, sig);
501 }
502
503 void silc_schedule_internal_signal_unregister(SilcSchedule schedule,
504                                               void *context,
505                                               SilcUInt32 sig)
506 {
507   SilcUnixScheduler internal = (SilcUnixScheduler)context;
508   int i;
509
510   if (!internal)
511     return;
512
513   SILC_LOG_DEBUG(("Unregistering signal %d", sig));
514
515   silc_schedule_internal_signals_block(schedule, context);
516
517   for (i = 0; i < SIGNAL_COUNT; i++) {
518     if (signal_call[i].sig == sig) {
519       signal_call[i].sig = 0;
520       signal_call[i].callback = NULL;
521       signal_call[i].context = NULL;
522       signal_call[i].call = FALSE;
523       signal(sig, SIG_DFL);
524     }
525   }
526
527   silc_schedule_internal_signals_unblock(schedule, context);
528   sigdelset(&internal->signals, sig);
529 }
530
531 /* Call all signals */
532
533 void silc_schedule_internal_signals_call(SilcSchedule schedule, void *context)
534 {
535   SilcUnixScheduler internal = (SilcUnixScheduler)context;
536   int i;
537
538   SILC_LOG_DEBUG(("Start"));
539
540   if (!internal)
541     return;
542
543   silc_schedule_internal_signals_block(schedule, context);
544
545   for (i = 0; i < SIGNAL_COUNT; i++) {
546     if (signal_call[i].call &&
547         signal_call[i].callback) {
548       SILC_LOG_DEBUG(("Calling signal %d callback",
549                       signal_call[i].sig));
550       silc_schedule_internal_signals_unblock(schedule, context);
551       signal_call[i].callback(schedule, internal->app_context,
552                               SILC_TASK_INTERRUPT,
553                               signal_call[i].sig,
554                               signal_call[i].context);
555       signal_call[i].call = FALSE;
556       silc_schedule_internal_signals_block(schedule, context);
557     }
558   }
559
560   silc_schedule_internal_signals_unblock(schedule, context);
561 }
562
563 /* Block registered signals in scheduler. */
564
565 void silc_schedule_internal_signals_block(SilcSchedule schedule, void *context)
566 {
567   SilcUnixScheduler internal = (SilcUnixScheduler)context;
568
569   if (!internal)
570     return;
571
572   sigprocmask(SIG_BLOCK, &internal->signals, &internal->signals_blocked);
573 }
574
575 /* Unblock registered signals in schedule. */
576
577 void silc_schedule_internal_signals_unblock(SilcSchedule schedule,
578                                             void *context)
579 {
580   SilcUnixScheduler internal = (SilcUnixScheduler)context;
581
582   if (!internal)
583     return;
584
585   sigprocmask(SIG_SETMASK, &internal->signals_blocked, NULL);
586 }
587
588 const SilcScheduleOps schedule_ops =
589 {
590   silc_schedule_internal_init,
591   silc_schedule_internal_uninit,
592 #if defined(HAVE_EPOLL_WAIT)
593   silc_epoll,
594 #elif defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
595   silc_poll,
596 #else
597   silc_select,
598 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
599   silc_schedule_internal_schedule_fd,
600   silc_schedule_internal_wakeup,
601   silc_schedule_internal_signal_register,
602   silc_schedule_internal_signal_unregister,
603   silc_schedule_internal_signals_call,
604   silc_schedule_internal_signals_block,
605   silc_schedule_internal_signals_unblock,
606 };