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