Mark signals to be called when signal happens.
[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_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
24 #include <poll.h>
25 #endif
26
27 const SilcScheduleOps schedule_ops;
28
29 /* Internal context. */
30 typedef struct {
31 #if defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
32   struct rlimit nofile;
33   struct pollfd *fds;
34   SilcUInt32 fds_count;
35 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
36   void *app_context;
37   int wakeup_pipe[2];
38   SilcTask wakeup_task;
39   sigset_t signals;
40   sigset_t signals_blocked;
41 } *SilcUnixScheduler;
42
43 typedef struct {
44   SilcUInt32 signal;
45   SilcTaskCallback callback;
46   void *context;
47   SilcBool call;
48   SilcSchedule schedule;
49 } SilcUnixSignal;
50
51 #define SIGNAL_COUNT 32
52 SilcUnixSignal signal_call[SIGNAL_COUNT];
53
54 #if defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
55
56 /* Calls normal poll() system call. */
57
58 int silc_poll(SilcSchedule schedule, void *context)
59 {
60   SilcUnixScheduler internal = context;
61   SilcHashTableList htl;
62   SilcTaskFd task;
63   struct pollfd *fds = internal->fds;
64   SilcUInt32 fds_count = internal->fds_count;
65   int fd, ret, i = 0, timeout = -1;
66
67   silc_hash_table_list(schedule->fd_queue, &htl);
68   while (silc_hash_table_get(&htl, (void **)&fd, (void **)&task)) {
69     if (!task->events)
70       continue;
71
72     /* Allocate larger fd table if needed */
73     if (i >= fds_count) {
74       struct rlimit nofile;
75
76       fds = silc_realloc(internal->fds, sizeof(*internal->fds) *
77                          (fds_count + (fds_count / 2)));
78       if (!fds)
79         break;
80       internal->fds = fds;
81       internal->fds_count = fds_count = fds_count + (fds_count / 2);
82       internal->nofile.rlim_cur = fds_count;
83       if (fds_count > internal->nofile.rlim_max)
84         internal->nofile.rlim_max = fds_count;
85       if (setrlimit(RLIMIT_NOFILE, &nofile) < 0)
86         break;
87     }
88
89     fds[i].fd = fd;
90     fds[i].events = 0;
91     task->revents = fds[i].revents = 0;
92
93     if (task->events & SILC_TASK_READ)
94       fds[i].events |= (POLLIN | POLLPRI);
95     if (task->events & SILC_TASK_WRITE)
96       fds[i].events |= POLLOUT;
97     i++;
98   }
99   silc_hash_table_list_reset(&htl);
100
101   if (schedule->has_timeout)
102     timeout = ((schedule->timeout.tv_sec * 1000) +
103                (schedule->timeout.tv_usec / 1000));
104
105   fds_count = i;
106   SILC_SCHEDULE_UNLOCK(schedule);
107   ret = poll(fds, fds_count, timeout);
108   SILC_SCHEDULE_LOCK(schedule);
109   if (ret <= 0)
110     return ret;
111
112   for (i = 0; i < fds_count; i++) {
113     if (!fds[i].revents)
114       continue;
115     if (!silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fds[i].fd),
116                               NULL, (void **)&task))
117       continue;
118
119     fd = fds[i].revents;
120     if (fd & (POLLIN | POLLPRI | POLLERR | POLLHUP | POLLNVAL))
121       task->revents |= SILC_TASK_READ;
122     if (fd & POLLOUT)
123       task->revents |= SILC_TASK_WRITE;
124   }
125
126   return ret;
127 }
128
129 #else
130
131 /* Calls normal select() system call. */
132
133 int silc_select(SilcSchedule schedule, void *context)
134 {
135   SilcHashTableList htl;
136   SilcTaskFd task;
137   fd_set in, out;
138   int fd, max_fd = 0, ret;
139
140   FD_ZERO(&in);
141   FD_ZERO(&out);
142
143   silc_hash_table_list(schedule->fd_queue, &htl);
144   while (silc_hash_table_get(&htl, (void **)&fd, (void **)&task)) {
145     if (!task->events)
146       continue;
147
148 #ifdef FD_SETSIZE
149     if (fd >= FD_SETSIZE)
150       break;
151 #endif /* FD_SETSIZE */
152
153     if (fd > max_fd)
154       max_fd = fd;
155
156     if (task->events & SILC_TASK_READ)
157       FD_SET(fd, &in);
158     if (task->events & SILC_TASK_WRITE)
159       FD_SET(fd, &out);
160
161     task->revents = 0;
162   }
163   silc_hash_table_list_reset(&htl);
164
165   SILC_SCHEDULE_UNLOCK(schedule);
166   ret = select(max_fd + 1, &in, &out, NULL, (schedule->has_timeout ?
167                                              &schedule->timeout : NULL));
168   SILC_SCHEDULE_LOCK(schedule);
169   if (ret <= 0)
170     return ret;
171
172   silc_hash_table_list(schedule->fd_queue, &htl);
173   while (silc_hash_table_get(&htl, (void **)&fd, (void **)&task)) {
174     if (!task->events)
175       continue;
176
177 #ifdef FD_SETSIZE
178     if (fd >= FD_SETSIZE)
179       break;
180 #endif /* FD_SETSIZE */
181
182     if (FD_ISSET(fd, &in))
183       task->revents |= SILC_TASK_READ;
184     if (FD_ISSET(fd, &out))
185       task->revents |= SILC_TASK_WRITE;
186   }
187   silc_hash_table_list_reset(&htl);
188
189   return ret;
190 }
191
192 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
193
194 #ifdef SILC_THREADS
195
196 SILC_TASK_CALLBACK(silc_schedule_wakeup_cb)
197 {
198   SilcUnixScheduler internal = (SilcUnixScheduler)context;
199   unsigned char c;
200
201   SILC_LOG_DEBUG(("Wokeup"));
202
203   read(internal->wakeup_pipe[0], &c, 1);
204 }
205
206 #endif /* SILC_THREADS */
207
208 /* Initializes the platform specific scheduler.  This for example initializes
209    the wakeup mechanism of the scheduler.  In multi-threaded environment
210    the scheduler needs to be wakenup when tasks are added or removed from
211    the task queues.  Returns context to the platform specific scheduler. */
212
213 void *silc_schedule_internal_init(SilcSchedule schedule,
214                                   void *app_context)
215 {
216   SilcUnixScheduler internal;
217   int i;
218
219   internal = silc_calloc(1, sizeof(*internal));
220   if (!internal)
221     return NULL;
222
223 #if defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
224   getrlimit(RLIMIT_NOFILE, &internal->nofile);
225
226   if (schedule->max_tasks > 0) {
227     internal->nofile.rlim_cur = schedule->max_tasks;
228     if (schedule->max_tasks > internal->nofile.rlim_max)
229       internal->nofile.rlim_max = schedule->max_tasks;
230     setrlimit(RLIMIT_NOFILE, &internal->nofile);
231     getrlimit(RLIMIT_NOFILE, &internal->nofile);
232     schedule->max_tasks = internal->nofile.rlim_max;
233   }
234
235   internal->fds = silc_calloc(internal->nofile.rlim_cur,
236                               sizeof(*internal->fds));
237   if (!internal->fds)
238     return NULL;
239   internal->fds_count = internal->nofile.rlim_cur;
240 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
241
242   sigemptyset(&internal->signals);
243
244 #ifdef SILC_THREADS
245   if (pipe(internal->wakeup_pipe)) {
246     SILC_LOG_ERROR(("pipe() fails: %s", strerror(errno)));
247     silc_free(internal);
248     return NULL;
249   }
250
251   internal->wakeup_task =
252     silc_schedule_task_add(schedule, internal->wakeup_pipe[0],
253                            silc_schedule_wakeup_cb, internal,
254                            0, 0, SILC_TASK_FD);
255   if (!internal->wakeup_task) {
256     SILC_LOG_ERROR(("Could not add a wakeup task, threads won't work"));
257     close(internal->wakeup_pipe[0]);
258     close(internal->wakeup_pipe[1]);
259     silc_free(internal);
260     return NULL;
261   }
262 #endif
263
264   internal->app_context = app_context;
265
266   for (i = 0; i < SIGNAL_COUNT; i++) {
267     signal_call[i].signal = 0;
268     signal_call[i].call = FALSE;
269     signal_call[i].schedule = schedule;
270   }
271
272   return (void *)internal;
273 }
274
275 void silc_schedule_internal_signals_block(SilcSchedule schedule,
276                                           void *context);
277 void silc_schedule_internal_signals_unblock(SilcSchedule schedule,
278                                             void *context);
279
280 /* Uninitializes the platform specific scheduler context. */
281
282 void silc_schedule_internal_uninit(SilcSchedule schedule, void *context)
283 {
284   SilcUnixScheduler internal = (SilcUnixScheduler)context;
285
286   if (!internal)
287     return;
288
289 #ifdef SILC_THREADS
290   close(internal->wakeup_pipe[0]);
291   close(internal->wakeup_pipe[1]);
292 #endif
293
294 #if defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
295   silc_free(internal->fds);
296 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
297
298   silc_free(internal);
299 }
300
301 /* Wakes up the scheduler */
302
303 void silc_schedule_internal_wakeup(SilcSchedule schedule, void *context)
304 {
305 #ifdef SILC_THREADS
306   SilcUnixScheduler internal = (SilcUnixScheduler)context;
307
308   if (!internal)
309     return;
310
311   SILC_LOG_DEBUG(("Wakeup"));
312
313   write(internal->wakeup_pipe[1], "!", 1);
314 #endif
315 }
316
317 /* Signal handler */
318
319 static void silc_schedule_internal_sighandler(int signal)
320 {
321   int i;
322
323   for (i = 0; i < SIGNAL_COUNT; i++) {
324     if (signal_call[i].signal == signal) {
325       signal_call[i].call = TRUE;
326       signal_call[i].schedule->signal_tasks = TRUE;
327       SILC_LOG_DEBUG(("Scheduling signal %d to be called",
328                       signal_call[i].signal));
329       break;
330     }
331   }
332 }
333
334 void silc_schedule_internal_signal_register(SilcSchedule schedule,
335                                             void *context,
336                                             SilcUInt32 sig,
337                                             SilcTaskCallback callback,
338                                             void *callback_context)
339 {
340   SilcUnixScheduler internal = (SilcUnixScheduler)context;
341   int i;
342
343   if (!internal)
344     return;
345
346   SILC_LOG_DEBUG(("Registering signal %d", signal));
347
348   silc_schedule_internal_signals_block(schedule, context);
349
350   for (i = 0; i < SIGNAL_COUNT; i++) {
351     if (!signal_call[i].signal) {
352       signal_call[i].signal = sig;
353       signal_call[i].callback = callback;
354       signal_call[i].context = callback_context;
355       signal_call[i].call = FALSE;
356       signal(sig, silc_schedule_internal_sighandler);
357       break;
358     }
359   }
360
361   silc_schedule_internal_signals_unblock(schedule, context);
362   sigaddset(&internal->signals, sig);
363 }
364
365 void silc_schedule_internal_signal_unregister(SilcSchedule schedule,
366                                               void *context,
367                                               SilcUInt32 sig)
368 {
369   SilcUnixScheduler internal = (SilcUnixScheduler)context;
370   int i;
371
372   if (!internal)
373     return;
374
375   SILC_LOG_DEBUG(("Unregistering signal %d", signal));
376
377   silc_schedule_internal_signals_block(schedule, context);
378
379   for (i = 0; i < SIGNAL_COUNT; i++) {
380     if (signal_call[i].signal == sig) {
381       signal_call[i].signal = 0;
382       signal_call[i].callback = NULL;
383       signal_call[i].context = NULL;
384       signal_call[i].call = FALSE;
385       signal(sig, SIG_DFL);
386     }
387   }
388
389   silc_schedule_internal_signals_unblock(schedule, context);
390   sigdelset(&internal->signals, sig);
391 }
392
393 /* Call all signals */
394
395 void silc_schedule_internal_signals_call(SilcSchedule schedule, void *context)
396 {
397   SilcUnixScheduler internal = (SilcUnixScheduler)context;
398   int i;
399
400   SILC_LOG_DEBUG(("Start"));
401
402   if (!internal)
403     return;
404
405   silc_schedule_internal_signals_block(schedule, context);
406
407   for (i = 0; i < SIGNAL_COUNT; i++) {
408     if (signal_call[i].call &&
409         signal_call[i].callback) {
410       SILC_LOG_DEBUG(("Calling signal %d callback",
411                       signal_call[i].signal));
412       signal_call[i].callback(schedule, internal->app_context,
413                               SILC_TASK_INTERRUPT,
414                               signal_call[i].signal,
415                               signal_call[i].context);
416       signal_call[i].call = FALSE;
417     }
418   }
419
420   silc_schedule_internal_signals_unblock(schedule, context);
421 }
422
423 /* Block registered signals in scheduler. */
424
425 void silc_schedule_internal_signals_block(SilcSchedule schedule, void *context)
426 {
427   SilcUnixScheduler internal = (SilcUnixScheduler)context;
428
429   if (!internal)
430     return;
431
432   sigprocmask(SIG_BLOCK, &internal->signals, &internal->signals_blocked);
433 }
434
435 /* Unblock registered signals in schedule. */
436
437 void silc_schedule_internal_signals_unblock(SilcSchedule schedule,
438                                             void *context)
439 {
440   SilcUnixScheduler internal = (SilcUnixScheduler)context;
441
442   if (!internal)
443     return;
444
445   sigprocmask(SIG_SETMASK, &internal->signals_blocked, NULL);
446 }
447
448 const SilcScheduleOps schedule_ops =
449 {
450   silc_schedule_internal_init,
451   silc_schedule_internal_uninit,
452 #if defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
453   silc_poll,
454 #else
455   silc_select,
456 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
457   silc_schedule_internal_wakeup,
458   silc_schedule_internal_signal_register,
459   silc_schedule_internal_signal_unregister,
460   silc_schedule_internal_signals_call,
461   silc_schedule_internal_signals_block,
462   silc_schedule_internal_signals_unblock,
463 };