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