/*
 *
 *   Copyright (c) International Business Machines  Corp., 2001
 *
 *   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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

/*
 * NAME
 *	waitpid10.c
 *
 * DESCRIPTION
 *	Tests to see if pid's returned from fork and waitpid are same
 *
 * ALGORITHM
 *	Set up to catch SIGINTs, SIGALRMs, and the real time timer.
 *	Until the timer interrupts, do the following.  Fork 8 kids.
 *	2 will immediately exit, 2 will sleep, 2 will be compute bound,
 *	and 2 will fork another child, both which will do mkdirs on
 *	the same directory 50 times.  When the timer expires, kill all
 *	kids and remove the directory.
 *
 * USAGE:  <for command-line>
 *      waitpid10 [-c n] [-i n] [-I x] [-P x] [-t]
 *      where,  -c n : Run n copies concurrently.
 *              -i n : Execute test n times.
 *              -I x : Execute test for x seconds.
 *              -P x : Pause for x seconds between iterations.
 *              -t   : Turn on syscall timing.
 *
 * NOTE
 *	This test was designed to see if the intermittant occurrance
 *	of a waitpid returning a pid different from that returned by the
 *	fork can be reproduced.
 *
 * History
 *	07/2001 John George
 *		-Ported
 *      04/2002 wjhuie sigset cleanups
 *
 * Restrictions
 *	None
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>

#include <stdio.h>
#include <signal.h>
#include <errno.h>
#include "test.h"
#include "usctest.h"

#define	MAXKIDS	8

char *TCID = "waitpid10";
int TST_TOTAL = 1;

static int alrmintr;
volatile int intintr;

static void setup(void);
static void cleanup(void);
static void inthandlr();
static void alrmhandlr();
static void wait_for_parent(void);
static void do_exit(void);
static void do_compute(void);
static void do_fork(void);
static void do_sleep(void);
static void do_mkdir(void);

static int fail;

#ifdef UCLINUX
static char *argv0;
#endif

int main(int ac, char **av)
{
	int kid_count, ret_val, status, nkids;
	int i, j, k, found;
	int fork_kid_pid[MAXKIDS], wait_kid_pid[MAXKIDS];
	int runtime;		/* time(sec) to run this process */

	int lc;
	const char *msg;

	msg = parse_opts(ac, av, NULL, NULL);
	if (msg != NULL)
		tst_brkm(TBROK, NULL, "OPTION PARSING ERROR - %s", msg);

#ifdef UCLINUX
	argv0 = av[0];

	maybe_run_child(&do_exit, "n", 1);
	maybe_run_child(&do_compute, "n", 2);
	maybe_run_child(&do_fork, "n", 3);
	maybe_run_child(&do_sleep, "n", 4);
	maybe_run_child(&do_mkdir, "n", 5);
#endif

	/*
	 * process the arg -- If there is one arg, it is the
	 * number of seconds to run.  If there is no arg the program
	 * defaults to 60 sec runtime.
	 */
	if (ac == 2) {
		if (sscanf(av[1], "%d", &runtime) != 1)
			tst_resm(TFAIL, "%s is an invalid argument", av[1]);
	} else {
		runtime = 60;
	}

	setup();

	for (lc = 0; TEST_LOOPING(lc); lc++) {
		/* reset tst_count in case we are looping */
		tst_count = 0;
		fail = 0;

		if (signal(SIGALRM, alrmhandlr) == SIG_ERR) {
			tst_resm(TFAIL, "signal SIGALRM failed.  errno = %d",
				 errno);

		}
		alrmintr = 0;

		/*
		 * Set up to catch SIGINT. The kids will wait till a SIGINT
		 * has been received before they proceed.
		 */
		if (signal(SIGINT, inthandlr) == SIG_ERR) {
			tst_resm(TFAIL, "signal SIGINT failed.  errno = %d",
				 errno);

		}
		intintr = 0;

		/* Turn on the real time interval timer. */
		if ((alarm(runtime)) < 0)
			tst_resm(TFAIL, "alarm failed.  errno = %d", errno);

		/* Run the test over and over until the timer expires */
		for (;;) {
			if (alrmintr)
				break;

			/*
			 * Fork 8 kids. There will be 4 sets of 2 processes
			 * doing the same thing. Save all kid pid's in an
			 * array for future use. The kids will first wait for
			 * the parent to send SIGINT. Then will proceed to
			 * their assigned tasks.
			 */
			kid_count = 0;
			/*
			 * Clearing the intinitr flag here for all the children.
			 * So that we may not miss any signals !
			 */
			intintr = 0;
			ret_val = FORK_OR_VFORK();
			if (ret_val == 0) {	/* child 0 */
#ifdef UCLINUX
				if (self_exec(argv0, "n", 1) < 0)
					tst_resm(TFAIL, "self_exec 0 failed");
#else
				do_exit();
#endif
			}
			if (ret_val < 0) {
				tst_resm(TFAIL, "Fork kid 0 failed. errno = "
					 "%d", errno);

			}

			/* parent */
			fork_kid_pid[kid_count++] = ret_val;

			ret_val = FORK_OR_VFORK();
			if (ret_val == 0) {	/* child 1 */
#ifdef UCLINUX
				if (self_exec(argv0, "n", 1) < 0)
					tst_resm(TFAIL, "self_exec 1 failed");
#else
				do_exit();
#endif
			}
			if (ret_val < 0) {
				tst_resm(TFAIL, "Fork kid 1 failed. errno = "
					 "%d", errno);

			}

			/* parent */
			fork_kid_pid[kid_count++] = ret_val;

			ret_val = FORK_OR_VFORK();
			if (ret_val == 0) {	/* child 2 */
#ifdef UCLINUX
				if (self_exec(argv0, "n", 2) < 0)
					tst_resm(TFAIL, "self_exec 2 failed");
#else
				do_compute();
#endif
			}
			if (ret_val < 0) {
				tst_resm(TFAIL, "Fork kid 2 failed. errno = "
					 "%d", errno);

			}

			/* parent */
			fork_kid_pid[kid_count++] = ret_val;

			ret_val = FORK_OR_VFORK();
			if (ret_val == 0) {	/* child 3 */
#ifdef UCLINUX
				if (self_exec(argv0, "n", 2) < 0)
					tst_resm(TFAIL, "self_exec 3 failed");
#else
				do_compute();
#endif
			}
			if (ret_val < 0) {
				tst_resm(TFAIL, "Fork kid 3 failed. errno = "
					 "%d", errno);

			}

			/* parent */
			fork_kid_pid[kid_count++] = ret_val;

			ret_val = FORK_OR_VFORK();
			if (ret_val == 0) {	/* child 4 */
#ifdef UCLINUX
				if (self_exec(argv0, "n", 3) < 0)
					tst_resm(TFAIL, "self_exec 4 failed");
#else
				do_fork();
#endif
			}
			if (ret_val < 0) {
				tst_resm(TFAIL, "Fork kid 4 failed. errno = "
					 "%d", errno);

			}

			/* parent */
			fork_kid_pid[kid_count++] = ret_val;

			ret_val = FORK_OR_VFORK();
			if (ret_val == 0) {	/* child 5 */
#ifdef UCLINUX
				if (self_exec(argv0, "n", 3) < 0)
					tst_resm(TFAIL, "self_exec 5 failed");
#else
				do_fork();
#endif
			}
			if (ret_val < 0) {
				tst_resm(TFAIL, "Fork kid 5 failed. errno = "
					 "%d", errno);

			}

			/* parent */
			fork_kid_pid[kid_count++] = ret_val;

			ret_val = FORK_OR_VFORK();
			if (ret_val == 0) {	/* child 6 */
#ifdef UCLINUX
				if (self_exec(argv0, "n", 4) < 0)
					tst_resm(TFAIL, "self_exec 6 failed");
#else
				do_sleep();
#endif
			}
			if (ret_val < 0) {
				tst_resm(TFAIL, "Fork kid 6 failed. errno = "
					 "%d", errno);

			}

			/* parent */
			fork_kid_pid[kid_count++] = ret_val;

			ret_val = FORK_OR_VFORK();
			if (ret_val == 0) {	/* child 7 */
#ifdef UCLINUX
				if (self_exec(argv0, "n", 4) < 0)
					tst_resm(TFAIL, "self_exec 7 failed");
#else
				do_sleep();
#endif
			}
			if (ret_val < 0) {
				tst_resm(TFAIL, "Fork kid 7 failed. errno = "
					 "%d", errno);

			}

			/* parent */
			fork_kid_pid[kid_count++] = ret_val;

			nkids = kid_count;

			/*
			 * Now send all the kids a SIGINT to tell them to
			 * proceed. We sleep for a while first to allow the
			 * children to initialize their "intintr" variables
			 * and get set up.
			 */
			sleep(15);

			for (i = 0; i < nkids; i++) {
				if (kill(fork_kid_pid[i], SIGINT) < 0) {
					tst_resm(TFAIL, "Kill of child %d "
						 "failed, errno = %d", i,
						 errno);
				}
			}

			/* Wait till all kids have terminated. */
			kid_count = 0;
			errno = 0;
			for (i = 0; i < nkids; i++) {
				while (((ret_val = waitpid(fork_kid_pid[i],
							   &status, 0)) != -1)
				       || (errno == EINTR)) {
					if (ret_val == -1)
						continue;

					wait_kid_pid[kid_count++] = ret_val;
				}
			}

			/*
			 * Check that for every entry in the fork_kid_pid
			 * array, there is a matching pid in the
			 * wait_kid_pid array.
			 */
			for (i = 0; i < MAXKIDS; i++) {
				found = 0;
				for (j = 0; j < MAXKIDS; j++) {
					if (fork_kid_pid[i] == wait_kid_pid[j]) {
						found = 1;
						break;
					}
				}
				if (!found) {
					tst_resm(TFAIL, "Did not find a "
						 "wait_kid_pid for the "
						 "fork_kid_pid of %d",
						 fork_kid_pid[i]);
					for (k = 0; k < nkids; k++) {
						tst_resm(TFAIL,
							 "fork_kid_pid[%d] = "
							 "%d", k,
							 fork_kid_pid[k]);
					}
					for (k = 0; k < kid_count; k++) {
						tst_resm(TFAIL,
							 "wait_kid_pid[%d] = "
							 "%d", k,
							 wait_kid_pid[k]);
					}
					fail = 1;
				}
			}
		}

		/* Kill kids and remove file from do_mkdir */
		rmdir("waitpid14.ttt.ttt");

		if (fail)
			tst_resm(TFAIL, "Test FAILED");
		else
			tst_resm(TPASS, "Test PASSED");
	}

	cleanup();
	tst_exit();
}

static void setup(void)
{
	tst_sig(FORK, DEF_HANDLER, cleanup);

	TEST_PAUSE;
}

static void cleanup(void)
{
	TEST_CLEANUP;
}

static void alrmhandlr(void)
{
	alrmintr++;
}

static void inthandlr(void)
{
	intintr++;
}

static void wait_for_parent(void)
{
	int testvar;

	while (!intintr)
		testvar = 0;
}

static void do_exit(void)
{
	wait_for_parent();
	exit(3);
}

static void do_compute(void)
{
	int i;

	wait_for_parent();

	for (i = 0; i < 100000; i++) ;
	for (i = 0; i < 100000; i++) ;
	for (i = 0; i < 100000; i++) ;
	for (i = 0; i < 100000; i++) ;
	for (i = 0; i < 100000; i++) ;
	for (i = 0; i < 100000; i++) ;
	for (i = 0; i < 100000; i++) ;
	for (i = 0; i < 100000; i++) ;
	for (i = 0; i < 100000; i++) ;
	for (i = 0; i < 100000; i++) ;

	exit(4);
}

static void do_fork(void)
{
	int fork_pid, wait_pid;
	int status, i;

	wait_for_parent();

	/*
	 * Fork a kid.  Keep track of the kid's pid, have the kid do_mkdir,
	 * and wait for it. Compare the fork_pid with the wait_pid to be
	 * sure they are the same.
	 */
	for (i = 0; i < 50; i++) {
		fork_pid = FORK_OR_VFORK();
		if (fork_pid < 0) {
			tst_resm(TFAIL, "Fork failed");
			tst_exit();
		}
		if (fork_pid == 0) {
#ifdef UCLINUX
			if (self_exec(argv0, "n", 5) < 0) {
				tst_resm(TFAIL, "do_fork self_exec failed");
				tst_exit();
			}
#else
			do_mkdir();
#endif
		}

		errno = 0;
		while (((wait_pid = waitpid(fork_pid, &status, 0)) != -1) ||
		       (errno == EINTR)) {
			if (wait_pid == -1)
				continue;

			if (fork_pid != wait_pid) {
				tst_resm(TFAIL, "Didnt get a pid returned "
					 "from waitpid that matches the one "
					 "returned by fork");
				tst_resm(TFAIL, "fork pid = %d, wait pid = "
					 "%d", fork_pid, wait_pid);
				fail = 1;
			}
		}
	}

	exit(4);
}

static void do_sleep(void)
{
	wait_for_parent();
	sleep(1);
	sleep(1);

	exit(4);
}

static void do_mkdir(void)
{
	int ret_val;

	/*
	 * Please note that this will succeed once, and then fail. That's
	 * part of the test.
	 */
	ret_val = mkdir("waitpid14.ttt.ttt", 0777);

	exit(4);
}
