1/*
2 * Copyright (c) 2005 David Xu <davidxu@freebsd.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice unmodified, this list of conditions, and the following
10 *    disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 *
26 * $FreeBSD$
27 *
28 */
29
30#include <sys/types.h>
31
32#include "namespace.h"
33#include <err.h>
34#include <errno.h>
35#include <ucontext.h>
36#include <sys/thr.h>
37#include <stdatomic.h>
38#include <stdio.h>
39#include <stdlib.h>
40#include <string.h>
41#include <signal.h>
42#include <pthread.h>
43#include "un-namespace.h"
44
45#include "sigev_thread.h"
46
47LIST_HEAD(sigev_list_head, sigev_node);
48#define HASH_QUEUES		17
49#define	HASH(t, id)		((((id) << 3) + (t)) % HASH_QUEUES)
50
51static struct sigev_list_head	sigev_hash[HASH_QUEUES];
52static struct sigev_list_head	sigev_all;
53static LIST_HEAD(,sigev_thread)	sigev_threads;
54static atomic_int		sigev_generation;
55static pthread_mutex_t		*sigev_list_mtx;
56static pthread_once_t		sigev_once = PTHREAD_ONCE_INIT;
57static pthread_once_t		sigev_once_default = PTHREAD_ONCE_INIT;
58static struct sigev_thread	*sigev_default_thread;
59static pthread_attr_t		sigev_default_attr;
60static int			atfork_registered;
61
62static void	__sigev_fork_prepare(void);
63static void	__sigev_fork_parent(void);
64static void	__sigev_fork_child(void);
65static struct sigev_thread	*sigev_thread_create(int);
66static void	*sigev_service_loop(void *);
67static void	*worker_routine(void *);
68static void	worker_cleanup(void *);
69
70#pragma weak _pthread_create
71
72static void
73attrcopy(pthread_attr_t *src, pthread_attr_t *dst)
74{
75	struct sched_param sched;
76	void *a;
77	size_t u;
78	int v;
79
80	_pthread_attr_getschedpolicy(src, &v);
81	_pthread_attr_setschedpolicy(dst, v);
82
83	_pthread_attr_getinheritsched(src, &v);
84	_pthread_attr_setinheritsched(dst, v);
85
86	_pthread_attr_getschedparam(src, &sched);
87	_pthread_attr_setschedparam(dst, &sched);
88
89	_pthread_attr_getscope(src, &v);
90	_pthread_attr_setscope(dst, v);
91
92	_pthread_attr_getstacksize(src, &u);
93	_pthread_attr_setstacksize(dst, u);
94
95	_pthread_attr_getstackaddr(src, &a);
96	_pthread_attr_setstackaddr(src, a);
97
98	_pthread_attr_getguardsize(src, &u);
99	_pthread_attr_setguardsize(dst, u);
100}
101
102static __inline int
103have_threads(void)
104{
105	return (&_pthread_create != NULL);
106}
107
108void
109__sigev_thread_init(void)
110{
111	static int inited = 0;
112	pthread_mutexattr_t mattr;
113	int i;
114
115	_pthread_mutexattr_init(&mattr);
116	_pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_NORMAL);
117	sigev_list_mtx = malloc(sizeof(pthread_mutex_t));
118	_pthread_mutex_init(sigev_list_mtx, &mattr);
119	_pthread_mutexattr_destroy(&mattr);
120
121	for (i = 0; i < HASH_QUEUES; ++i)
122		LIST_INIT(&sigev_hash[i]);
123	LIST_INIT(&sigev_all);
124	LIST_INIT(&sigev_threads);
125	sigev_default_thread = NULL;
126	if (atfork_registered == 0) {
127		_pthread_atfork(
128			__sigev_fork_prepare,
129			__sigev_fork_parent,
130			__sigev_fork_child);
131		atfork_registered = 1;
132	}
133	if (!inited) {
134		_pthread_attr_init(&sigev_default_attr);
135		_pthread_attr_setscope(&sigev_default_attr,
136			PTHREAD_SCOPE_SYSTEM);
137		_pthread_attr_setdetachstate(&sigev_default_attr,
138			PTHREAD_CREATE_DETACHED);
139		inited = 1;
140	}
141	sigev_default_thread = sigev_thread_create(0);
142}
143
144int
145__sigev_check_init(void)
146{
147	if (!have_threads())
148		return (-1);
149
150	_pthread_once(&sigev_once, __sigev_thread_init);
151	return (sigev_default_thread != NULL) ? 0 : -1;
152}
153
154static void
155__sigev_fork_prepare(void)
156{
157}
158
159static void
160__sigev_fork_parent(void)
161{
162}
163
164static void
165__sigev_fork_child(void)
166{
167	/*
168	 * This is a hack, the thread libraries really should
169	 * check if the handlers were already registered in
170	 * pthread_atfork().
171	 */
172	atfork_registered = 1;
173	memcpy(&sigev_once, &sigev_once_default, sizeof(sigev_once));
174	__sigev_thread_init();
175}
176
177void
178__sigev_list_lock(void)
179{
180	_pthread_mutex_lock(sigev_list_mtx);
181}
182
183void
184__sigev_list_unlock(void)
185{
186	_pthread_mutex_unlock(sigev_list_mtx);
187}
188
189struct sigev_node *
190__sigev_alloc(int type, const struct sigevent *evp, struct sigev_node *prev,
191	int usedefault)
192{
193	struct sigev_node *sn;
194
195	sn = calloc(1, sizeof(*sn));
196	if (sn != NULL) {
197		sn->sn_value = evp->sigev_value;
198		sn->sn_func  = evp->sigev_notify_function;
199		sn->sn_gen   = atomic_fetch_add_explicit(&sigev_generation, 1,
200		    memory_order_relaxed);
201		sn->sn_type  = type;
202		_pthread_attr_init(&sn->sn_attr);
203		_pthread_attr_setdetachstate(&sn->sn_attr, PTHREAD_CREATE_DETACHED);
204		if (evp->sigev_notify_attributes)
205			attrcopy(evp->sigev_notify_attributes, &sn->sn_attr);
206		if (prev) {
207			__sigev_list_lock();
208			prev->sn_tn->tn_refcount++;
209			__sigev_list_unlock();
210			sn->sn_tn = prev->sn_tn;
211		} else {
212			sn->sn_tn = sigev_thread_create(usedefault);
213			if (sn->sn_tn == NULL) {
214				_pthread_attr_destroy(&sn->sn_attr);
215				free(sn);
216				sn = NULL;
217			}
218		}
219	}
220	return (sn);
221}
222
223void
224__sigev_get_sigevent(struct sigev_node *sn, struct sigevent *newevp,
225	sigev_id_t id)
226{
227	/*
228	 * Build a new sigevent, and tell kernel to deliver SIGLIBRT
229	 * signal to the new thread.
230	 */
231	newevp->sigev_notify = SIGEV_THREAD_ID;
232	newevp->sigev_signo  = SIGLIBRT;
233	newevp->sigev_notify_thread_id = (lwpid_t)sn->sn_tn->tn_lwpid;
234	newevp->sigev_value.sival_ptr = (void *)id;
235}
236
237void
238__sigev_free(struct sigev_node *sn)
239{
240	_pthread_attr_destroy(&sn->sn_attr);
241	free(sn);
242}
243
244struct sigev_node *
245__sigev_find(int type, sigev_id_t id)
246{
247	struct sigev_node *sn;
248	int chain = HASH(type, id);
249
250	LIST_FOREACH(sn, &sigev_hash[chain], sn_link) {
251		if (sn->sn_type == type && sn->sn_id == id)
252			break;
253	}
254	return (sn);
255}
256
257int
258__sigev_register(struct sigev_node *sn)
259{
260	int chain = HASH(sn->sn_type, sn->sn_id);
261
262	LIST_INSERT_HEAD(&sigev_hash[chain], sn, sn_link);
263	return (0);
264}
265
266int
267__sigev_delete(int type, sigev_id_t id)
268{
269	struct sigev_node *sn;
270
271	sn = __sigev_find(type, id);
272	if (sn != NULL)
273		return (__sigev_delete_node(sn));
274	return (0);
275}
276
277int
278__sigev_delete_node(struct sigev_node *sn)
279{
280	LIST_REMOVE(sn, sn_link);
281
282	if (--sn->sn_tn->tn_refcount == 0)
283		_pthread_kill(sn->sn_tn->tn_thread, SIGLIBRT);
284	if (sn->sn_flags & SNF_WORKING)
285		sn->sn_flags |= SNF_REMOVED;
286	else
287		__sigev_free(sn);
288	return (0);
289}
290
291static sigev_id_t
292sigev_get_id(siginfo_t *si)
293{
294	switch(si->si_code) {
295	case SI_TIMER:
296		return (si->si_timerid);
297	case SI_MESGQ:
298		return (si->si_mqd);
299	case SI_ASYNCIO:
300		return (sigev_id_t)si->si_value.sival_ptr;
301	}
302	return (-1);
303}
304
305static struct sigev_thread *
306sigev_thread_create(int usedefault)
307{
308	struct sigev_thread *tn;
309	sigset_t set, oset;
310	int ret;
311
312	if (usedefault && sigev_default_thread) {
313		__sigev_list_lock();
314		sigev_default_thread->tn_refcount++;
315		__sigev_list_unlock();
316		return (sigev_default_thread);
317	}
318
319	tn = malloc(sizeof(*tn));
320	tn->tn_cur = NULL;
321	tn->tn_lwpid = -1;
322	tn->tn_refcount = 1;
323	_pthread_cond_init(&tn->tn_cv, NULL);
324
325	/* for debug */
326	__sigev_list_lock();
327	LIST_INSERT_HEAD(&sigev_threads, tn, tn_link);
328	__sigev_list_unlock();
329
330	sigfillset(&set);	/* SIGLIBRT is masked. */
331	sigdelset(&set, SIGBUS);
332	sigdelset(&set, SIGILL);
333	sigdelset(&set, SIGFPE);
334	sigdelset(&set, SIGSEGV);
335	sigdelset(&set, SIGTRAP);
336	_sigprocmask(SIG_SETMASK, &set, &oset);
337	ret = _pthread_create(&tn->tn_thread, &sigev_default_attr,
338		 sigev_service_loop, tn);
339	_sigprocmask(SIG_SETMASK, &oset, NULL);
340
341	if (ret != 0) {
342		__sigev_list_lock();
343		LIST_REMOVE(tn, tn_link);
344		__sigev_list_unlock();
345		free(tn);
346		tn = NULL;
347	} else {
348		/* wait the thread to get its lwpid */
349
350		__sigev_list_lock();
351		while (tn->tn_lwpid == -1)
352			_pthread_cond_wait(&tn->tn_cv, sigev_list_mtx);
353		__sigev_list_unlock();
354	}
355	return (tn);
356}
357
358/*
359 * The thread receives notification from kernel and creates
360 * a thread to call user callback function.
361 */
362static void *
363sigev_service_loop(void *arg)
364{
365	static int failure;
366
367	siginfo_t si;
368	sigset_t set;
369	struct sigev_thread *tn;
370	struct sigev_node *sn;
371	sigev_id_t id;
372	pthread_t td;
373	int ret;
374
375	tn = arg;
376	thr_self(&tn->tn_lwpid);
377	__sigev_list_lock();
378	_pthread_cond_broadcast(&tn->tn_cv);
379	__sigev_list_unlock();
380
381	sigemptyset(&set);
382	sigaddset(&set, SIGLIBRT);
383	for (;;) {
384		ret = sigwaitinfo(&set, &si);
385
386		__sigev_list_lock();
387		if (tn->tn_refcount == 0) {
388			LIST_REMOVE(tn, tn_link);
389			__sigev_list_unlock();
390			free(tn);
391			break;
392		}
393
394		if (ret == -1) {
395			__sigev_list_unlock();
396			continue;
397		}
398
399		id = sigev_get_id(&si);
400		sn = __sigev_find(si.si_code, id);
401		if (sn == NULL) {
402			__sigev_list_unlock();
403			continue;
404		}
405
406		sn->sn_info = si;
407		if (sn->sn_flags & SNF_SYNC)
408			tn->tn_cur = sn;
409		else
410			tn->tn_cur = NULL;
411		sn->sn_flags |= SNF_WORKING;
412		__sigev_list_unlock();
413
414		ret = _pthread_create(&td, &sn->sn_attr, worker_routine, sn);
415		if (ret != 0) {
416			if (failure++ < 5)
417				warnc(ret, "%s:%s failed to create thread.\n",
418					__FILE__, __func__);
419
420			__sigev_list_lock();
421			sn->sn_flags &= ~SNF_WORKING;
422			if (sn->sn_flags & SNF_REMOVED)
423				__sigev_free(sn);
424			__sigev_list_unlock();
425		} else if (tn->tn_cur) {
426			__sigev_list_lock();
427			while (tn->tn_cur)
428				_pthread_cond_wait(&tn->tn_cv, sigev_list_mtx);
429			__sigev_list_unlock();
430		}
431	}
432	return (0);
433}
434
435/*
436 * newly created worker thread to call user callback function.
437 */
438static void *
439worker_routine(void *arg)
440{
441	struct sigev_node *sn = arg;
442
443	pthread_cleanup_push(worker_cleanup, sn);
444	sn->sn_dispatch(sn);
445	pthread_cleanup_pop(1);
446
447	return (0);
448}
449
450/* clean up a notification after dispatch. */
451static void
452worker_cleanup(void *arg)
453{
454	struct sigev_node *sn = arg;
455
456	__sigev_list_lock();
457	if (sn->sn_flags & SNF_SYNC) {
458		sn->sn_tn->tn_cur = NULL;
459		_pthread_cond_broadcast(&sn->sn_tn->tn_cv);
460	}
461	if (sn->sn_flags & SNF_REMOVED)
462		__sigev_free(sn);
463	else
464		sn->sn_flags &= ~SNF_WORKING;
465	__sigev_list_unlock();
466}
467