1/*
2 * David Leonard <d@openbsd.org>, 1999. Public domain.
3 * $FreeBSD$
4 */
5#include "namespace.h"
6#include <sys/errno.h>
7#include <pthread.h>
8#include "un-namespace.h"
9#include "thr_private.h"
10
11__weak_reference(_pthread_cancel, pthread_cancel);
12__weak_reference(_pthread_setcancelstate, pthread_setcancelstate);
13__weak_reference(_pthread_setcanceltype, pthread_setcanceltype);
14__weak_reference(_pthread_testcancel, pthread_testcancel);
15
16static inline int
17checkcancel(struct pthread *curthread)
18{
19	if ((curthread->cancelflags & THR_CANCELLING) != 0) {
20		/*
21		 * It is possible for this thread to be swapped out
22		 * while performing cancellation; do not allow it
23		 * to be cancelled again.
24		 */
25		if ((curthread->flags & THR_FLAGS_EXITING) != 0) {
26			/*
27			 * This may happen once, but after this, it
28			 * shouldn't happen again.
29			 */
30			curthread->cancelflags &= ~THR_CANCELLING;
31			return (0);
32		}
33		if ((curthread->cancelflags & PTHREAD_CANCEL_DISABLE) == 0) {
34			curthread->cancelflags &= ~THR_CANCELLING;
35			return (1);
36		}
37	}
38	return (0);
39}
40
41static inline void
42testcancel(struct pthread *curthread)
43{
44	if (checkcancel(curthread) != 0) {
45		/* Unlock before exiting: */
46		THR_THREAD_UNLOCK(curthread, curthread);
47
48		_thr_exit_cleanup();
49		_pthread_exit(PTHREAD_CANCELED);
50		PANIC("cancel");
51	}
52}
53
54int
55_pthread_cancel(pthread_t pthread)
56{
57	struct pthread *curthread = _get_curthread();
58	struct pthread *joinee = NULL;
59	struct kse_mailbox *kmbx = NULL;
60	int ret;
61
62	if ((ret = _thr_ref_add(curthread, pthread, /*include dead*/0)) == 0) {
63		/*
64		 * Take the thread's lock while we change the cancel flags.
65		 */
66		THR_THREAD_LOCK(curthread, pthread);
67		THR_SCHED_LOCK(curthread, pthread);
68		if (pthread->flags & THR_FLAGS_EXITING) {
69			THR_SCHED_UNLOCK(curthread, pthread);
70			THR_THREAD_UNLOCK(curthread, pthread);
71			_thr_ref_delete(curthread, pthread);
72			return (ESRCH);
73		}
74		if (((pthread->cancelflags & PTHREAD_CANCEL_DISABLE) != 0) ||
75		    (((pthread->cancelflags & THR_AT_CANCEL_POINT) == 0) &&
76		    ((pthread->cancelflags & PTHREAD_CANCEL_ASYNCHRONOUS) == 0)))
77			/* Just mark it for cancellation: */
78			pthread->cancelflags |= THR_CANCELLING;
79		else {
80			/*
81			 * Check if we need to kick it back into the
82			 * run queue:
83			 */
84			switch (pthread->state) {
85			case PS_RUNNING:
86				/* No need to resume: */
87				pthread->cancelflags |= THR_CANCELLING;
88				break;
89
90			case PS_LOCKWAIT:
91				/*
92				 * These can't be removed from the queue.
93				 * Just mark it as cancelling and tell it
94				 * to yield once it leaves the critical
95				 * region.
96				 */
97				pthread->cancelflags |= THR_CANCELLING;
98				pthread->critical_yield = 1;
99				break;
100
101			case PS_SLEEP_WAIT:
102			case PS_SIGSUSPEND:
103			case PS_SIGWAIT:
104				/* Interrupt and resume: */
105				pthread->interrupted = 1;
106				pthread->cancelflags |= THR_CANCELLING;
107				kmbx = _thr_setrunnable_unlocked(pthread);
108				break;
109
110			case PS_JOIN:
111				/* Disconnect the thread from the joinee: */
112				joinee = pthread->join_status.thread;
113				pthread->join_status.thread = NULL;
114				pthread->cancelflags |= THR_CANCELLING;
115				kmbx = _thr_setrunnable_unlocked(pthread);
116				if ((joinee != NULL) &&
117				    (pthread->kseg == joinee->kseg)) {
118					/* Remove the joiner from the joinee. */
119					joinee->joiner = NULL;
120					joinee = NULL;
121				}
122				break;
123
124			case PS_SUSPENDED:
125			case PS_MUTEX_WAIT:
126			case PS_COND_WAIT:
127				/*
128				 * Threads in these states may be in queues.
129				 * In order to preserve queue integrity, the
130				 * cancelled thread must remove itself from the
131				 * queue.  Mark the thread as interrupted and
132				 * needing cancellation, and set the state to
133				 * running.  When the thread resumes, it will
134				 * remove itself from the queue and call the
135				 * cancellation completion routine.
136				 */
137				pthread->interrupted = 1;
138				pthread->cancelflags |= THR_CANCEL_NEEDED;
139				kmbx = _thr_setrunnable_unlocked(pthread);
140				pthread->continuation =
141					_thr_finish_cancellation;
142				break;
143
144			case PS_DEAD:
145			case PS_DEADLOCK:
146			case PS_STATE_MAX:
147				/* Ignore - only here to silence -Wall: */
148				break;
149			}
150			if ((pthread->cancelflags & THR_AT_CANCEL_POINT) &&
151			    (pthread->blocked != 0 ||
152			     pthread->attr.flags & PTHREAD_SCOPE_SYSTEM))
153				kse_thr_interrupt(&pthread->tcb->tcb_tmbx,
154					KSE_INTR_INTERRUPT, 0);
155		}
156
157		/*
158		 * Release the thread's lock and remove the
159		 * reference:
160		 */
161		THR_SCHED_UNLOCK(curthread, pthread);
162		THR_THREAD_UNLOCK(curthread, pthread);
163		_thr_ref_delete(curthread, pthread);
164		if (kmbx != NULL)
165			kse_wakeup(kmbx);
166
167		if ((joinee != NULL) &&
168		    (_thr_ref_add(curthread, joinee, /* include dead */1) == 0)) {
169			/* Remove the joiner from the joinee. */
170			THR_SCHED_LOCK(curthread, joinee);
171			joinee->joiner = NULL;
172			THR_SCHED_UNLOCK(curthread, joinee);
173			_thr_ref_delete(curthread, joinee);
174		}
175	}
176	return (ret);
177}
178
179int
180_pthread_setcancelstate(int state, int *oldstate)
181{
182	struct pthread	*curthread = _get_curthread();
183	int ostate;
184	int ret;
185	int need_exit = 0;
186
187	/* Take the thread's lock while fiddling with the state: */
188	THR_THREAD_LOCK(curthread, curthread);
189
190	ostate = curthread->cancelflags & PTHREAD_CANCEL_DISABLE;
191
192	switch (state) {
193	case PTHREAD_CANCEL_ENABLE:
194		curthread->cancelflags &= ~PTHREAD_CANCEL_DISABLE;
195		if ((curthread->cancelflags & PTHREAD_CANCEL_ASYNCHRONOUS) != 0)
196			need_exit = checkcancel(curthread);
197		ret = 0;
198		break;
199	case PTHREAD_CANCEL_DISABLE:
200		curthread->cancelflags |= PTHREAD_CANCEL_DISABLE;
201		ret = 0;
202		break;
203	default:
204		ret = EINVAL;
205	}
206
207	THR_THREAD_UNLOCK(curthread, curthread);
208	if (need_exit != 0) {
209		_thr_exit_cleanup();
210		_pthread_exit(PTHREAD_CANCELED);
211		PANIC("cancel");
212	}
213	if (ret == 0 && oldstate != NULL)
214		*oldstate = ostate;
215
216	return (ret);
217}
218
219int
220_pthread_setcanceltype(int type, int *oldtype)
221{
222	struct pthread	*curthread = _get_curthread();
223	int otype;
224	int ret;
225	int need_exit = 0;
226
227	/* Take the thread's lock while fiddling with the state: */
228	THR_THREAD_LOCK(curthread, curthread);
229
230	otype = curthread->cancelflags & PTHREAD_CANCEL_ASYNCHRONOUS;
231	switch (type) {
232	case PTHREAD_CANCEL_ASYNCHRONOUS:
233		curthread->cancelflags |= PTHREAD_CANCEL_ASYNCHRONOUS;
234		need_exit = checkcancel(curthread);
235		ret = 0;
236		break;
237	case PTHREAD_CANCEL_DEFERRED:
238		curthread->cancelflags &= ~PTHREAD_CANCEL_ASYNCHRONOUS;
239		ret = 0;
240		break;
241	default:
242		ret = EINVAL;
243	}
244
245	THR_THREAD_UNLOCK(curthread, curthread);
246	if (need_exit != 0) {
247		_thr_exit_cleanup();
248		_pthread_exit(PTHREAD_CANCELED);
249		PANIC("cancel");
250	}
251	if (ret == 0 && oldtype != NULL)
252		*oldtype = otype;
253
254	return (ret);
255}
256
257void
258_pthread_testcancel(void)
259{
260	struct pthread	*curthread = _get_curthread();
261
262	THR_THREAD_LOCK(curthread, curthread);
263	testcancel(curthread);
264	THR_THREAD_UNLOCK(curthread, curthread);
265}
266
267void
268_thr_cancel_enter(struct pthread *thread)
269{
270	/* Look for a cancellation before we block: */
271	THR_THREAD_LOCK(thread, thread);
272	testcancel(thread);
273	thread->cancelflags |= THR_AT_CANCEL_POINT;
274	THR_THREAD_UNLOCK(thread, thread);
275}
276
277void
278_thr_cancel_leave(struct pthread *thread, int check)
279{
280	THR_THREAD_LOCK(thread, thread);
281	thread->cancelflags &= ~THR_AT_CANCEL_POINT;
282	/* Look for a cancellation after we unblock: */
283	if (check)
284		testcancel(thread);
285	THR_THREAD_UNLOCK(thread, thread);
286}
287
288void
289_thr_finish_cancellation(void *arg __unused)
290{
291	struct pthread	*curthread = _get_curthread();
292
293	curthread->continuation = NULL;
294	curthread->interrupted = 0;
295
296	THR_THREAD_LOCK(curthread, curthread);
297	if ((curthread->cancelflags & THR_CANCEL_NEEDED) != 0) {
298		curthread->cancelflags &= ~THR_CANCEL_NEEDED;
299		THR_THREAD_UNLOCK(curthread, curthread);
300		_thr_exit_cleanup();
301		_pthread_exit(PTHREAD_CANCELED);
302	}
303	THR_THREAD_UNLOCK(curthread, curthread);
304}
305