Added SILC Server library.
[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 - 2005 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 #define SIGNAL_COUNT 32
30
31 typedef struct {
32   SilcUInt32 signal;
33   SilcTaskCallback callback;
34   void *context;
35   SilcBool call;
36 } SilcUnixSignal;
37
38 /* Internal context. */
39 typedef struct {
40 #if defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
41   struct rlimit nofile;
42   struct pollfd *fds;
43   SilcUInt32 fds_count;
44 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
45   void *app_context;
46   int wakeup_pipe[2];
47   SilcTask wakeup_task;
48   sigset_t signals;
49   sigset_t signals_blocked;
50   SilcUnixSignal signal_call[SIGNAL_COUNT];
51 } *SilcUnixScheduler;
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   return (void *)internal;
265 }
266
267 void silc_schedule_internal_signals_block(SilcSchedule schedule,
268                                           void *context);
269 void silc_schedule_internal_signals_unblock(SilcSchedule schedule,
270                                             void *context);
271
272 /* Uninitializes the platform specific scheduler context. */
273
274 void silc_schedule_internal_uninit(SilcSchedule schedule, void *context)
275 {
276   SilcUnixScheduler internal = (SilcUnixScheduler)context;
277
278   if (!internal)
279     return;
280
281 #ifdef SILC_THREADS
282   close(internal->wakeup_pipe[0]);
283   close(internal->wakeup_pipe[1]);
284 #endif
285
286 #if defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
287   silc_free(internal->fds);
288 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
289
290   silc_free(internal);
291 }
292
293 /* Wakes up the scheduler */
294
295 void silc_schedule_internal_wakeup(SilcSchedule schedule, void *context)
296 {
297 #ifdef SILC_THREADS
298   SilcUnixScheduler internal = (SilcUnixScheduler)context;
299
300   if (!internal)
301     return;
302
303   SILC_LOG_DEBUG(("Wakeup"));
304
305   write(internal->wakeup_pipe[1], "!", 1);
306 #endif
307 }
308
309 void silc_schedule_internal_signal_register(SilcSchedule schedule,
310                                             void *context,
311                                             SilcUInt32 signal,
312                                             SilcTaskCallback callback,
313                                             void *callback_context)
314 {
315   SilcUnixScheduler internal = (SilcUnixScheduler)context;
316   int i;
317
318   if (!internal)
319     return;
320
321   SILC_LOG_DEBUG(("Registering signal %d", signal));
322
323   silc_schedule_internal_signals_block(schedule, context);
324
325   for (i = 0; i < SIGNAL_COUNT; i++) {
326     if (!internal->signal_call[i].signal) {
327       internal->signal_call[i].signal = signal;
328       internal->signal_call[i].callback = callback;
329       internal->signal_call[i].context = callback_context;
330       internal->signal_call[i].call = FALSE;
331       break;
332     }
333   }
334
335   silc_schedule_internal_signals_unblock(schedule, context);
336   sigaddset(&internal->signals, signal);
337 }
338
339 void silc_schedule_internal_signal_unregister(SilcSchedule schedule,
340                                               void *context,
341                                               SilcUInt32 signal,
342                                               SilcTaskCallback callback,
343                                               void *callback_context)
344 {
345   SilcUnixScheduler internal = (SilcUnixScheduler)context;
346   int i;
347
348   if (!internal)
349     return;
350
351   SILC_LOG_DEBUG(("Unregistering signal %d", signal));
352
353   silc_schedule_internal_signals_block(schedule, context);
354
355   for (i = 0; i < SIGNAL_COUNT; i++) {
356     if (internal->signal_call[i].signal == signal &&
357         internal->signal_call[i].callback == callback &&
358         internal->signal_call[i].context == callback_context) {
359       internal->signal_call[i].signal = 0;
360       internal->signal_call[i].callback = NULL;
361       internal->signal_call[i].context = NULL;
362       internal->signal_call[i].call = FALSE;
363     }
364   }
365
366   silc_schedule_internal_signals_unblock(schedule, context);
367   sigdelset(&internal->signals, signal);
368 }
369
370 /* Mark signal to be called later. */
371
372 void silc_schedule_internal_signal_call(SilcSchedule schedule,
373                                         void *context, SilcUInt32 signal)
374 {
375   SilcUnixScheduler internal = (SilcUnixScheduler)context;
376   int i;
377
378   if (!internal)
379     return;
380
381   silc_schedule_internal_signals_block(schedule, context);
382
383   for (i = 0; i < SIGNAL_COUNT; i++) {
384     if (internal->signal_call[i].signal == signal) {
385       internal->signal_call[i].call = TRUE;
386       SILC_LOG_DEBUG(("Scheduling signal %d to be called",
387                       internal->signal_call[i].signal));
388     }
389   }
390
391   silc_schedule_internal_signals_unblock(schedule, context);
392 }
393
394 /* Call all signals */
395
396 void silc_schedule_internal_signals_call(SilcSchedule schedule, void *context)
397 {
398   SilcUnixScheduler internal = (SilcUnixScheduler)context;
399   int i;
400
401   SILC_LOG_DEBUG(("Start"));
402
403   if (!internal)
404     return;
405
406   silc_schedule_internal_signals_block(schedule, context);
407
408   for (i = 0; i < SIGNAL_COUNT; i++) {
409     if (internal->signal_call[i].call &&
410         internal->signal_call[i].callback) {
411       SILC_LOG_DEBUG(("Calling signal %d callback",
412                       internal->signal_call[i].signal));
413       internal->signal_call[i].callback(schedule, internal->app_context,
414                                         SILC_TASK_INTERRUPT,
415                                         internal->signal_call[i].signal,
416                                         internal->signal_call[i].context);
417       internal->signal_call[i].call = FALSE;
418     }
419   }
420
421   silc_schedule_internal_signals_unblock(schedule, context);
422 }
423
424 /* Block registered signals in scheduler. */
425
426 void silc_schedule_internal_signals_block(SilcSchedule schedule, void *context)
427 {
428   SilcUnixScheduler internal = (SilcUnixScheduler)context;
429
430   if (!internal)
431     return;
432
433   sigprocmask(SIG_BLOCK, &internal->signals, &internal->signals_blocked);
434 }
435
436 /* Unblock registered signals in schedule. */
437
438 void silc_schedule_internal_signals_unblock(SilcSchedule schedule,
439                                             void *context)
440 {
441   SilcUnixScheduler internal = (SilcUnixScheduler)context;
442
443   if (!internal)
444     return;
445
446   sigprocmask(SIG_SETMASK, &internal->signals_blocked, NULL);
447 }
448
449 const SilcScheduleOps schedule_ops =
450 {
451   silc_schedule_internal_init,
452   silc_schedule_internal_uninit,
453 #if defined(HAVE_POLL) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)
454   silc_poll,
455 #else
456   silc_select,
457 #endif /* HAVE_POLL && HAVE_SETRLIMIT && RLIMIT_NOFILE */
458   silc_schedule_internal_wakeup,
459   silc_schedule_internal_signal_register,
460   silc_schedule_internal_signal_unregister,
461   silc_schedule_internal_signal_call,
462   silc_schedule_internal_signals_call,
463   silc_schedule_internal_signals_block,
464   silc_schedule_internal_signals_unblock,
465 };