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 along
17     with this program; if not, write to the Free Software Foundation, Inc.,
18     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 
signal_unref_full(Signal * rec,int remove)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 
signal_hash_ref(void * key,Signal * rec)75 static void signal_hash_ref(void *key, Signal *rec)
76 {
77 	signal_ref(rec);
78 }
79 
signal_hash_unref(void * key,Signal * rec)80 static int signal_hash_unref(void *key, Signal *rec)
81 {
82 	return !signal_unref_full(rec, FALSE);
83 }
84 
signal_add_full(const char * module,int priority,const char * signal,SIGNAL_FUNC func,void * user_data)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 */
signal_add_full_id(const char * module,int priority,int signal_id,SIGNAL_FUNC func,void * user_data)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 
signal_remove_hook(Signal * rec,SignalHook ** hook_pos)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 */
signal_remove_func(Signal * rec,SIGNAL_FUNC func,void * user_data)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 
signal_remove_id(int signal_id,SIGNAL_FUNC func,void * user_data)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 */
signal_remove_full(const char * signal,SIGNAL_FUNC func,void * user_data)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 
signal_hooks_clean(Signal * rec)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 
signal_emit_real(Signal * rec,int params,va_list va,SignalHook * first_hook)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 
signal_emit(const char * signal,int params,...)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 
signal_emit_id(int signal_id,int params,...)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 
signal_continue(int params,...)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 */
signal_stop(void)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 */
signal_stop_by_name(const char * signal)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 */
signal_get_emitted(void)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 */
signal_get_emitted_id(void)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 */
signal_is_stopped(int signal_id)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 
signal_remove_module(void * signal,Signal * rec,const char * module)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' */
signals_remove_module(const char * 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 
signals_init(void)412 void signals_init(void)
413 {
414 	signals = g_hash_table_new(NULL, NULL);
415 }
416 
signal_free(void * key,Signal * rec)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 
signals_deinit(void)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 }
440