Some bugfixes for the new signals support
[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 - 2001 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; either version 2 of the License, or
12   (at your option) any later version.
13   
14   This program is distributed in the hope that it will be useful,
15   but WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   GNU General Public License for more details.
18
19 */
20 /* $Id$ */
21
22 #include "silcincludes.h"
23 #include "silcschedule_i.h"
24
25 /* Calls normal select() system call. */
26
27 int silc_select(SilcScheduleFd fds, SilcUInt32 fds_count, 
28                 struct timeval *timeout)
29 {
30   fd_set in, out;
31   int ret, i, max_fd = 0;
32
33   FD_ZERO(&in);
34   FD_ZERO(&out);
35
36   for (i = 0; i < fds_count; i++) {
37     if (!fds[i].events)
38       continue;
39
40     if (fds[i].fd > max_fd)
41       max_fd = fds[i].fd;
42
43     if (fds[i].events & SILC_TASK_READ)
44       FD_SET(fds[i].fd, &in);
45     if (fds[i].events & SILC_TASK_WRITE)
46       FD_SET(fds[i].fd, &out);
47
48     fds[i].revents = 0;
49   }
50
51   ret = select(max_fd + 1, &in, &out, NULL, timeout);
52   if (ret <= 0)
53     return ret;
54
55   for (i = 0; i < fds_count; i++) {
56     if (!fds[i].events)
57       continue;
58
59     if (FD_ISSET(fds[i].fd, &in))
60       fds[i].revents |= SILC_TASK_READ;
61     if (FD_ISSET(fds[i].fd, &out))
62       fds[i].revents |= SILC_TASK_WRITE;
63   }
64
65   return ret;
66 }
67
68 #define SIGNAL_COUNT 32
69
70 typedef struct {
71   SilcUInt32 signal;
72   SilcTaskCallback callback;
73   void *context;
74   bool call;
75 } SilcUnixSignal;
76
77 /* Internal context. */
78 typedef struct {
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 {
105   SilcUnixScheduler internal;
106
107   internal = silc_calloc(1, sizeof(*internal));
108   if (!internal)
109     return NULL;
110
111   sigemptyset(&internal->signals);
112
113 #ifdef SILC_THREADS
114   if (pipe(internal->wakeup_pipe)) {
115     silc_free(internal);
116     return NULL;
117   }
118
119   internal->wakeup_task = 
120     silc_schedule_task_add(schedule, internal->wakeup_pipe[0],
121                            silc_schedule_wakeup_cb, internal,
122                            0, 0, SILC_TASK_FD, 
123                            SILC_TASK_PRI_NORMAL);
124   if (!internal->wakeup_task) {
125     close(internal->wakeup_pipe[0]);
126     close(internal->wakeup_pipe[1]);
127     silc_free(internal);
128     return NULL;
129   }
130 #endif
131
132   return (void *)internal;
133 }
134
135 /* Uninitializes the platform specific scheduler context. */
136
137 void silc_schedule_internal_uninit(void *context)
138 {
139   SilcUnixScheduler internal = (SilcUnixScheduler)context;
140
141   if (!internal)
142     return;
143
144 #ifdef SILC_THREADS
145   close(internal->wakeup_pipe[0]);
146   close(internal->wakeup_pipe[1]);
147 #endif
148
149   silc_free(internal);
150 }
151
152 /* Wakes up the scheduler */
153
154 void silc_schedule_internal_wakeup(void *context)
155 {
156 #ifdef SILC_THREADS
157   SilcUnixScheduler internal = (SilcUnixScheduler)context;
158
159   if (!internal)
160     return;
161
162   write(internal->wakeup_pipe[1], "!", 1);
163 #endif
164 }
165
166 void silc_schedule_internal_signal_register(void *context,
167                                             SilcUInt32 signal,
168                                             SilcTaskCallback callback,
169                                             void *callback_context)
170 {
171   SilcUnixScheduler internal = (SilcUnixScheduler)context;
172   int i;
173
174   sigprocmask(SIG_BLOCK, &internal->signals, &internal->signals_blocked);
175
176   for (i = 0; i < SIGNAL_COUNT; i++) {
177     if (!internal->signal_call[i].signal) {
178       internal->signal_call[i].signal = signal;
179       internal->signal_call[i].callback = callback;
180       internal->signal_call[i].context = callback_context;
181       internal->signal_call[i].call = FALSE;
182       break;
183     }
184   }
185
186   sigprocmask(SIG_SETMASK, &internal->signals_blocked, NULL);
187   sigaddset(&internal->signals, signal);
188 }
189
190 void silc_schedule_internal_signal_unregister(void *context,
191                                               SilcUInt32 signal,
192                                               SilcTaskCallback callback,
193                                               void *callback_context)
194 {
195   SilcUnixScheduler internal = (SilcUnixScheduler)context;
196   int i;
197
198   sigprocmask(SIG_BLOCK, &internal->signals, &internal->signals_blocked);
199
200   for (i = 0; i < SIGNAL_COUNT; i++) {
201     if (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].signal = 0;
205       internal->signal_call[i].callback = NULL;
206       internal->signal_call[i].context = NULL;
207       internal->signal_call[i].call = FALSE;
208     }
209   }
210
211   sigprocmask(SIG_SETMASK, &internal->signals_blocked, NULL);
212   sigdelset(&internal->signals, signal);
213 }
214
215 /* Mark signal to be called later. */
216
217 void silc_schedule_internal_signal_call(void *context, SilcUInt32 signal)
218 {
219   SilcUnixScheduler internal = (SilcUnixScheduler)context;
220   int i;
221
222   sigprocmask(SIG_BLOCK, &internal->signals, &internal->signals_blocked);
223
224   for (i = 0; i < SIGNAL_COUNT; i++) {
225     if (internal->signal_call[i].signal == signal)
226       internal->signal_call[i].call = TRUE;
227   }
228
229   sigprocmask(SIG_SETMASK, &internal->signals_blocked, NULL);
230 }
231                                         
232 /* Call all signals */
233
234 void silc_schedule_internal_signals_call(void *context,
235                                          SilcSchedule schedule)
236 {
237   SilcUnixScheduler internal = (SilcUnixScheduler)context;
238   int i;
239
240   sigprocmask(SIG_BLOCK, &internal->signals, &internal->signals_blocked);
241
242   for (i = 0; i < SIGNAL_COUNT; i++) {
243     if (internal->signal_call[i].call &&
244         internal->signal_call[i].callback) {
245       internal->signal_call[i].callback(schedule, SILC_TASK_INTERRUPT,
246                                         internal->signal_call[i].signal,
247                                         internal->signal_call[i].context);
248       internal->signal_call[i].call = FALSE;
249     }
250   }
251
252   sigprocmask(SIG_SETMASK, &internal->signals_blocked, NULL);
253 }
254
255 /* Block registered signals in scheduler. */
256
257 void silc_schedule_internal_signals_block(void *context)
258 {
259   SilcUnixScheduler internal = (SilcUnixScheduler)context;
260   sigprocmask(SIG_BLOCK, &internal->signals, &internal->signals_blocked);
261 }
262
263 /* Unblock registered signals in schedule. */
264
265 void silc_schedule_internal_signals_unblock(void *context)
266 {
267   SilcUnixScheduler internal = (SilcUnixScheduler)context;
268   sigprocmask(SIG_SETMASK, &internal->signals_blocked, NULL);
269 }