1/*	$OpenBSD: sigpthread.c,v 1.2 2021/07/06 11:50:34 bluhm Exp $	*/
2/*
3 * Copyright (c) 2019 Alexander Bluhm <bluhm@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#include <err.h>
19#include <errno.h>
20#include <limits.h>
21#include <pthread.h>
22#include <signal.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26#include <unistd.h>
27
28void __dead usage(void);
29void handler(int);
30void *runner(void *);
31
32void __dead
33usage(void)
34{
35	fprintf(stderr, "sigpthread [-bSsU] [-k kill] -t threads [-u unblock] "
36	    "[-w waiter]\n"
37	    "    -b             block signal to make it pending\n"
38	    "    -k kill        thread to kill, else process\n"
39	    "    -S             sleep in each thread before suspend\n"
40	    "    -s             sleep in main before kill\n"
41	    "    -t threads     number of threads to run\n"
42	    "    -U             sleep in thread before unblock\n"
43	    "    -u unblock     thread to unblock, else unblock all\n"
44	    "    -w waiter      use sigwait in thread\n"
45	);
46	exit(1);
47}
48
49int blocksignal = 0;
50int threadmax, threadunblock = -1, threadwaiter = -1;
51int sleepthread, sleepmain, sleepunblock;
52sigset_t set, oset;
53pthread_t *threads;
54volatile sig_atomic_t *signaled;
55
56int
57main(int argc, char *argv[])
58{
59	struct sigaction act;
60	int ch, ret, tnum, threadkill = -1;
61	long arg;
62	void *val;
63	const char *errstr;
64
65	while ((ch = getopt(argc, argv, "bk:Sst:Uu:w:")) != -1) {
66		switch (ch) {
67		case 'b':
68			blocksignal = 1;
69			break;
70		case 'k':
71			threadkill = strtonum(optarg, 0, INT_MAX, &errstr);
72			if (errstr != NULL)
73				errx(1, "thread to kill is %s: %s",
74				    errstr, optarg);
75			break;
76		case 'S':
77			sleepthread = 1;
78			break;
79		case 's':
80			sleepmain = 1;
81			break;
82		case 't':
83			threadmax = strtonum(optarg, 1, INT_MAX, &errstr);
84			if (errstr != NULL)
85				errx(1, "number of threads is %s: %s",
86				    errstr, optarg);
87			break;
88		case 'U':
89			sleepunblock = 1;
90			break;
91		case 'u':
92			threadunblock = strtonum(optarg, 0, INT_MAX, &errstr);
93			if (errstr != NULL)
94				errx(1, "thread to unblock is %s: %s",
95				    errstr, optarg);
96			break;
97		case 'w':
98			threadwaiter = strtonum(optarg, 0, INT_MAX, &errstr);
99			if (errstr != NULL)
100				errx(1, "thread to wait is %s: %s",
101				    errstr, optarg);
102			break;
103		default:
104			usage();
105		}
106	}
107	argc -= optind;
108	argv += optind;
109	if (argc != 0)
110		errx(1, "more arguments than expected");
111	if (threadmax == 0)
112		errx(1, "number of threads required");
113	if (threadkill >= threadmax)
114		errx(1, "thread to kill greater than number of threads");
115	if (threadunblock >= threadmax)
116		errx(1, "thread to unblock greater than number of threads");
117	if (threadwaiter >= threadmax)
118		errx(1, "thread to wait greater than number of threads");
119	if (!blocksignal && threadunblock >= 0)
120		errx(1, "do not unblock thread without blocked signals");
121	if (!blocksignal && threadwaiter >= 0)
122		errx(1, "do not wait in thread without blocked signals");
123	if (threadunblock >= 0 && threadwaiter >= 0)
124		errx(1, "do not unblock and wait together");
125	if (sleepunblock && threadwaiter >= 0)
126		errx(1, "do not sleep before unblock and wait together");
127
128	/* Make sure that we do not hang forever. */
129	alarm(10);
130
131	if (sigemptyset(&set) == -1)
132		err(1, "sigemptyset");
133	if (sigaddset(&set, SIGUSR1) == -1)
134		err(1, "sigaddset");
135	/* Either deliver SIGUSR2 immediately, or mark it pending. */
136	if (blocksignal) {
137		if (sigaddset(&set, SIGUSR2) == -1)
138			err(1, "sigaddset");
139	}
140	/* Block both SIGUSR1 and SIGUSR2 with set. */
141	if (sigprocmask(SIG_BLOCK, &set, &oset) == -1)
142		err(1, "sigprocmask");
143
144	/* Prepare to wait for SIGUSR1, but block SIGUSR2 with oset. */
145	if (sigaddset(&oset, SIGUSR2) == -1)
146		err(1, "sigaddset");
147	/* Unblock or wait for SIGUSR2 */
148	if (sigemptyset(&set) == -1)
149		err(1, "sigemptyset");
150	if (sigaddset(&set, SIGUSR2) == -1)
151		err(1, "sigaddset");
152
153	memset(&act, 0, sizeof(act));
154	act.sa_handler = handler;
155	if (sigaction(SIGUSR1, &act, NULL) == -1)
156		err(1, "sigaction SIGUSR1");
157	if (sigaction(SIGUSR2, &act, NULL) == -1)
158		err(1, "sigaction SIGUSR2");
159
160	signaled = calloc(threadmax, sizeof(*signaled));
161	if (signaled == NULL)
162		err(1, "calloc signaled");
163	threads = calloc(threadmax, sizeof(*threads));
164	if (threads == NULL)
165		err(1, "calloc threads");
166
167	for (tnum = 1; tnum < threadmax; tnum++) {
168		arg = tnum;
169		errno = pthread_create(&threads[tnum], NULL, runner,
170		    (void *)arg);
171		if (errno)
172			err(1, "pthread_create %d", tnum);
173	}
174	/* Handle the main thread like thread 0. */
175	threads[0] = pthread_self();
176
177	/* Test what happens if thread is running when killed. */
178	if (sleepmain)
179		sleep(1);
180
181	/* All threads are still alive. */
182	if (threadkill < 0) {
183		if (kill(getpid(), SIGUSR2) == -1)
184			err(1, "kill SIGUSR2");
185	} else {
186		errno = pthread_kill(threads[threadkill], SIGUSR2);
187		if (errno)
188			err(1, "pthread_kill %d SIGUSR2", tnum);
189	}
190
191	/* Sending SIGUSR1 means threads can continue and finish. */
192	for (tnum = 0; tnum < threadmax; tnum++) {
193		errno = pthread_kill(threads[tnum], SIGUSR1);
194		if (errno)
195			err(1, "pthread_kill %d SIGUSR1", tnum);
196	}
197
198	val = runner(0);
199	ret = (int)val;
200
201	for (tnum = 1; tnum < threadmax; tnum++) {
202		errno = pthread_join(threads[tnum], &val);
203		if (errno)
204			err(1, "pthread_join %d", tnum);
205		ret = (int)val;
206		if (ret)
207			errx(1, "pthread %d returned %d", tnum, ret);
208	}
209	free(threads);
210
211	for (tnum = 0; tnum < threadmax; tnum++) {
212		int i;
213
214		for (i = 0; i < signaled[tnum]; i++)
215			printf("signal %d\n", tnum);
216	}
217	free((void *)signaled);
218
219	return 0;
220}
221
222void
223handler(int sig)
224{
225	pthread_t tid;
226	int tnum;
227
228	tid = pthread_self();
229	for (tnum = 0; tnum < threadmax; tnum++) {
230		if (tid == threads[tnum])
231			break;
232	}
233	switch (sig) {
234	case SIGUSR1:
235		break;
236	case SIGUSR2:
237		signaled[tnum]++;
238		break;
239	default:
240		errx(1, "unexpected signal %d thread %d", sig, tnum);
241	}
242}
243
244void *
245runner(void *arg)
246{
247	int tnum = (int)arg;
248
249	/* Test what happens if thread is running when killed. */
250	if (sleepthread)
251		sleep(1);
252
253	if (tnum == threadwaiter) {
254		int sig;
255
256		if (sigwait(&set, &sig) != 0)
257			err(1, "sigwait thread %d", tnum);
258		if (sig != SIGUSR2)
259			errx(1, "unexpected signal %d thread %d", sig, tnum);
260		signaled[tnum]++;
261	}
262
263	/*
264	 * Wait for SIGUSER1, continue to block SIGUSER2.
265	 * The thread is keeps running until it gets SIGUSER1.
266	 */
267	if (sigsuspend(&oset) != -1 || errno != EINTR)
268		err(1, "sigsuspend thread %d", tnum);
269	if ((threadunblock < 0 || tnum == threadunblock) && threadwaiter < 0) {
270		/* Test what happens if other threads exit before unblock. */
271		if (sleepunblock)
272			sleep(1);
273
274		/* Also unblock SIGUSER2, if this thread should get it. */
275		if (pthread_sigmask(SIG_UNBLOCK, &set, NULL) == -1)
276			err(1, "pthread_sigmask thread %d", tnum);
277	}
278
279	return (void *)0;
280}
281