Added SILC Thread Queue API
[crypto.git] / apps / irssi / src / core / signals.c
1 /*
2  signals.c : irssi
3
4     Copyright (C) 1999-2002 Timo Sirainen
5
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21 #include "module.h"
22 #include "signals.h"
23 #include "modules.h"
24
25 typedef struct _SignalHook {
26         struct _SignalHook *next;
27
28         int priority;
29         const char *module;
30         SIGNAL_FUNC func;
31         void *user_data;
32 } SignalHook;
33
34 typedef struct {
35         int id; /* signal id */
36         int refcount;
37
38         int emitting; /* signal is being emitted */
39         int stop_emit; /* this signal was stopped */
40         int continue_emit; /* this signal emit was continued elsewhere */
41         int remove_count; /* hooks were removed from signal */
42
43         SignalHook *hooks;
44 } Signal;
45
46 void *signal_user_data;
47
48 static GHashTable *signals;
49 static Signal *current_emitted_signal;
50 static SignalHook *current_emitted_hook;
51
52 #define signal_ref(signal) ++(signal)->refcount
53 #define signal_unref(signal) (signal_unref_full(signal, TRUE))
54
55 static int signal_unref_full(Signal *rec, int remove)
56 {
57         g_assert(rec->refcount > 0);
58
59         if (--rec->refcount != 0)
60                 return TRUE;
61
62         /* remove whole signal from memory */
63         if (rec->hooks != NULL) {
64                 g_error("signal_unref(%s) : BUG - hook list wasn't empty",
65                         signal_get_id_str(rec->id));
66         }
67
68         if (remove)
69                 g_hash_table_remove(signals, GINT_TO_POINTER(rec->id));
70         g_free(rec);
71
72         return FALSE;
73 }
74
75 static void signal_hash_ref(void *key, Signal *rec)
76 {
77         signal_ref(rec);
78 }
79
80 static int signal_hash_unref(void *key, Signal *rec)
81 {
82         return !signal_unref_full(rec, FALSE);
83 }
84
85 void signal_add_full(const char *module, int priority,
86                      const char *signal, SIGNAL_FUNC func, void *user_data)
87 {
88         signal_add_full_id(module, priority, signal_get_uniq_id(signal),
89                            func, user_data);
90 }
91
92 /* bind a signal */
93 void signal_add_full_id(const char *module, int priority,
94                         int signal_id, SIGNAL_FUNC func, void *user_data)
95 {
96         Signal *signal;
97         SignalHook *hook, **tmp;
98
99         g_return_if_fail(signal_id >= 0);
100         g_return_if_fail(func != NULL);
101
102         signal = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
103         if (signal == NULL) {
104                 /* new signal */
105                 signal = g_new0(Signal, 1);
106                 signal->id = signal_id;
107                 g_hash_table_insert(signals, GINT_TO_POINTER(signal_id), signal);
108         }
109
110         hook = g_new0(SignalHook, 1);
111         hook->priority = priority;
112         hook->module = module;
113         hook->func = func;
114         hook->user_data = user_data;
115
116         /* insert signal to proper position in list */
117         for (tmp = &signal->hooks; ; tmp = &(*tmp)->next) {
118                 if (*tmp == NULL) {
119                         /* last in list */
120                         *tmp = hook;
121                         break;
122                 } else if (priority <= (*tmp)->priority) {
123                         /* insert before others with same priority */
124                         hook->next = *tmp;
125                         *tmp = hook;
126                         break;
127                 }
128         }
129
130         signal_ref(signal);
131 }
132
133 static void signal_remove_hook(Signal *rec, SignalHook **hook_pos)
134 {
135         SignalHook *hook;
136
137         hook = *hook_pos;
138         *hook_pos = hook->next;
139
140         g_free(hook);
141
142         signal_unref(rec);
143 }
144
145 /* Remove function from signal's emit list */
146 static int signal_remove_func(Signal *rec, SIGNAL_FUNC func, void *user_data)
147 {
148         SignalHook **hook;
149
150         for (hook = &rec->hooks; *hook != NULL; hook = &(*hook)->next) {
151                 if ((*hook)->func == func && (*hook)->user_data == user_data) {
152                         if (rec->emitting) {
153                                 /* mark it removed after emitting is done */
154                                 (*hook)->func = NULL;
155                                 rec->remove_count++;
156                         } else {
157                                 /* remove the function from emit list */
158                                 signal_remove_hook(rec, hook);
159                         }
160                         return TRUE;
161                 }
162         }
163
164         return FALSE;
165 }
166
167 void signal_remove_id(int signal_id, SIGNAL_FUNC func, void *user_data)
168 {
169         Signal *rec;
170
171         g_return_if_fail(signal_id >= 0);
172         g_return_if_fail(func != NULL);
173
174         rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
175         if (rec != NULL)
176                 signal_remove_func(rec, func, user_data);
177 }
178
179 /* unbind signal */
180 void signal_remove_full(const char *signal, SIGNAL_FUNC func, void *user_data)
181 {
182         g_return_if_fail(signal != NULL);
183
184         signal_remove_id(signal_get_uniq_id(signal), func, user_data);
185 }
186
187 static void signal_hooks_clean(Signal *rec)
188 {
189         SignalHook **hook, **next;
190         int count;
191
192         count = rec->remove_count;
193         rec->remove_count = 0;
194
195         for (hook = &rec->hooks; *hook != NULL; hook = next) {
196                 next = &(*hook)->next;
197
198                 if ((*hook)->func == NULL) {
199                         next = hook;
200                         signal_remove_hook(rec, hook);
201
202                         if (--count == 0)
203                                 break;
204                 }
205         }
206 }
207
208 static int signal_emit_real(Signal *rec, int params, va_list va,
209                             SignalHook *first_hook)
210 {
211         const void *arglist[SIGNAL_MAX_ARGUMENTS];
212         Signal *prev_emitted_signal;
213         SignalHook *hook, *prev_emitted_hook;
214         int i, stopped, stop_emit_count, continue_emit_count;
215
216         for (i = 0; i < SIGNAL_MAX_ARGUMENTS; i++)
217                 arglist[i] = i >= params ? NULL : va_arg(va, const void *);
218
219         /* signal_stop_by_name("signal"); signal_emit("signal", ...);
220            fails if we compare rec->stop_emit against 0. */
221         stop_emit_count = rec->stop_emit;
222         continue_emit_count = rec->continue_emit;
223
224         signal_ref(rec);
225
226         stopped = FALSE;
227         rec->emitting++;
228
229         prev_emitted_signal = current_emitted_signal;
230         prev_emitted_hook = current_emitted_hook;
231         current_emitted_signal = rec;
232
233         for (hook = first_hook; hook != NULL; hook = hook->next) {
234                 if (hook->func == NULL)
235                         continue; /* removed */
236
237                 current_emitted_hook = hook;
238 #if SIGNAL_MAX_ARGUMENTS != 6
239 #  error SIGNAL_MAX_ARGUMENTS changed - update code
240 #endif
241                 signal_user_data = hook->user_data;
242                 hook->func(arglist[0], arglist[1], arglist[2], arglist[3],
243                            arglist[4], arglist[5]);
244
245                 if (rec->continue_emit != continue_emit_count)
246                         rec->continue_emit--;
247
248                 if (rec->stop_emit != stop_emit_count) {
249                         stopped = TRUE;
250                         rec->stop_emit--;
251                         break;
252                 }
253         }
254
255         current_emitted_signal = prev_emitted_signal;
256         current_emitted_hook = prev_emitted_hook;
257
258         rec->emitting--;
259         signal_user_data = NULL;
260
261         if (!rec->emitting) {
262                 g_assert(rec->stop_emit == 0);
263                 g_assert(rec->continue_emit == 0);
264
265                 if (rec->remove_count > 0)
266                         signal_hooks_clean(rec);
267         }
268
269         signal_unref(rec);
270         return stopped;
271 }
272
273 int signal_emit(const char *signal, int params, ...)
274 {
275         Signal *rec;
276         va_list va;
277         int signal_id;
278
279         g_return_val_if_fail(params >= 0 && params <= SIGNAL_MAX_ARGUMENTS, FALSE);
280
281         signal_id = signal_get_uniq_id(signal);
282
283         rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
284         if (rec != NULL) {
285                 va_start(va, params);
286                 signal_emit_real(rec, params, va, rec->hooks);
287                 va_end(va);
288         }
289
290         return rec != NULL;
291 }
292
293 int signal_emit_id(int signal_id, int params, ...)
294 {
295         Signal *rec;
296         va_list va;
297
298         g_return_val_if_fail(signal_id >= 0, FALSE);
299         g_return_val_if_fail(params >= 0 && params <= SIGNAL_MAX_ARGUMENTS, FALSE);
300
301         rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
302         if (rec != NULL) {
303                 va_start(va, params);
304                 signal_emit_real(rec, params, va, rec->hooks);
305                 va_end(va);
306         }
307
308         return rec != NULL;
309 }
310
311 void signal_continue(int params, ...)
312 {
313         Signal *rec;
314         va_list va;
315
316         rec = current_emitted_signal;
317         if (rec == NULL || rec->emitting <= rec->continue_emit)
318                 g_warning("signal_continue() : no signals are being emitted currently");
319         else {
320                 va_start(va, params);
321
322                 /* stop the signal */
323                 if (rec->emitting > rec->stop_emit)
324                         rec->stop_emit++;
325
326                 /* re-emit */
327                 rec->continue_emit++;
328                 signal_emit_real(rec, params, va, current_emitted_hook->next);
329                 va_end(va);
330         }
331 }
332
333 /* stop the current ongoing signal emission */
334 void signal_stop(void)
335 {
336         Signal *rec;
337
338         rec = current_emitted_signal;
339         if (rec == NULL)
340                 g_warning("signal_stop() : no signals are being emitted currently");
341         else if (rec->emitting > rec->stop_emit)
342                 rec->stop_emit++;
343 }
344
345 /* stop ongoing signal emission by signal name */
346 void signal_stop_by_name(const char *signal)
347 {
348         Signal *rec;
349         int signal_id;
350
351         signal_id = signal_get_uniq_id(signal);
352         rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
353         if (rec == NULL)
354                 g_warning("signal_stop_by_name() : unknown signal \"%s\"", signal);
355         else if (rec->emitting > rec->stop_emit)
356                 rec->stop_emit++;
357 }
358
359 /* return the name of the signal that is currently being emitted */
360 const char *signal_get_emitted(void)
361 {
362         return signal_get_id_str(signal_get_emitted_id());
363 }
364
365 /* return the ID of the signal that is currently being emitted */
366 int signal_get_emitted_id(void)
367 {
368         Signal *rec;
369
370         rec = current_emitted_signal;
371         g_return_val_if_fail(rec != NULL, -1);
372         return rec->id;
373 }
374
375 /* return TRUE if specified signal was stopped */
376 int signal_is_stopped(int signal_id)
377 {
378         Signal *rec;
379
380         rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
381         g_return_val_if_fail(rec != NULL, FALSE);
382
383         return rec->emitting <= rec->stop_emit;
384 }
385
386 static void signal_remove_module(void *signal, Signal *rec,
387                                  const char *module)
388 {
389         SignalHook **hook, **next;
390
391         for (hook = &rec->hooks; *hook != NULL; hook = next) {
392                 next = &(*hook)->next;
393
394                 if (strcasecmp((*hook)->module, module) == 0) {
395                         next = hook;
396                         signal_remove_hook(rec, hook);
397                 }
398         }
399 }
400
401 /* remove all signals that belong to `module' */
402 void signals_remove_module(const char *module)
403 {
404         g_return_if_fail(module != NULL);
405
406         g_hash_table_foreach(signals, (GHFunc) signal_hash_ref, NULL);
407         g_hash_table_foreach(signals, (GHFunc) signal_remove_module,
408                              (void *) module);
409         g_hash_table_foreach_remove(signals, (GHRFunc) signal_hash_unref, NULL);
410 }
411
412 void signals_init(void)
413 {
414         signals = g_hash_table_new(NULL, NULL);
415 }
416
417 static void signal_free(void *key, Signal *rec)
418 {
419         /* refcount-1 because we just referenced it ourself */
420         g_warning("signal_free(%s) : signal still has %d references:",
421                   signal_get_id_str(rec->id), rec->refcount-1);
422
423         while (rec->hooks != NULL) {
424                 g_warning(" - module '%s' function %p",
425                           rec->hooks->module, rec->hooks->func);
426
427                 signal_remove_hook(rec, &rec->hooks);
428         }
429 }
430
431 void signals_deinit(void)
432 {
433         g_hash_table_foreach(signals, (GHFunc) signal_hash_ref, NULL);
434         g_hash_table_foreach(signals, (GHFunc) signal_free, NULL);
435         g_hash_table_foreach_remove(signals, (GHRFunc) signal_hash_unref, NULL);
436         g_hash_table_destroy(signals);
437
438         module_uniq_destroy("signals");
439 }