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