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