| 1 | #include "pthread_impl.h" |
| 2 | #include <semaphore.h> |
| 3 | #include <string.h> |
| 4 | |
| 5 | static void dummy_0(void) |
| 6 | { |
| 7 | } |
| 8 | |
| 9 | weak_alias(dummy_0, __tl_lock); |
| 10 | weak_alias(dummy_0, __tl_unlock); |
| 11 | |
| 12 | static int target_tid; |
| 13 | static void (*callback)(void *), *context; |
| 14 | static sem_t target_sem, caller_sem, exit_sem; |
| 15 | |
| 16 | static void dummy(void *p) |
| 17 | { |
| 18 | } |
| 19 | |
| 20 | static void handler(int sig) |
| 21 | { |
| 22 | 	if (__pthread_self()->tid != target_tid) return; |
| 23 | |
| 24 | 	int old_errno = errno; |
| 25 | |
| 26 | 	/* Inform caller we have received signal and wait for |
| 27 | 	 * the caller to let us make the callback. */ |
| 28 | 	sem_post(&caller_sem); |
| 29 | 	sem_wait(&target_sem); |
| 30 | |
| 31 | 	callback(context); |
| 32 | |
| 33 | 	/* Inform caller we've complered the callback and wait |
| 34 | 	 * for the caller to release us to return. */ |
| 35 | 	sem_post(&caller_sem); |
| 36 | 	sem_wait(&exit_sem); |
| 37 | |
| 38 | 	/* Inform caller we are returning and state is destroyable. */ |
| 39 | 	sem_post(&caller_sem); |
| 40 | |
| 41 | 	errno = old_errno; |
| 42 | } |
| 43 | |
| 44 | void __synccall(void (*func)(void *), void *ctx) |
| 45 | { |
| 46 | 	sigset_t oldmask; |
| 47 | 	int cs, i, r; |
| 48 | 	struct sigaction sa = { .sa_flags = SA_RESTART | SA_ONSTACK, .sa_handler = handler }; |
| 49 | 	pthread_t self = __pthread_self(), td; |
| 50 | 	int count = 0; |
| 51 | |
| 52 | 	/* Blocking signals in two steps, first only app-level signals |
| 53 | 	 * before taking the lock, then all signals after taking the lock, |
| 54 | 	 * is necessary to achieve AS-safety. Blocking them all first would |
| 55 | 	 * deadlock if multiple threads called __synccall. Waiting to block |
| 56 | 	 * any until after the lock would allow re-entry in the same thread |
| 57 | 	 * with the lock already held. */ |
| 58 | 	__block_app_sigs(&oldmask); |
| 59 | 	__tl_lock(); |
| 60 | 	__block_all_sigs(0); |
| 61 | 	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs); |
| 62 | |
| 63 | 	sem_init(&target_sem, 0, 0); |
| 64 | 	sem_init(&caller_sem, 0, 0); |
| 65 | 	sem_init(&exit_sem, 0, 0); |
| 66 | |
| 67 | 	if (!libc.threads_minus_1 || __syscall(SYS_gettid) != self->tid) |
| 68 | 		goto single_threaded; |
| 69 | |
| 70 | 	callback = func; |
| 71 | 	context = ctx; |
| 72 | |
| 73 | 	/* Block even implementation-internal signals, so that nothing |
| 74 | 	 * interrupts the SIGSYNCCALL handlers. The main possible source |
| 75 | 	 * of trouble is asynchronous cancellation. */ |
| 76 | 	memset(&sa.sa_mask, -1, sizeof sa.sa_mask); |
| 77 | 	__libc_sigaction(SIGSYNCCALL, &sa, 0); |
| 78 | |
| 79 | |
| 80 | 	for (td=self->next; td!=self; td=td->next) { |
| 81 | 		target_tid = td->tid; |
| 82 | 		while ((r = -__syscall(SYS_tkill, td->tid, SIGSYNCCALL)) == EAGAIN); |
| 83 | 		if (r) { |
| 84 | 			/* If we failed to signal any thread, nop out the |
| 85 | 			 * callback to abort the synccall and just release |
| 86 | 			 * any threads already caught. */ |
| 87 | 			callback = func = dummy; |
| 88 | 			break; |
| 89 | 		} |
| 90 | 		sem_wait(&caller_sem); |
| 91 | 		count++; |
| 92 | 	} |
| 93 | 	target_tid = 0; |
| 94 | |
| 95 | 	/* Serialize execution of callback in caught threads, or just |
| 96 | 	 * release them all if synccall is being aborted. */ |
| 97 | 	for (i=0; i<count; i++) { |
| 98 | 		sem_post(&target_sem); |
| 99 | 		sem_wait(&caller_sem); |
| 100 | 	} |
| 101 | |
| 102 | 	sa.sa_handler = SIG_IGN; |
| 103 | 	__libc_sigaction(SIGSYNCCALL, &sa, 0); |
| 104 | |
| 105 | single_threaded: |
| 106 | 	func(ctx); |
| 107 | |
| 108 | 	/* Only release the caught threads once all threads, including the |
| 109 | 	 * caller, have returned from the callback function. */ |
| 110 | 	for (i=0; i<count; i++) |
| 111 | 		sem_post(&exit_sem); |
| 112 | 	for (i=0; i<count; i++) |
| 113 | 		sem_wait(&caller_sem); |
| 114 | |
| 115 | 	sem_destroy(&caller_sem); |
| 116 | 	sem_destroy(&target_sem); |
| 117 | 	sem_destroy(&exit_sem); |
| 118 | |
| 119 | 	pthread_setcancelstate(cs, 0); |
| 120 | 	__tl_unlock(); |
| 121 | 	__restore_sigs(&oldmask); |
| 122 | } |