Mon Mar 5 23:27:32 CET 2007 Jochen Eisinger <coffee@silcnet.org>
[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 - 2004 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 "silcincludes.h"
22 #include "silcschedule_i.h"
23
24 /* Calls normal select() system call. */
25
26 int silc_select(SilcScheduleFd fds, SilcUInt32 fds_count,
27                 struct timeval *timeout)
28 {
29   fd_set in, out;
30   int ret, i, max_fd = 0;
31
32   FD_ZERO(&in);
33   FD_ZERO(&out);
34
35   for (i = 0; i < fds_count; i++) {
36     if (!fds[i].events)
37       continue;
38
39 #ifdef FD_SETSIZE
40     if (fds[i].fd >= FD_SETSIZE)
41       break;
42 #endif /* FD_SETSIZE */
43
44     if (fds[i].fd > max_fd)
45       max_fd = fds[i].fd;
46
47     if (fds[i].events & SILC_TASK_READ)
48       FD_SET(fds[i].fd, &in);
49     if (fds[i].events & SILC_TASK_WRITE)
50       FD_SET(fds[i].fd, &out);
51
52     fds[i].revents = 0;
53   }
54
55   ret = select(max_fd + 1, &in, &out, NULL, timeout);
56   if (ret <= 0)
57     return ret;
58
59   for (i = 0; i < fds_count; i++) {
60     if (!fds[i].events)
61       continue;
62
63 #ifdef FD_SETSIZE
64     if (fds[i].fd >= FD_SETSIZE)
65       break;
66 #endif /* FD_SETSIZE */
67
68     if (FD_ISSET(fds[i].fd, &in))
69       fds[i].revents |= SILC_TASK_READ;
70     if (FD_ISSET(fds[i].fd, &out))
71       fds[i].revents |= SILC_TASK_WRITE;
72   }
73
74   return ret;
75 }
76
77 #define SIGNAL_COUNT 32
78
79 typedef struct {
80   SilcUInt32 signal;
81   SilcTaskCallback callback;
82   void *context;
83   bool call;
84 } SilcUnixSignal;
85
86 /* Internal context. */
87 typedef struct {
88   void *app_context;
89   int wakeup_pipe[2];
90   SilcTask wakeup_task;
91   sigset_t signals;
92   sigset_t signals_blocked;
93   SilcUnixSignal signal_call[SIGNAL_COUNT];
94 } *SilcUnixScheduler;
95
96 #ifdef SILC_THREADS
97
98 SILC_TASK_CALLBACK(silc_schedule_wakeup_cb)
99 {
100   SilcUnixScheduler internal = (SilcUnixScheduler)context;
101   unsigned char c;
102
103   read(internal->wakeup_pipe[0], &c, 1);
104 }
105
106 #endif /* SILC_THREADS */
107
108 /* Initializes the platform specific scheduler.  This for example initializes
109    the wakeup mechanism of the scheduler.  In multi-threaded environment
110    the scheduler needs to be wakenup when tasks are added or removed from
111    the task queues.  Returns context to the platform specific scheduler. */
112
113 void *silc_schedule_internal_init(SilcSchedule schedule,
114                                   void *app_context)
115 {
116   SilcUnixScheduler internal;
117
118   internal = silc_calloc(1, sizeof(*internal));
119   if (!internal)
120     return NULL;
121
122   sigemptyset(&internal->signals);
123
124 #ifdef SILC_THREADS
125   if (pipe(internal->wakeup_pipe)) {
126     SILC_LOG_ERROR(("pipe() fails: %s", strerror(errno)));
127     silc_free(internal);
128     return NULL;
129   }
130
131   internal->wakeup_task =
132     silc_schedule_task_add(schedule, internal->wakeup_pipe[0],
133                            silc_schedule_wakeup_cb, internal,
134                            0, 0, SILC_TASK_FD,
135                            SILC_TASK_PRI_NORMAL);
136   if (!internal->wakeup_task) {
137     SILC_LOG_ERROR(("Could not add a wakeup task, threads won't work"));
138     close(internal->wakeup_pipe[0]);
139     close(internal->wakeup_pipe[1]);
140     silc_free(internal);
141     return NULL;
142   }
143 #endif
144
145   internal->app_context = app_context;
146
147   return (void *)internal;
148 }
149
150 void silc_schedule_internal_signals_block(void *context);
151 void silc_schedule_internal_signals_unblock(void *context);
152
153 /* Uninitializes the platform specific scheduler context. */
154
155 void silc_schedule_internal_uninit(void *context)
156 {
157   SilcUnixScheduler internal = (SilcUnixScheduler)context;
158
159   if (!internal)
160     return;
161
162 #ifdef SILC_THREADS
163   close(internal->wakeup_pipe[0]);
164   close(internal->wakeup_pipe[1]);
165 #endif
166
167   silc_free(internal);
168 }
169
170 /* Wakes up the scheduler */
171
172 void silc_schedule_internal_wakeup(void *context)
173 {
174 #ifdef SILC_THREADS
175   SilcUnixScheduler internal = (SilcUnixScheduler)context;
176
177   if (!internal)
178     return;
179
180   write(internal->wakeup_pipe[1], "!", 1);
181 #endif
182 }
183
184 void silc_schedule_internal_signal_register(void *context,
185                                             SilcUInt32 signal,
186                                             SilcTaskCallback callback,
187                                             void *callback_context)
188 {
189   SilcUnixScheduler internal = (SilcUnixScheduler)context;
190   int i;
191
192   if (!internal)
193     return;
194
195   SILC_LOG_DEBUG(("Registering signal %d", signal));
196
197   silc_schedule_internal_signals_block(context);
198
199   for (i = 0; i < SIGNAL_COUNT; i++) {
200     if (!internal->signal_call[i].signal) {
201       internal->signal_call[i].signal = signal;
202       internal->signal_call[i].callback = callback;
203       internal->signal_call[i].context = callback_context;
204       internal->signal_call[i].call = FALSE;
205       break;
206     }
207   }
208
209   silc_schedule_internal_signals_unblock(context);
210   sigaddset(&internal->signals, signal);
211 }
212
213 void silc_schedule_internal_signal_unregister(void *context,
214                                               SilcUInt32 signal,
215                                               SilcTaskCallback callback,
216                                               void *callback_context)
217 {
218   SilcUnixScheduler internal = (SilcUnixScheduler)context;
219   int i;
220
221   if (!internal)
222     return;
223
224   SILC_LOG_DEBUG(("Unregistering signal %d", signal));
225
226   silc_schedule_internal_signals_block(context);
227
228   for (i = 0; i < SIGNAL_COUNT; i++) {
229     if (internal->signal_call[i].signal == signal &&
230         internal->signal_call[i].callback == callback &&
231         internal->signal_call[i].context == callback_context) {
232       internal->signal_call[i].signal = 0;
233       internal->signal_call[i].callback = NULL;
234       internal->signal_call[i].context = NULL;
235       internal->signal_call[i].call = FALSE;
236     }
237   }
238
239   silc_schedule_internal_signals_unblock(context);
240   sigdelset(&internal->signals, signal);
241 }
242
243 /* Mark signal to be called later. */
244
245 void silc_schedule_internal_signal_call(void *context, SilcUInt32 signal)
246 {
247   SilcUnixScheduler internal = (SilcUnixScheduler)context;
248   int i;
249
250   if (!internal)
251     return;
252
253   silc_schedule_internal_signals_block(context);
254
255   for (i = 0; i < SIGNAL_COUNT; i++) {
256     if (internal->signal_call[i].signal == signal) {
257       internal->signal_call[i].call = TRUE;
258       SILC_LOG_DEBUG(("Scheduling signal %d to be called",
259                       internal->signal_call[i].signal));
260     }
261   }
262
263   silc_schedule_internal_signals_unblock(context);
264 }
265
266 /* Call all signals */
267
268 void silc_schedule_internal_signals_call(void *context,
269                                          SilcSchedule schedule)
270 {
271   SilcUnixScheduler internal = (SilcUnixScheduler)context;
272   int i;
273
274   SILC_LOG_DEBUG(("Start"));
275
276   if (!internal)
277     return;
278
279   silc_schedule_internal_signals_block(context);
280
281   for (i = 0; i < SIGNAL_COUNT; i++) {
282     if (internal->signal_call[i].call &&
283         internal->signal_call[i].callback) {
284       SILC_LOG_DEBUG(("Calling signal %d callback",
285                       internal->signal_call[i].signal));
286       internal->signal_call[i].callback(schedule, internal->app_context,
287                                         SILC_TASK_INTERRUPT,
288                                         internal->signal_call[i].signal,
289                                         internal->signal_call[i].context);
290       internal->signal_call[i].call = FALSE;
291     }
292   }
293
294   silc_schedule_internal_signals_unblock(context);
295 }
296
297 /* Block registered signals in scheduler. */
298
299 void silc_schedule_internal_signals_block(void *context)
300 {
301   SilcUnixScheduler internal = (SilcUnixScheduler)context;
302
303   if (!internal)
304     return;
305
306   sigprocmask(SIG_BLOCK, &internal->signals, &internal->signals_blocked);
307 }
308
309 /* Unblock registered signals in schedule. */
310
311 void silc_schedule_internal_signals_unblock(void *context)
312 {
313   SilcUnixScheduler internal = (SilcUnixScheduler)context;
314
315   if (!internal)
316     return;
317
318   sigprocmask(SIG_SETMASK, &internal->signals_blocked, NULL);
319 }