/*
 * sigpg_forkstorm.c: race a killing signal against fork() storm within
 *	a new process group.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 * Copyright (C) IBM Corporation, 2005
 *
 * Author: Paul E. McKenney <paulmck@us.ibm.com>
 */

#include "steamroller.h"

struct sig_test_ctrl {
	int startflag;
	int stopstorm;
	int terminatestorm;
	int raced;
	int childforkdone;
	int grandchildrunning;
	int stillrunning;
	int error;
};

int test_sigpg_dfl__forkstorm(void *p, int parentspin, long childcpuset)
{
	int childpid;
	int i;
	int parentpid;
	int pid;
	int retval;
	int status;
	struct sig_test_ctrl *stp = (struct sig_test_ctrl *)p;

	if (steamroller_args.verbosity >= 100) {
		printf("test_sigpg_dfl__forkstorm parentspin: %d\n",
		       parentspin);
	}
	stp->startflag = 0;
	stp->stopstorm = 0;
	stp->terminatestorm = 0;
	stp->raced = 0;
	stp->childforkdone = 0;
	stp->grandchildrunning = 0;
	stp->error = 0;
	parentpid = getpid();
	if ((pid = fork()) == 0) {
		childpid = getpid();
		if (setpgid(0, childpid) == -1) {
			perror("setpgid");
			stp->error = 1;
			exit(-1);
		}
		if (child_affinity_rest(childcpuset)) {
			perror("child_affinity_rest");
			stp->error = 1;
			exit(-1);
		}
		stp->startflag = 1;
		spindelay(us2spindelay(100));
		stp->raced = 1;
		while (!stp->stopstorm && (stp->childforkdone < 10)) {
			if ((pid = fork()) == 0) {
				stp->grandchildrunning++;
				if (steamroller_args.verbosity >= 100) {
					printf("Child forked: %d\n", getpid());
				}
			} else if (pid > 0) {
				stp->childforkdone++;
			}
		}
		while (!stp->terminatestorm) {
			sched_yield();
			stp->stillrunning++;
		}
		exit(0);
	}
	if (pid == -1) {
		perror("fork");
		if (getpid() != parentpid) {
			fprintf(stderr,
				"Error fork() child: parent: %d mine:%d\n",
				parentpid, getpid());
			stp->terminatestorm = 1;
			exit(-1);
		}
		stp->terminatestorm = 1;
		exit(-1);
	}
	while (stp->startflag == 0) continue;
	spindelay(parentspin);
	if (kill(-pid, SIGINT) != 0) {
		stp->stopstorm = 1;
		perror("kill");
		stp->terminatestorm = 1;
		exit(-1);
	}
	stp->stopstorm = 1;
	if (stp->error) {
		fprintf(stderr, "error in child!\n");
		stp->terminatestorm = 1;
		exit(-1);
	}
	i = stp->stillrunning;
	spindelayus(50);
	if (i != stp->stillrunning) {
		fprintf(stderr, "error: processes survived killpg!\n");
		stp->terminatestorm = 1;
		exit(-1);
	}
	stp->terminatestorm = 1;
	while ((retval = waitpid(-1, &status, WNOHANG)) > 0) {
		if (steamroller_args.verbosity >= 10) {
			printf("Child %d done: %s\n", retval,
			       WIFEXITED(status) ?
			       		"exited" :
					(WIFSIGNALED(status) ?
					 	"signaled" :
						"cause of death unknown"));
		}
	}
	if (steamroller_args.verbosity >= 10) {
		printf("final waitpid returned %d\n", retval);
	}
	if ((retval != 0) && (errno != ECHILD)) {
		perror("waitpid");
		exit(-1);
	}
	if (steamroller_args.verbosity >= 10) {
		printf("# forks: %d  # kids: %d\n",
		       stp->grandchildrunning, stp->childforkdone);
	}
	if (!stp->raced) {
		return STEAMROLLER_EARLY;
	}
	if ((stp->childforkdone <= 2) && (stp->grandchildrunning <= 2)) {
		return STEAMROLLER_RACED;
	} else {
		return STEAMROLLER_LATE;
	}
}

int main(int argc, char *argv[])
{
	long childcpuset;
	void *p;

	childcpuset = steamroller_init(argc, argv);
	p = (int *)mapmem(sizeof(struct sig_test_ctrl), -1);
	search_and_steamroller(test_sigpg_dfl__forkstorm, p, childcpuset);
}
