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
28 void __dead usage(void);
29 void handler(int);
30 void *runner(void *);
31
32 void __dead
usage(void)33 usage(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
49 int blocksignal = 0;
50 int threadmax, threadunblock = -1, threadwaiter = -1;
51 int sleepthread, sleepmain, sleepunblock;
52 sigset_t set, oset;
53 pthread_t *threads;
54 volatile sig_atomic_t *signaled;
55
56 int
main(int argc,char * argv[])57 main(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
222 void
handler(int sig)223 handler(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
244 void *
runner(void * arg)245 runner(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