1 /*
2 * Copyright (c) 2013-2014 Hugh Bailey <obs.jim@gmail.com>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include "../util/darray.h"
18 #include "../util/threading.h"
19
20 #include "decl.h"
21 #include "signal.h"
22
23 struct signal_callback {
24 signal_callback_t callback;
25 void *data;
26 bool remove;
27 bool keep_ref;
28 };
29
30 struct signal_info {
31 struct decl_info func;
32 DARRAY(struct signal_callback) callbacks;
33 pthread_mutex_t mutex;
34 bool signalling;
35
36 struct signal_info *next;
37 };
38
signal_info_create(struct decl_info * info)39 static inline struct signal_info *signal_info_create(struct decl_info *info)
40 {
41 struct signal_info *si = bmalloc(sizeof(struct signal_info));
42 si->func = *info;
43 si->next = NULL;
44 si->signalling = false;
45 da_init(si->callbacks);
46
47 if (pthread_mutex_init_recursive(&si->mutex) != 0) {
48 blog(LOG_ERROR, "Could not create signal");
49
50 decl_info_free(&si->func);
51 bfree(si);
52 return NULL;
53 }
54
55 return si;
56 }
57
signal_info_destroy(struct signal_info * si)58 static inline void signal_info_destroy(struct signal_info *si)
59 {
60 if (si) {
61 pthread_mutex_destroy(&si->mutex);
62 decl_info_free(&si->func);
63 da_free(si->callbacks);
64 bfree(si);
65 }
66 }
67
signal_get_callback_idx(struct signal_info * si,signal_callback_t callback,void * data)68 static inline size_t signal_get_callback_idx(struct signal_info *si,
69 signal_callback_t callback,
70 void *data)
71 {
72 for (size_t i = 0; i < si->callbacks.num; i++) {
73 struct signal_callback *sc = si->callbacks.array + i;
74
75 if (sc->callback == callback && sc->data == data)
76 return i;
77 }
78
79 return DARRAY_INVALID;
80 }
81
82 struct global_callback_info {
83 global_signal_callback_t callback;
84 void *data;
85 long signaling;
86 bool remove;
87 };
88
89 struct signal_handler {
90 struct signal_info *first;
91 pthread_mutex_t mutex;
92 volatile long refs;
93
94 DARRAY(struct global_callback_info) global_callbacks;
95 pthread_mutex_t global_callbacks_mutex;
96 };
97
getsignal(signal_handler_t * handler,const char * name,struct signal_info ** p_last)98 static struct signal_info *getsignal(signal_handler_t *handler,
99 const char *name,
100 struct signal_info **p_last)
101 {
102 struct signal_info *signal, *last = NULL;
103
104 signal = handler->first;
105 while (signal != NULL) {
106 if (strcmp(signal->func.name, name) == 0)
107 break;
108
109 last = signal;
110 signal = signal->next;
111 }
112
113 if (p_last)
114 *p_last = last;
115 return signal;
116 }
117
118 /* ------------------------------------------------------------------------- */
119
signal_handler_create(void)120 signal_handler_t *signal_handler_create(void)
121 {
122 struct signal_handler *handler = bzalloc(sizeof(struct signal_handler));
123 handler->first = NULL;
124 handler->refs = 1;
125
126 if (pthread_mutex_init(&handler->mutex, NULL) != 0) {
127 blog(LOG_ERROR, "Couldn't create signal handler mutex!");
128 bfree(handler);
129 return NULL;
130 }
131 if (pthread_mutex_init_recursive(&handler->global_callbacks_mutex) !=
132 0) {
133 blog(LOG_ERROR, "Couldn't create signal handler global "
134 "callbacks mutex!");
135 pthread_mutex_destroy(&handler->mutex);
136 bfree(handler);
137 return NULL;
138 }
139
140 return handler;
141 }
142
signal_handler_actually_destroy(signal_handler_t * handler)143 static void signal_handler_actually_destroy(signal_handler_t *handler)
144 {
145 struct signal_info *sig = handler->first;
146 while (sig != NULL) {
147 struct signal_info *next = sig->next;
148 signal_info_destroy(sig);
149 sig = next;
150 }
151
152 da_free(handler->global_callbacks);
153 pthread_mutex_destroy(&handler->global_callbacks_mutex);
154 pthread_mutex_destroy(&handler->mutex);
155 bfree(handler);
156 }
157
signal_handler_destroy(signal_handler_t * handler)158 void signal_handler_destroy(signal_handler_t *handler)
159 {
160 if (handler && os_atomic_dec_long(&handler->refs) == 0) {
161 signal_handler_actually_destroy(handler);
162 }
163 }
164
signal_handler_add(signal_handler_t * handler,const char * signal_decl)165 bool signal_handler_add(signal_handler_t *handler, const char *signal_decl)
166 {
167 struct decl_info func = {0};
168 struct signal_info *sig, *last;
169 bool success = true;
170
171 if (!parse_decl_string(&func, signal_decl)) {
172 blog(LOG_ERROR, "Signal declaration invalid: %s", signal_decl);
173 return false;
174 }
175
176 pthread_mutex_lock(&handler->mutex);
177
178 sig = getsignal(handler, func.name, &last);
179 if (sig) {
180 blog(LOG_WARNING, "Signal declaration '%s' exists", func.name);
181 decl_info_free(&func);
182 success = false;
183 } else {
184 sig = signal_info_create(&func);
185 if (!last)
186 handler->first = sig;
187 else
188 last->next = sig;
189 }
190
191 pthread_mutex_unlock(&handler->mutex);
192
193 return success;
194 }
195
signal_handler_connect_internal(signal_handler_t * handler,const char * signal,signal_callback_t callback,void * data,bool keep_ref)196 static void signal_handler_connect_internal(signal_handler_t *handler,
197 const char *signal,
198 signal_callback_t callback,
199 void *data, bool keep_ref)
200 {
201 struct signal_info *sig, *last;
202 struct signal_callback cb_data = {callback, data, false, keep_ref};
203 size_t idx;
204
205 if (!handler)
206 return;
207
208 pthread_mutex_lock(&handler->mutex);
209 sig = getsignal(handler, signal, &last);
210 pthread_mutex_unlock(&handler->mutex);
211
212 if (!sig) {
213 blog(LOG_WARNING,
214 "signal_handler_connect: "
215 "signal '%s' not found",
216 signal);
217 return;
218 }
219
220 /* -------------- */
221
222 pthread_mutex_lock(&sig->mutex);
223
224 if (keep_ref)
225 os_atomic_inc_long(&handler->refs);
226
227 idx = signal_get_callback_idx(sig, callback, data);
228 if (keep_ref || idx == DARRAY_INVALID)
229 da_push_back(sig->callbacks, &cb_data);
230
231 pthread_mutex_unlock(&sig->mutex);
232 }
233
signal_handler_connect(signal_handler_t * handler,const char * signal,signal_callback_t callback,void * data)234 void signal_handler_connect(signal_handler_t *handler, const char *signal,
235 signal_callback_t callback, void *data)
236 {
237 signal_handler_connect_internal(handler, signal, callback, data, false);
238 }
239
signal_handler_connect_ref(signal_handler_t * handler,const char * signal,signal_callback_t callback,void * data)240 void signal_handler_connect_ref(signal_handler_t *handler, const char *signal,
241 signal_callback_t callback, void *data)
242 {
243 signal_handler_connect_internal(handler, signal, callback, data, true);
244 }
245
getsignal_locked(signal_handler_t * handler,const char * name)246 static inline struct signal_info *getsignal_locked(signal_handler_t *handler,
247 const char *name)
248 {
249 struct signal_info *sig;
250
251 if (!handler)
252 return NULL;
253
254 pthread_mutex_lock(&handler->mutex);
255 sig = getsignal(handler, name, NULL);
256 pthread_mutex_unlock(&handler->mutex);
257
258 return sig;
259 }
260
signal_handler_disconnect(signal_handler_t * handler,const char * signal,signal_callback_t callback,void * data)261 void signal_handler_disconnect(signal_handler_t *handler, const char *signal,
262 signal_callback_t callback, void *data)
263 {
264 struct signal_info *sig = getsignal_locked(handler, signal);
265 bool keep_ref = false;
266 size_t idx;
267
268 if (!sig)
269 return;
270
271 pthread_mutex_lock(&sig->mutex);
272
273 idx = signal_get_callback_idx(sig, callback, data);
274 if (idx != DARRAY_INVALID) {
275 if (sig->signalling) {
276 sig->callbacks.array[idx].remove = true;
277 } else {
278 keep_ref = sig->callbacks.array[idx].keep_ref;
279 da_erase(sig->callbacks, idx);
280 }
281 }
282
283 pthread_mutex_unlock(&sig->mutex);
284
285 if (keep_ref && os_atomic_dec_long(&handler->refs) == 0) {
286 signal_handler_actually_destroy(handler);
287 }
288 }
289
290 static THREAD_LOCAL struct signal_callback *current_signal_cb = NULL;
291 static THREAD_LOCAL struct global_callback_info *current_global_cb = NULL;
292
signal_handler_remove_current(void)293 void signal_handler_remove_current(void)
294 {
295 if (current_signal_cb)
296 current_signal_cb->remove = true;
297 else if (current_global_cb)
298 current_global_cb->remove = true;
299 }
300
signal_handler_signal(signal_handler_t * handler,const char * signal,calldata_t * params)301 void signal_handler_signal(signal_handler_t *handler, const char *signal,
302 calldata_t *params)
303 {
304 struct signal_info *sig = getsignal_locked(handler, signal);
305 long remove_refs = 0;
306
307 if (!sig)
308 return;
309
310 pthread_mutex_lock(&sig->mutex);
311 sig->signalling = true;
312
313 for (size_t i = 0; i < sig->callbacks.num; i++) {
314 struct signal_callback *cb = sig->callbacks.array + i;
315 if (!cb->remove) {
316 current_signal_cb = cb;
317 cb->callback(cb->data, params);
318 current_signal_cb = NULL;
319 }
320 }
321
322 for (size_t i = sig->callbacks.num; i > 0; i--) {
323 struct signal_callback *cb = sig->callbacks.array + i - 1;
324 if (cb->remove) {
325 if (cb->keep_ref)
326 remove_refs++;
327
328 da_erase(sig->callbacks, i - 1);
329 }
330 }
331
332 sig->signalling = false;
333 pthread_mutex_unlock(&sig->mutex);
334
335 pthread_mutex_lock(&handler->global_callbacks_mutex);
336
337 if (handler->global_callbacks.num) {
338 for (size_t i = 0; i < handler->global_callbacks.num; i++) {
339 struct global_callback_info *cb =
340 handler->global_callbacks.array + i;
341
342 if (!cb->remove) {
343 cb->signaling++;
344 current_global_cb = cb;
345 cb->callback(cb->data, signal, params);
346 current_global_cb = NULL;
347 cb->signaling--;
348 }
349 }
350
351 for (size_t i = handler->global_callbacks.num; i > 0; i--) {
352 struct global_callback_info *cb =
353 handler->global_callbacks.array + (i - 1);
354
355 if (cb->remove && !cb->signaling)
356 da_erase(handler->global_callbacks, i - 1);
357 }
358 }
359
360 pthread_mutex_unlock(&handler->global_callbacks_mutex);
361
362 if (remove_refs) {
363 os_atomic_set_long(&handler->refs,
364 os_atomic_load_long(&handler->refs) -
365 remove_refs);
366 }
367 }
368
signal_handler_connect_global(signal_handler_t * handler,global_signal_callback_t callback,void * data)369 void signal_handler_connect_global(signal_handler_t *handler,
370 global_signal_callback_t callback,
371 void *data)
372 {
373 struct global_callback_info cb_data = {callback, data, 0, false};
374 size_t idx;
375
376 if (!handler || !callback)
377 return;
378
379 pthread_mutex_lock(&handler->global_callbacks_mutex);
380
381 idx = da_find(handler->global_callbacks, &cb_data, 0);
382 if (idx == DARRAY_INVALID)
383 da_push_back(handler->global_callbacks, &cb_data);
384
385 pthread_mutex_unlock(&handler->global_callbacks_mutex);
386 }
387
signal_handler_disconnect_global(signal_handler_t * handler,global_signal_callback_t callback,void * data)388 void signal_handler_disconnect_global(signal_handler_t *handler,
389 global_signal_callback_t callback,
390 void *data)
391 {
392 struct global_callback_info cb_data = {callback, data, 0, false};
393 size_t idx;
394
395 if (!handler || !callback)
396 return;
397
398 pthread_mutex_lock(&handler->global_callbacks_mutex);
399
400 idx = da_find(handler->global_callbacks, &cb_data, 0);
401 if (idx != DARRAY_INVALID) {
402 struct global_callback_info *cb =
403 handler->global_callbacks.array + idx;
404
405 if (cb->signaling)
406 cb->remove = true;
407 else
408 da_erase(handler->global_callbacks, idx);
409 }
410
411 pthread_mutex_unlock(&handler->global_callbacks_mutex);
412 }
413