520 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			520 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* 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();
 | 
						|
}
 |