1#include "pthread_impl.h"
2#include <semaphore.h>
3#include <unistd.h>
4#include <dirent.h>
5#include <string.h>
6#include <ctype.h>
7#include "futex.h"
8#include "atomic.h"
9#include "../dirent/__dirent.h"
10
11static struct chain {
12	struct chain *next;
13	int tid;
14	sem_t target_sem, caller_sem;
15} *volatile head;
16
17static volatile int synccall_lock[2];
18static volatile int target_tid;
19static void (*callback)(void *), *context;
20static volatile int dummy = 0;
21weak_alias(dummy, __block_new_threads);
22
23static void handler(int sig)
24{
25	struct chain ch;
26	int old_errno = errno;
27
28	sem_init(&ch.target_sem, 0, 0);
29	sem_init(&ch.caller_sem, 0, 0);
30
31	ch.tid = __syscall(SYS_gettid);
32
33	do ch.next = head;
34	while (a_cas_p(&head, ch.next, &ch) != ch.next);
35
36	if (a_cas(&target_tid, ch.tid, 0) == (ch.tid | 0x80000000))
37		__syscall(SYS_futex, &target_tid, FUTEX_UNLOCK_PI|FUTEX_PRIVATE);
38
39	sem_wait(&ch.target_sem);
40	callback(context);
41	sem_post(&ch.caller_sem);
42	sem_wait(&ch.target_sem);
43
44	errno = old_errno;
45}
46
47void __synccall(void (*func)(void *), void *ctx)
48{
49	sigset_t oldmask;
50	int cs, i, r, pid, self;;
51	DIR dir = {0};
52	struct dirent *de;
53	struct sigaction sa = { .sa_flags = 0, .sa_handler = handler };
54	struct chain *cp, *next;
55	struct timespec ts;
56
57	/* Blocking signals in two steps, first only app-level signals
58	 * before taking the lock, then all signals after taking the lock,
59	 * is necessary to achieve AS-safety. Blocking them all first would
60	 * deadlock if multiple threads called __synccall. Waiting to block
61	 * any until after the lock would allow re-entry in the same thread
62	 * with the lock already held. */
63	__block_app_sigs(&oldmask);
64	LOCK(synccall_lock);
65	__block_all_sigs(0);
66	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
67
68	head = 0;
69
70	if (!libc.threaded) goto single_threaded;
71
72	callback = func;
73	context = ctx;
74
75	/* This atomic store ensures that any signaled threads will see the
76	 * above stores, and prevents more than a bounded number of threads,
77	 * those already in pthread_create, from creating new threads until
78	 * the value is cleared to zero again. */
79	a_store(&__block_new_threads, 1);
80
81	/* Block even implementation-internal signals, so that nothing
82	 * interrupts the SIGSYNCCALL handlers. The main possible source
83	 * of trouble is asynchronous cancellation. */
84	memset(&sa.sa_mask, -1, sizeof sa.sa_mask);
85	__libc_sigaction(SIGSYNCCALL, &sa, 0);
86
87	pid = __syscall(SYS_getpid);
88	self = __syscall(SYS_gettid);
89
90	/* Since opendir is not AS-safe, the DIR needs to be setup manually
91	 * in automatic storage. Thankfully this is easy. */
92	dir.fd = open("/proc/self/task", O_RDONLY|O_DIRECTORY|O_CLOEXEC);
93	if (dir.fd < 0) goto out;
94
95	/* Initially send one signal per counted thread. But since we can't
96	 * synchronize with thread creation/exit here, there could be too
97	 * few signals. This initial signaling is just an optimization, not
98	 * part of the logic. */
99	for (i=libc.threads_minus_1; i; i--)
100		__syscall(SYS_kill, pid, SIGSYNCCALL);
101
102	/* Loop scanning the kernel-provided thread list until it shows no
103	 * threads that have not already replied to the signal. */
104	for (;;) {
105		int miss_cnt = 0;
106		while ((de = readdir(&dir))) {
107			if (!isdigit(de->d_name[0])) continue;
108			int tid = atoi(de->d_name);
109			if (tid == self || !tid) continue;
110
111			/* Set the target thread as the PI futex owner before
112			 * checking if it's in the list of caught threads. If it
113			 * adds itself to the list after we check for it, then
114			 * it will see its own tid in the PI futex and perform
115			 * the unlock operation. */
116			a_store(&target_tid, tid);
117
118			/* Thread-already-caught is a success condition. */
119			for (cp = head; cp && cp->tid != tid; cp=cp->next);
120			if (cp) continue;
121
122			r = -__syscall(SYS_tgkill, pid, tid, SIGSYNCCALL);
123
124			/* Target thread exit is a success condition. */
125			if (r == ESRCH) continue;
126
127			/* The FUTEX_LOCK_PI operation is used to loan priority
128			 * to the target thread, which otherwise may be unable
129			 * to run. Timeout is necessary because there is a race
130			 * condition where the tid may be reused by a different
131			 * process. */
132			clock_gettime(CLOCK_REALTIME, &ts);
133			ts.tv_nsec += 10000000;
134			if (ts.tv_nsec >= 1000000000) {
135				ts.tv_sec++;
136				ts.tv_nsec -= 1000000000;
137			}
138			r = -__syscall(SYS_futex, &target_tid,
139				FUTEX_LOCK_PI|FUTEX_PRIVATE, 0, &ts);
140
141			/* Obtaining the lock means the thread responded. ESRCH
142			 * means the target thread exited, which is okay too. */
143			if (!r || r == ESRCH) continue;
144
145			miss_cnt++;
146		}
147		if (!miss_cnt) break;
148		rewinddir(&dir);
149	}
150	close(dir.fd);
151
152	/* Serialize execution of callback in caught threads. */
153	for (cp=head; cp; cp=cp->next) {
154		sem_post(&cp->target_sem);
155		sem_wait(&cp->caller_sem);
156	}
157
158	sa.sa_handler = SIG_IGN;
159	__libc_sigaction(SIGSYNCCALL, &sa, 0);
160
161single_threaded:
162	func(ctx);
163
164	/* Only release the caught threads once all threads, including the
165	 * caller, have returned from the callback function. */
166	for (cp=head; cp; cp=next) {
167		next = cp->next;
168		sem_post(&cp->target_sem);
169	}
170
171out:
172	a_store(&__block_new_threads, 0);
173	__wake(&__block_new_threads, -1, 1);
174
175	pthread_setcancelstate(cs, 0);
176	UNLOCK(synccall_lock);
177	__restore_sigs(&oldmask);
178}
179