/*
 *
 *   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
 *	waitpid08.c
 *
 * DESCRIPTION
 *	Tests to see if pid's returned from fork and waitpid are same
 *
 * ALGORITHM
 *	Check proper functioning of waitpid with pid = -1 and arg =
 *	WUNTRACED
 *
 * USAGE:  <for command-line>
 *      waitpid08 [-c n] [-e] [-i n] [-I x] [-P x] [-t]
 *      where,  -c n : Run n copies concurrently.
 *              -e   : Turn on errno logging.
 *              -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.
 *
 * History
 *	07/2001 John George
 *		-Ported
 *      04/2002 wjhuie sigset cleanups
 *
 * Restrictions
 *	None
 */

#include <sys/types.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
#include "test.h"

static void inthandlr();
static void do_exit(void);
static void setup_sigint(void);
static void do_child_1(void);
static void setup(void);
static void cleanup(void);

char *TCID = "waitpid08";
int TST_TOTAL = 1;
volatile int intintr;
static int fail;

#define	MAXKIDS	8

#ifdef UCLINUX
static char *argv0;
static void do_child_2_uclinux(void);
#endif

int main(int argc, char **argv)
{
	int lc;

	int status;
	int pid;

	tst_parse_opts(argc, argv, NULL, NULL);

#ifdef UCLINUX
	argv0 = argv[0];

	maybe_run_child(&do_child_1, "n", 1);
	maybe_run_child(&do_child_2_uclinux, "n", 2);
#endif

	setup();

	/* check for looping state if -i option is given */
	for (lc = 0; TEST_LOOPING(lc); lc++) {
		/* reset tst_count in case we are looping */
		tst_count = 0;
		fail = 0;

		pid = FORK_OR_VFORK();
		if (pid < 0) {
			tst_resm(TFAIL, "Fork Failed, may be OK under stress");

		} else if (pid == 0) {
			/*
			 * Child:
			 * Set up to catch SIGINT.
			 * The kids will wait till a SIGINT has been received
			 * before they proceed.
			 */
#ifdef UCLINUX
			if (self_exec(argv[0], "n", 1) < 0) {
				tst_resm(TINFO, "self_exec failed");
				exit(pid);
			}
#else
			do_child_1();
#endif
		} else {	/* parent */
			waitpid(pid, &status, 0);
			if (WEXITSTATUS(status) != 0) {
				tst_resm(TFAIL, "child returned bad status");
				fail = 1;
			}
			if (fail)
				tst_resm(TFAIL, "%s FAILED", TCID);
			else
				tst_resm(TPASS, "%s PASSED", TCID);
		}
	}

	cleanup();
	tst_exit();
}

/*
 * setup_sigint()
 *	Sets up a SIGINT handler
 */
static void setup_sigint(void)
{
	if ((sig_t) signal(SIGINT, inthandlr) == SIG_ERR) {
		tst_resm(TFAIL, "signal SIGINT failed. " "errno = %d", errno);
		exit(-1);
	}
}

static void do_child_1(void)
{
	int kid_count, fork_kid_pid[MAXKIDS];
	int ret_val;
	int i, j, k, found;
	int group1, group2;
	int wait_kid_pid[MAXKIDS], status;

	setup_sigint();

	group1 = getpgrp();

	for (kid_count = 0; kid_count < MAXKIDS; kid_count++) {
		if (kid_count == (MAXKIDS / 2))
			group2 = setpgrp();

		intintr = 0;
		ret_val = FORK_OR_VFORK();
		if (ret_val == 0) {
#ifdef UCLINUX
			if (self_exec(argv0, "n", 2) < 0) {
				tst_resm(TFAIL, "Fork kid %d failed. "
					 "errno = %d", kid_count, errno);
				exit(ret_val);
			}
#else
			do_exit();
#endif
		}

		if (ret_val < 0) {
			tst_resm(TFAIL, "Fork kid %d failed. "
				 "errno = %d", kid_count, errno);
			exit(ret_val);
		}

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

	/* Check that waitpid with WNOHANG|WUNTRACED returns
	 * zero
	 */
	ret_val = waitpid(-1, &status, WNOHANG | WUNTRACED);
	if (ret_val != 0) {
		tst_resm(TFAIL, "Waitpid returned wrong value");
		tst_resm(TFAIL, "Expected 0 got %d", ret_val);
		fail = 1;
	}
#ifdef UCLINUX
	/* Give the kids a chance to setup SIGINT again, since this is
	 * cleared by exec().
	 */
	sleep(3);
#endif

	/* Now send all the kids a SIGINT to tell them to
	 * proceed
	 */
	for (i = 0; i < MAXKIDS; i++) {
		if (kill(fork_kid_pid[i], SIGINT) < 0) {
			tst_brkm(TFAIL, NULL, "Kill of child %d "
				 "failed, errno = %d", i, errno);
		}
	}

	/*
	 * Wait till all kids have terminated.  Stash away their
	 * pid's in an array.
	 */
	kid_count = 0;
	errno = 0;
	while (((ret_val = waitpid(-1, &status, WUNTRACED)) !=
		-1) || (errno == EINTR)) {
		if (ret_val == -1)
			continue;

		if (!WIFEXITED(status)) {
			if (!WIFSTOPPED(status)) {
				tst_resm(TFAIL, "Child %d did "
					 "not stopped", ret_val);
				fail = 1;
			} else {
				if (WSTOPSIG(status) != SIGSTOP) {
					tst_resm(TFAIL,
						 "Child %d "
						 "exited with "
						 "wrong status", ret_val);
					tst_resm(TFAIL,
						 "Expected "
						 "SIGSTOP got %d",
						 WSTOPSIG(status));
					fail = 1;
				}
			}
			if (kill(ret_val, SIGCONT) < 0) {
				tst_resm(TFAIL, "Kill of child "
					 "%d failed, errno = %d",
					 ret_val, errno);
				fail = 1;
			}
		}
		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. If
	 * not, it's an error.
	 */
	for (i = 0; i < kid_count; i++) {
		found = 0;
		for (j = 0; j < MAXKIDS; j++) {
			if (fork_kid_pid[j] == wait_kid_pid[i]) {
				found = 1;
				break;
			}
		}
		if (!found) {
			tst_resm(TFAIL,
				 "Did not find a wait_kid_pid "
				 "for the fork_kid_pid of %d", wait_kid_pid[i]);
			for (k = 0; k < MAXKIDS; 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;
		}
	}

	/*
	 * Check that waitpid(WUNTRACED) returns -1 when no
	 * stopped children
	 */
	ret_val = waitpid(-1, &status, WUNTRACED);
	if (ret_val != -1) {
		tst_resm(TFAIL, "Waitpid returned wrong value.");
		tst_resm(TFAIL, "Expected -1 got %d from "
			 "waitpid(WUNTRACED)", ret_val);
		fail = 1;
	}

	if (errno != ECHILD) {
		tst_resm(TFAIL, "Waitpid returned wrong errno.");
		tst_resm(TFAIL, "Expected ECHILD got %d", errno);
		fail = 1;
	}

	exit(fail);
}

#ifdef UCLINUX
/*
 * do_child_2_uclinux()
 *	sets up sigint handler again, then calls the normal child 2 function
 */
static void do_child_2_uclinux(void)
{
	setup_sigint();
	do_exit();
}
#endif

static void setup(void)
{
	TEST_PAUSE;
}

static void cleanup(void)
{
}

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);
}
