summaryrefslogtreecommitdiff
path: root/minix/tests/test79.c
blob: b5948c67355356442f365d0d613aeb9224c77262 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
/* Tests for PM signal handling robustness - by D.C. van Moolenbroek */
/*
 * The signal handling code must not rely on priorities assigned to services,
 * and so, this test (like any test!) must also pass if PM and/or VFS are not
 * given a fixed high priority.  A good way to verify this is to let PM and VFS
 * be scheduled by SCHED rather than KERNEL, and to give them the same priority
 * as (or slightly lower than) normal user processes.  Note that if VFS is
 * configured to use a priority *far lower* than user processes, starvation may
 * cause this test not to complete in some scenarios.  In that case, Ctrl+C
 * should still be able to kill the test.
 */
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/utsname.h>

#define ITERATIONS 1

#include "common.h"

#define NR_SIGNALS	20000

#define MAX_SIGNALERS	3

static const int signaler_sig[MAX_SIGNALERS] = { SIGUSR1, SIGUSR2, SIGHUP };
static pid_t signaler_pid[MAX_SIGNALERS];
static int sig_counter;

enum {
	JOB_RUN = 0,
	JOB_CALL_PM,
	JOB_CALL_VFS,
	JOB_SET_MASK,
	JOB_BLOCK_PM,
	JOB_BLOCK_VFS,
	JOB_CALL_PM_VFS,
	JOB_FORK,
	NR_JOBS
};

#define OPT_NEST	0x1
#define OPT_ALARM	0x2
#define OPT_ALL		0x3

struct link {
	pid_t pid;
	int sndfd;
	int rcvfd;
};

/*
 * Spawn a child process, with a pair of pipes to talk to it bidirectionally.
 */
static void
spawn(struct link *link, void (*proc)(struct link *))
{
	int up[2], dn[2];

	fflush(stdout);
	fflush(stderr);

	if (pipe(up) != 0) e(0);
	if (pipe(dn) != 0) e(0);

	link->pid = fork();

	switch (link->pid) {
	case 0:
		close(up[1]);
		close(dn[0]);

		link->rcvfd = up[0];
		link->sndfd = dn[1];

		errct = 0;

		proc(link);

		/* Close our pipe FDs on exit, so that we can make zombies. */
		exit(errct);
	case -1:
		e(0);
		break;
	}

	close(up[0]);
	close(dn[1]);

	link->sndfd = up[1];
	link->rcvfd = dn[0];
}

/*
 * Wait for a child process to terminate, and clean up.
 */
static void
collect(struct link *link)
{
	int status;

	close(link->sndfd);
	close(link->rcvfd);

	if (waitpid(link->pid, &status, 0) <= 0) e(0);

	if (!WIFEXITED(status)) e(0);
	else errct += WEXITSTATUS(status);
}

/*
 * Forcibly terminate a child process, and clean up.
 */
static void
terminate(struct link *link)
{
	int status;

	if (kill(link->pid, SIGKILL) != 0) e(0);

	close(link->sndfd);
	close(link->rcvfd);

	if (waitpid(link->pid, &status, 0) <= 0) e(0);

	if (WIFSIGNALED(status)) {
		if (WTERMSIG(status) != SIGKILL) e(0);
	} else {
		if (!WIFEXITED(status)) e(0);
		else errct += WEXITSTATUS(status);
	}
}

/*
 * Send an integer value to the child or parent.
 */
static void
snd(struct link *link, int val)
{
	if (write(link->sndfd, (void *) &val, sizeof(val)) != sizeof(val))
		e(0);
}

/*
 * Receive an integer value from the child or parent, or -1 on EOF.
 */
static int
rcv(struct link *link)
{
	int r, val;

	if ((r = read(link->rcvfd, (void *) &val, sizeof(val))) == 0)
		return -1;

	if (r != sizeof(val)) e(0);

	return val;
}

/*
 * Set a signal handler for a particular signal, blocking either all or no
 * signals when the signal handler is invoked.
 */
static void
set_handler(int sig, void (*proc)(int), int block)
{
	struct sigaction act;

	memset(&act, 0, sizeof(act));
	if (block) sigfillset(&act.sa_mask);
	act.sa_handler = proc;

	if (sigaction(sig, &act, NULL) != 0) e(0);
}

/*
 * Generic signal handler for the worker process.
 */
static void
worker_handler(int sig)
{
	int i;

	switch (sig) {
	case SIGUSR1:
	case SIGUSR2:
	case SIGHUP:
		for (i = 0; i < MAX_SIGNALERS; i++) {
			if (signaler_sig[i] != sig) continue;

			if (signaler_pid[i] == -1) e(0);
			else if (kill(signaler_pid[i], SIGUSR1) != 0) e(0);
			break;
		}
		if (i == MAX_SIGNALERS) e(0);
		break;
	case SIGTERM:
		exit(errct);
		break;
	case SIGALRM:
		/* Do nothing. */
		break;
	default:
		e(0);
	}
}

/*
 * Procedure for the worker process.  Sets up its own environment using
 * information sent to it by the parent, sends an acknowledgement to the
 * parent, and loops executing the job given to it until a SIGTERM comes in.
 */
static void __dead
worker_proc(struct link *parent)
{
	struct utsname name;
	struct itimerval it;
	struct timeval tv;
	sigset_t set, oset;
	uid_t uid;
	int i, job, options;

	job = rcv(parent);
	options = rcv(parent);

	for (i = 0; i < MAX_SIGNALERS; i++) {
		set_handler(signaler_sig[i], worker_handler,
		    !(options & OPT_NEST));

		signaler_pid[i] = rcv(parent);
	}

	set_handler(SIGTERM, worker_handler, 1 /* block */);
	set_handler(SIGALRM, worker_handler, !(options & OPT_NEST));

	snd(parent, 0);

	if (options & OPT_ALARM) {
		/* The timer would kill wimpy platforms such as ARM. */
		if (uname(&name) < 0) e(0);
		if (strcmp(name.machine, "arm")) {
			it.it_value.tv_sec = 0;
			it.it_value.tv_usec = 1;
			it.it_interval.tv_sec = 0;
			it.it_interval.tv_usec = 1;
			if (setitimer(ITIMER_REAL, &it, NULL) != 0) e(0);
		}
	}

	switch (job) {
	case JOB_RUN:
		for (;;);
		break;
	case JOB_CALL_PM:
		/*
		 * Part of the complication of the current system in PM comes
		 * from the fact that when a process is being stopped, it might
		 * already have started sending a message.  That message will
		 * arrive at its destination regardless of the process's run
		 * state.  PM must avoid setting up a signal handler (and
		 * changing the process's signal mask as part of that) if such
		 * a message is still in transit, because that message might,
		 * for example, query (or even change) the signal mask.
		 */
		for (;;) {
			if (sigprocmask(SIG_BLOCK, NULL, &set) != 0) e(0);
			if (sigismember(&set, SIGUSR1)) e(0);
		}
		break;
	case JOB_CALL_VFS:
		for (;;) {
			tv.tv_sec = 0;
			tv.tv_usec = 0;
			select(0, NULL, NULL, NULL, &tv);
		}
		break;
	case JOB_SET_MASK:
		for (;;) {
			sigfillset(&set);
			if (sigprocmask(SIG_SETMASK, &set, &oset) != 0) e(0);
			if (sigprocmask(SIG_SETMASK, &oset, NULL) != 0) e(0);
		}
		break;
	case JOB_BLOCK_PM:
		for (;;) {
			sigemptyset(&set);
			sigsuspend(&set);
		}
		break;
	case JOB_BLOCK_VFS:
		for (;;)
			select(0, NULL, NULL, NULL, NULL);
		break;
	case JOB_CALL_PM_VFS:
		uid = getuid();
		for (;;)
			setuid(uid);
		break;
	case JOB_FORK:
		/*
		 * The child exits immediately; the parent kills the child
		 * immediately.  The outcome mostly depends on scheduling.
		 * Varying process priorities may yield different tests.
		 */
		for (;;) {
			pid_t pid = fork();
			switch (pid) {
			case 0:
				exit(0);
			case -1:
				e(1);
				break;
			default:
				kill(pid, SIGKILL);
				if (wait(NULL) != pid) e(0);
			}
		}
		break;
	default:
		e(0);
		exit(1);
	}
}

/*
 * Signal handler procedure for the signaler processes, counting the number of
 * signals received from the worker process.
 */
static void
signaler_handler(int sig)
{
	sig_counter++;
}

/*
 * Procedure for the signaler processes.  Gets the pid of the worker process
 * and the signal to use, and then repeatedly sends that signal to the worker
 * process, waiting for a SIGUSR1 signal back from the worker before
 * continuing.  This signal ping-pong is repeated for a set number of times.
 */
static void
signaler_proc(struct link *parent)
{
	sigset_t set, oset;
	pid_t pid;
	int i, sig, nr;

	pid = rcv(parent);
	sig = rcv(parent);
	nr = rcv(parent);
	sig_counter = 0;

	sigfillset(&set);
	if (sigprocmask(SIG_SETMASK, &set, &oset) != 0) e(0);

	set_handler(SIGUSR1, signaler_handler, 1 /*block*/);

	for (i = 0; nr == 0 || i < nr; i++) {
		if (sig_counter != i) e(0);

		if (kill(pid, sig) != 0 && nr > 0) e(0);

		sigsuspend(&oset);
	}

	if (sig_counter != nr) e(0);
}

/*
 * Set up the worker and signaler processes, wait for the signaler processes to
 * do their work and terminate, and then terminate the worker process.
 */
static void
sub79a(int job, int signalers, int options)
{
	struct link worker, signaler[MAX_SIGNALERS];
	int i;

	spawn(&worker, worker_proc);

	snd(&worker, job);
	snd(&worker, options);

	for (i = 0; i < signalers; i++) {
		spawn(&signaler[i], signaler_proc);

		snd(&worker, signaler[i].pid);
	}
	for (; i < MAX_SIGNALERS; i++)
		snd(&worker, -1);

	if (rcv(&worker) != 0) e(0);

	for (i = 0; i < signalers; i++) {
		snd(&signaler[i], worker.pid);
		snd(&signaler[i], signaler_sig[i]);
		snd(&signaler[i], NR_SIGNALS);
	}

	for (i = 0; i < signalers; i++)
		collect(&signaler[i]);

	if (kill(worker.pid, SIGTERM) != 0) e(0);

	collect(&worker);
}

/*
 * Stress test for signal handling.  One worker process gets signals from up to
 * three signaler processes while performing one of a number of jobs.  It
 * replies to each signal by signaling the source, thus creating a ping-pong
 * effect for each of the signaler processes.  The signal ping-ponging is
 * supposed to be reliable, and the most important aspect of the test is that
 * no signals get lost.  The test is performed a number of times, varying the
 * job executed by the worker process, the number of signalers, whether signals
 * are blocked while executing a signal handler in the worker, and whether the
 * worker process has a timer running at high frequency.
 */
static void
test79a(void)
{
	int job, signalers, options;

	subtest = 1;

	for (options = 0; options <= OPT_ALL; options++)
		for (signalers = 1; signalers <= MAX_SIGNALERS; signalers++)
			for (job = 0; job < NR_JOBS; job++)
				sub79a(job, signalers, options);
}

/*
 * Set up the worker process and optionally a signaler process, wait for a
 * predetermined amount of time, and then kill all the child processes.
 */
static void
sub79b(int job, int use_signaler, int options)
{
	struct link worker, signaler;
	struct timeval tv;
	int i;

	spawn(&worker, worker_proc);

	snd(&worker, job);
	snd(&worker, options);

	if ((i = use_signaler) != 0) {
		spawn(&signaler, signaler_proc);

		snd(&worker, signaler.pid);
	}
	for (; i < MAX_SIGNALERS; i++)
		snd(&worker, -1);

	if (rcv(&worker) != 0) e(0);

	if (use_signaler) {
		snd(&signaler, worker.pid);
		snd(&signaler, signaler_sig[0]);
		snd(&signaler, 0);
	}

	/* Use select() so that we can verify we don't get signals. */
	tv.tv_sec = 0;
	tv.tv_usec = 100000;
	if (select(0, NULL, NULL, NULL, &tv) != 0) e(0);

	terminate(&worker);

	if (use_signaler)
		terminate(&signaler);
}

/*
 * This test is similar to the previous one, except that we now kill the worker
 * process after a while.  This should trigger various process transitions to
 * the exiting state.  Not much can be verified from this test program, but we
 * intend to trigger as many internal state verification statements of PM
 * itself as possible this way.  A signaler process is optional in this test,
 * and if used, it will not stop after a predetermined number of signals.
 */
static void
test79b(void)
{
	int job, signalers, options;

	subtest = 2;

	for (options = 0; options <= OPT_ALL; options++)
		for (signalers = 0; signalers <= 1; signalers++)
			for (job = 0; job < NR_JOBS; job++)
				sub79b(job, signalers, options);

}

/*
 * PM signal handling robustness test program.
 */
int
main(int argc, char **argv)
{
	int i, m;

	start(79);

	if (argc == 2)
		m = atoi(argv[1]);
	else
		m = 0xFF;

	for (i = 0; i < ITERATIONS; i++) {
		if (m & 0x01) test79a();
		if (m & 0x02) test79b();
	}

	quit();
}