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