1145519Sdarrenr#include "pthread_impl.h"
2145519Sdarrenr#include <semaphore.h>
331183Speter#include <unistd.h>
431183Speter#include <dirent.h>
5255332Scy#include <string.h>
631183Speter#include <ctype.h>
7145519Sdarrenr#include "futex.h"
831183Speter#include "atomic.h"
9161357Sguido#include "../dirent/__dirent.h"
1063537Sarchie
1131183Speterstatic struct chain {
1253024Sguido	struct chain *next;
1331183Speter	int tid;
1431183Speter	sem_t target_sem, caller_sem;
1531183Speter} *volatile head;
1631183Speter
17170268Sdarrenrstatic volatile int synccall_lock[2];
1831183Speterstatic volatile int target_tid;
19170268Sdarrenrstatic void (*callback)(void *), *context;
2031183Speterstatic volatile int dummy = 0;
2131183Speterweak_alias(dummy, __block_new_threads);
2231183Speter
2331183Speterstatic void handler(int sig)
2431183Speter{
2531183Speter	struct chain ch;
2631183Speter	int old_errno = errno;
2731183Speter
2831183Speter	sem_init(&ch.target_sem, 0, 0);
29255332Scy	sem_init(&ch.caller_sem, 0, 0);
3031183Speter
3131183Speter	ch.tid = __syscall(SYS_gettid);
3231183Speter
3331183Speter	do ch.next = head;
34170268Sdarrenr	while (a_cas_p(&head, ch.next, &ch) != ch.next);
35255332Scy
36170268Sdarrenr	if (a_cas(&target_tid, ch.tid, 0) == (ch.tid | 0x80000000))
3731183Speter		__syscall(SYS_futex, &target_tid, FUTEX_UNLOCK_PI|FUTEX_PRIVATE);
3831183Speter
3931183Speter	sem_wait(&ch.target_sem);
4031183Speter	callback(context);
4131183Speter	sem_post(&ch.caller_sem);
4231183Speter	sem_wait(&ch.target_sem);
4331183Speter
4431183Speter	errno = old_errno;
4531183Speter}
4631183Speter
4731183Spetervoid __synccall(void (*func)(void *), void *ctx)
4864591Sdarrenr{
49145519Sdarrenr	sigset_t oldmask;
5031183Speter	int cs, i, r, pid, self;;
5137074Speter	DIR dir = {0};
5231183Speter	struct dirent *de;
5331183Speter	struct sigaction sa = { .sa_flags = 0, .sa_handler = handler };
5431183Speter	struct chain *cp, *next;
5531183Speter	struct timespec ts;
5631183Speter
5731183Speter	/* Blocking signals in two steps, first only app-level signals
5831183Speter	 * before taking the lock, then all signals after taking the lock,
5931183Speter	 * is necessary to achieve AS-safety. Blocking them all first would
6031183Speter	 * deadlock if multiple threads called __synccall. Waiting to block
6131183Speter	 * any until after the lock would allow re-entry in the same thread
6231183Speter	 * with the lock already held. */
6331183Speter	__block_app_sigs(&oldmask);
6431183Speter	LOCK(synccall_lock);
6531183Speter	__block_all_sigs(0);
6631183Speter	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
6731183Speter
6831183Speter	head = 0;
6931183Speter
7031183Speter	if (!libc.threaded) goto single_threaded;
7131183Speter
7231183Speter	callback = func;
7331183Speter	context = ctx;
7431183Speter
7531183Speter	/* This atomic store ensures that any signaled threads will see the
7631183Speter	 * above stores, and prevents more than a bounded number of threads,
7731183Speter	 * those already in pthread_create, from creating new threads until
7831183Speter	 * the value is cleared to zero again. */
7931183Speter	a_store(&__block_new_threads, 1);
8031183Speter
8131183Speter	/* Block even implementation-internal signals, so that nothing
8231183Speter	 * interrupts the SIGSYNCCALL handlers. The main possible source
8331183Speter	 * of trouble is asynchronous cancellation. */
8431183Speter	memset(&sa.sa_mask, -1, sizeof sa.sa_mask);
8531183Speter	__libc_sigaction(SIGSYNCCALL, &sa, 0);
8631183Speter
8731183Speter	pid = __syscall(SYS_getpid);
8831183Speter	self = __syscall(SYS_gettid);
8931183Speter
9031183Speter	/* Since opendir is not AS-safe, the DIR needs to be setup manually
9131183Speter	 * in automatic storage. Thankfully this is easy. */
9231183Speter	dir.fd = open("/proc/self/task", O_RDONLY|O_DIRECTORY|O_CLOEXEC);
9331183Speter	if (dir.fd < 0) goto out;
9431183Speter
9531183Speter	/* Initially send one signal per counted thread. But since we can't
9631183Speter	 * synchronize with thread creation/exit here, there could be too
9731183Speter	 * few signals. This initial signaling is just an optimization, not
9831183Speter	 * part of the logic. */
9931183Speter	for (i=libc.threads_minus_1; i; i--)
10031183Speter		__syscall(SYS_kill, pid, SIGSYNCCALL);
10131183Speter
10231183Speter	/* Loop scanning the kernel-provided thread list until it shows no
10331183Speter	 * threads that have not already replied to the signal. */
10431183Speter	for (;;) {
10531183Speter		int miss_cnt = 0;
10631183Speter		while ((de = readdir(&dir))) {
10731183Speter			if (!isdigit(de->d_name[0])) continue;
10831183Speter			int tid = atoi(de->d_name);
10931183Speter			if (tid == self || !tid) continue;
11031183Speter
11131183Speter			/* Set the target thread as the PI futex owner before
11231183Speter			 * checking if it's in the list of caught threads. If it
11331183Speter			 * adds itself to the list after we check for it, then
11431183Speter			 * it will see its own tid in the PI futex and perform
11531183Speter			 * the unlock operation. */
11631183Speter			a_store(&target_tid, tid);
11731183Speter
11831183Speter			/* Thread-already-caught is a success condition. */
11931183Speter			for (cp = head; cp && cp->tid != tid; cp=cp->next);
12031183Speter			if (cp) continue;
12131183Speter
12231183Speter			r = -__syscall(SYS_tgkill, pid, tid, SIGSYNCCALL);
12331183Speter
12431183Speter			/* Target thread exit is a success condition. */
12531183Speter			if (r == ESRCH) continue;
12631183Speter
12731183Speter			/* The FUTEX_LOCK_PI operation is used to loan priority
12831183Speter			 * to the target thread, which otherwise may be unable
12931183Speter			 * to run. Timeout is necessary because there is a race
13031183Speter			 * condition where the tid may be reused by a different
13131183Speter			 * process. */
13231183Speter			clock_gettime(CLOCK_REALTIME, &ts);
13331183Speter			ts.tv_nsec += 10000000;
13431183Speter			if (ts.tv_nsec >= 1000000000) {
13531183Speter				ts.tv_sec++;
13631183Speter				ts.tv_nsec -= 1000000000;
13731183Speter			}
13831183Speter			r = -__syscall(SYS_futex, &target_tid,
13931183Speter				FUTEX_LOCK_PI|FUTEX_PRIVATE, 0, &ts);
14031183Speter
14131183Speter			/* Obtaining the lock means the thread responded. ESRCH
14231183Speter			 * means the target thread exited, which is okay too. */
14331183Speter			if (!r || r == ESRCH) continue;
14431183Speter
14531183Speter			miss_cnt++;
14631183Speter		}
14731183Speter		if (!miss_cnt) break;
14831183Speter		rewinddir(&dir);
14931183Speter	}
15031183Speter	close(dir.fd);
15131183Speter
15231183Speter	/* Serialize execution of callback in caught threads. */
15331183Speter	for (cp=head; cp; cp=cp->next) {
15431183Speter		sem_post(&cp->target_sem);
15531183Speter		sem_wait(&cp->caller_sem);
15631183Speter	}
15731183Speter
15831183Speter	sa.sa_handler = SIG_IGN;
15931183Speter	__libc_sigaction(SIGSYNCCALL, &sa, 0);
16031183Speter
16131183Spetersingle_threaded:
16231183Speter	func(ctx);
16331183Speter
16431183Speter	/* Only release the caught threads once all threads, including the
16555924Sguido	 * caller, have returned from the callback function. */
16631183Speter	for (cp=head; cp; cp=next) {
16731183Speter		next = cp->next;
16831183Speter		sem_post(&cp->target_sem);
16931183Speter	}
17031183Speter
17131183Speterout:
17231183Speter	a_store(&__block_new_threads, 0);
17331183Speter	__wake(&__block_new_threads, -1, 1);
17431183Speter
17531183Speter	pthread_setcancelstate(cs, 0);
17631183Speter	UNLOCK(synccall_lock);
17731183Speter	__restore_sigs(&oldmask);
17831183Speter}
17931183Speter