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