Merged silc_1_1_branch to trunk.
[silc.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_calloc(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     silc_free(internal);
393     return NULL;
394   }
395
396   silc_schedule_task_add_timeout(schedule, silc_schedule_wakeup_init,
397                                  internal, 0, 0);
398 #endif /* SILC_THREADS */
399
400   internal->app_context = app_context;
401
402   for (i = 0; i < SIGNAL_COUNT; i++) {
403     signal_call[i].sig = 0;
404     signal_call[i].call = FALSE;
405     signal_call[i].schedule = schedule;
406   }
407
408   return (void *)internal;
409 }
410
411 void silc_schedule_internal_signals_block(SilcSchedule schedule,
412                                           void *context);
413 void silc_schedule_internal_signals_unblock(SilcSchedule schedule,
414                                             void *context);
415
416 /* Uninitializes the platform specific scheduler context. */
417
418 void silc_schedule_internal_uninit(SilcSchedule schedule, void *context)
419 {
420   SilcUnixScheduler internal = (SilcUnixScheduler)context;
421
422   if (!internal)
423     return;
424
425 #ifdef SILC_THREADS
426   close(internal->wakeup_pipe[0]);
427   close(internal->wakeup_pipe[1]);
428 #endif
429
430 #if defined(HAVE_EPOLL_WAIT)
431   close(internal->epfd);
432   silc_free(internal->fds);
433 #elif defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
434   silc_free(internal->fds);
435 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
436
437   silc_free(internal);
438 }
439
440 /* Wakes up the scheduler */
441
442 void silc_schedule_internal_wakeup(SilcSchedule schedule, void *context)
443 {
444 #ifdef SILC_THREADS
445   SilcUnixScheduler internal = (SilcUnixScheduler)context;
446
447   if (!internal || !internal->wakeup_task)
448     return;
449
450   SILC_LOG_DEBUG(("Wakeup"));
451
452   (void)write(internal->wakeup_pipe[1], "!", 1);
453 #endif
454 }
455
456 /* Signal handler */
457
458 static void silc_schedule_internal_sighandler(int signal)
459 {
460   int i;
461
462   SILC_LOG_DEBUG(("Start"));
463
464   for (i = 0; i < SIGNAL_COUNT; i++) {
465     if (signal_call[i].sig == signal) {
466       signal_call[i].call = TRUE;
467       signal_call[i].schedule->signal_tasks = TRUE;
468       SILC_LOG_DEBUG(("Scheduling signal %d to be called",
469                       signal_call[i].sig));
470       break;
471     }
472   }
473 }
474
475 void silc_schedule_internal_signal_register(SilcSchedule schedule,
476                                             void *context,
477                                             SilcUInt32 sig,
478                                             SilcTaskCallback callback,
479                                             void *callback_context)
480 {
481   SilcUnixScheduler internal = (SilcUnixScheduler)context;
482   int i;
483
484   if (!internal)
485     return;
486
487   SILC_LOG_DEBUG(("Registering signal %d", sig));
488
489   silc_schedule_internal_signals_block(schedule, context);
490
491   for (i = 0; i < SIGNAL_COUNT; i++) {
492     if (!signal_call[i].sig) {
493       signal_call[i].sig = sig;
494       signal_call[i].callback = callback;
495       signal_call[i].context = callback_context;
496       signal_call[i].call = FALSE;
497       signal(sig, silc_schedule_internal_sighandler);
498       break;
499     }
500   }
501
502   silc_schedule_internal_signals_unblock(schedule, context);
503   sigaddset(&internal->signals, sig);
504 }
505
506 void silc_schedule_internal_signal_unregister(SilcSchedule schedule,
507                                               void *context,
508                                               SilcUInt32 sig)
509 {
510   SilcUnixScheduler internal = (SilcUnixScheduler)context;
511   int i;
512
513   if (!internal)
514     return;
515
516   SILC_LOG_DEBUG(("Unregistering signal %d", sig));
517
518   silc_schedule_internal_signals_block(schedule, context);
519
520   for (i = 0; i < SIGNAL_COUNT; i++) {
521     if (signal_call[i].sig == sig) {
522       signal_call[i].sig = 0;
523       signal_call[i].callback = NULL;
524       signal_call[i].context = NULL;
525       signal_call[i].call = FALSE;
526       signal(sig, SIG_DFL);
527     }
528   }
529
530   silc_schedule_internal_signals_unblock(schedule, context);
531   sigdelset(&internal->signals, sig);
532 }
533
534 /* Call all signals */
535
536 void silc_schedule_internal_signals_call(SilcSchedule schedule, void *context)
537 {
538   SilcUnixScheduler internal = (SilcUnixScheduler)context;
539   int i;
540
541   SILC_LOG_DEBUG(("Start"));
542
543   if (!internal)
544     return;
545
546   silc_schedule_internal_signals_block(schedule, context);
547
548   for (i = 0; i < SIGNAL_COUNT; i++) {
549     if (signal_call[i].call &&
550         signal_call[i].callback) {
551       SILC_LOG_DEBUG(("Calling signal %d callback",
552                       signal_call[i].sig));
553       silc_schedule_internal_signals_unblock(schedule, context);
554       signal_call[i].callback(schedule, internal->app_context,
555                               SILC_TASK_INTERRUPT,
556                               signal_call[i].sig,
557                               signal_call[i].context);
558       signal_call[i].call = FALSE;
559       silc_schedule_internal_signals_block(schedule, context);
560     }
561   }
562
563   silc_schedule_internal_signals_unblock(schedule, context);
564 }
565
566 /* Block registered signals in scheduler. */
567
568 void silc_schedule_internal_signals_block(SilcSchedule schedule, void *context)
569 {
570   SilcUnixScheduler internal = (SilcUnixScheduler)context;
571
572   if (!internal)
573     return;
574
575   sigprocmask(SIG_BLOCK, &internal->signals, &internal->signals_blocked);
576 }
577
578 /* Unblock registered signals in schedule. */
579
580 void silc_schedule_internal_signals_unblock(SilcSchedule schedule,
581                                             void *context)
582 {
583   SilcUnixScheduler internal = (SilcUnixScheduler)context;
584
585   if (!internal)
586     return;
587
588   sigprocmask(SIG_SETMASK, &internal->signals_blocked, NULL);
589 }
590
591 const SilcScheduleOps schedule_ops =
592 {
593   silc_schedule_internal_init,
594   silc_schedule_internal_uninit,
595 #if defined(HAVE_EPOLL_WAIT)
596   silc_epoll,
597 #elif defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
598   silc_poll,
599 #else
600   silc_select,
601 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
602   silc_schedule_internal_schedule_fd,
603   silc_schedule_internal_wakeup,
604   silc_schedule_internal_signal_register,
605   silc_schedule_internal_signal_unregister,
606   silc_schedule_internal_signals_call,
607   silc_schedule_internal_signals_block,
608   silc_schedule_internal_signals_unblock,
609 };