1/*	$OpenBSD: signal-stress.c,v 1.2 2021/09/20 16:39:40 claudio Exp $	*/
2/*
3 *	Written by Artur Grabowski <art@openbsd.org> 2004 Public Domain.
4 */
5#include <sys/types.h>
6#include <sys/mman.h>
7#include <sys/wait.h>
8
9#include <signal.h>
10#include <unistd.h>
11#include <stdlib.h>
12#include <err.h>
13
14int nprocs, nsigs;
15pid_t *pids;
16pid_t next, prev;
17sig_atomic_t usr1, usr2;
18
19void
20sighand(int sig)
21{
22	if (sig == SIGUSR1 && ++usr1 <= nsigs) {
23		if (kill(next, sig))
24			_exit(1);
25	}
26	if (sig == SIGUSR2 && ++usr2 <= nsigs) {
27		if (kill(prev, sig))
28			_exit(1);
29	}
30}
31
32void
33do_child(void)
34{
35	int i;
36	sigset_t mask, oldmask;
37
38	/*
39	 * Step 1 - suspend and wait for SIGCONT so that all siblings have
40	 * been started before the next step.
41	 */
42	raise(SIGSTOP);
43
44	/* Find our neighbours. */
45	for (i = 0; i < nprocs; i++) {
46		if (pids[i] != getpid())
47			continue;
48		if (i + 1 == nprocs)
49			next = pids[0];
50		else
51			next = pids[i + 1];
52		if (i == 0)
53			prev = pids[nprocs - 1];
54		else
55			prev = pids[i - 1];
56	}
57
58	signal(SIGUSR1, sighand);
59	signal(SIGUSR2, sighand);
60
61	sigemptyset(&mask);
62	sigaddset(&mask, SIGUSR1);
63	sigaddset(&mask, SIGUSR2);
64
65	sigprocmask(SIG_BLOCK, &mask, &oldmask);
66
67	/* Step 2 - wait again until everyone is ready. */
68	raise(SIGSTOP);
69
70	while (usr1 < nsigs || usr2 < nsigs)
71		sigsuspend(&oldmask);
72
73	/* Step 3 - wait again until everyone is ready. */
74	raise(SIGSTOP);
75}
76
77void
78wait_stopped(pid_t pid)
79{
80	int status;
81
82	if (waitpid(pid, &status, WUNTRACED) != pid)
83		err(1, "waitpid");
84	if (!WIFSTOPPED(status))
85		errx(1, "child %d not stopped", pid);
86}
87
88void
89cleanup(void)
90{
91	int i;
92
93	for (i = 0; i < nprocs; i++)
94		kill(pids[i], 9);
95}
96
97void
98alrmhand(int sig)
99{
100	cleanup();
101	_exit(1);
102}
103
104int
105main()
106{
107	int i;
108	pid_t pid;
109
110	nprocs = 35;
111
112	nsigs = 1000;
113
114	if ((pids = mmap(NULL, getpagesize(), PROT_READ|PROT_WRITE,
115	    MAP_ANON|MAP_SHARED, -1, 0)) == MAP_FAILED)
116		err(1, "mmap");
117
118	for (i = 0; i < nprocs; i++) {
119		switch((pid = fork())) {
120		case 0:
121			do_child();
122			_exit(0);
123		case -1:
124			err(1, "fork");
125		}
126		pids[i] = pid;
127	}
128
129	atexit(cleanup);
130	signal(SIGALRM, alrmhand);
131	alarm(120);			/* Die after two minutes. */
132
133	/* Step 1. Wait until all children have went to sleep */
134	for (i = 0; i < nprocs; i++)
135		wait_stopped(pids[i]);
136	/* And wake them */
137	for (i = 0; i < nprocs; i++)
138		kill(pids[i], SIGCONT);
139
140	/* Step 2. Repeat. */
141	for (i = 0; i < nprocs; i++)
142		wait_stopped(pids[i]);
143	for (i = 0; i < nprocs; i++)
144		kill(pids[i], SIGCONT);
145
146	/*
147	 * Now all children are ready for action.
148	 * Send the first signals and wait until they all exit.
149	 */
150	kill(pids[arc4random_uniform(nprocs)], SIGUSR1);
151	kill(pids[arc4random_uniform(nprocs)], SIGUSR2);
152
153	/*
154	 * The signal game is running, now insert noise in the process.
155	 */
156	for (i = 0; i < nprocs; i++) {
157		pid_t pid = pids[arc4random_uniform(nprocs)];
158		kill(pid, SIGSTOP);
159		wait_stopped(pid);
160		kill(pid, SIGCONT);
161	}
162
163
164	/* Step 3. Repeat. */
165	for (i = 0; i < nprocs; i++)
166		wait_stopped(pids[i]);
167	for (i = 0; i < nprocs; i++)
168		kill(pids[i], SIGCONT);
169
170	/* Wait for everyone to finish. */
171	for (i = 0; i < nprocs; i++) {
172		int status;
173
174		if (waitpid(pids[i], &status, WUNTRACED) != pids[i])
175			err(1, "waitpid");
176		if (!WIFEXITED(status))
177			errx(1, "child %d not stopped (%d)", pids[i], status);
178		if (WEXITSTATUS(status) != 0)
179			warnx("child %d status: %d", i, status);
180	}
181
182	return (0);
183}
184