/*
 *
 *   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
 *	waitpid11.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 = 0
 *
 * USAGE:  <for command-line>
 *      waitpid11 [-c n] [-t]
 *      where,  -c n : Run n copies concurrently.
 *              -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"

#define	MAXKIDS	8

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

volatile int intintr;
static void setup(void);
static void cleanup(void);
static void inthandlr();
static void wait_for_parent(void);
static void do_exit(void);
static void setup_sigint(void);
#ifdef UCLINUX
static void do_exit_uclinux(void);
#endif

static int fail;
static int fork_kid_pid[MAXKIDS];

int main(int ac, char **av)
{
	int kid_count, ret_val, status;
	int i, j, k, found;
	int group1, group2;
	int wait_kid_pid[MAXKIDS];
	int pid;

	tst_parse_opts(ac, av, NULL, NULL);

#ifdef UCLINUX
	maybe_run_child(&do_exit_uclinux, "");
#endif

	setup();

	tst_count = 0;
	fail = 0;

	/*
	 * Need to have test run from child as test driver causes
	 * test to be a session leader and setpgrp fails.
	 */

	pid = FORK_OR_VFORK();
	if (pid > 0) {
		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);
		tst_exit();
	} else if (pid < 0)
		tst_brkm(TBROK, cleanup, "fork failed");

	/*
	 * Set up to catch SIGINT.  The kids will wait till a SIGINT
	 * has been received before they proceed.
	 */
	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(av[0], "") < 0)
				tst_resm(TFAIL | TERRNO, "self_exec kid %d "
					 "failed", kid_count);
#else
			do_exit();
#endif
		}

		if (ret_val < 0)
			tst_brkm(TBROK|TERRNO, cleanup, "Fork kid %d failed",
				 kid_count);

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

#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_resm(TFAIL | TERRNO, "Kill of child %d failed", i);

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

		if (!WIFEXITED(status)) {
			tst_resm(TFAIL, "Child %d did not exit "
				 "normally", ret_val);
			fail = 1;
		} else {
			if (WEXITSTATUS(status) != 3) {
				tst_resm(TFAIL, "Child %d exited with "
					 "wrong status", ret_val);
				tst_resm(TFAIL, "Expected 3 got %d",
					 WEXITSTATUS(status));
				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 = (MAXKIDS / 2); j < MAXKIDS; j++) {
			if (fork_kid_pid[j] == wait_kid_pid[i]) {
				found = 1;
				break;
			}
		}
		if (!found) {
			tst_resm(TFAIL, "Did not find a fork_kid_pid "
				 "for the wait_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;
		}
	}

	if (kid_count != (MAXKIDS / 2)) {
		tst_resm(TFAIL, "Wrong number of children waited on "
			 "for pid = 0");
		tst_resm(TFAIL, "Expected 4 got %d", kid_count);
		fail = 1;
	}

	/* Make sure can pickup children in a diff. process group */

	kid_count = 0;
	errno = 0;
	while (((ret_val = waitpid(-(group1), &status, 0)) != -1) ||
	       (errno == EINTR)) {
		if (ret_val == -1)
			continue;
		if (!WIFEXITED(status)) {
			tst_resm(TFAIL, "Child %d did not exit "
				 "normally", ret_val);
			fail = 1;
		} else {
			if (WEXITSTATUS(status) != 3) {
				tst_resm(TFAIL, "Child %d exited with "
					 "wrong status", ret_val);
				tst_resm(TFAIL, "Expected 3 got %d",
					 WEXITSTATUS(status));
				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 / 2); j++) {
			if (fork_kid_pid[j] == wait_kid_pid[i]) {
				found = 1;
				break;
			}
		}
		if (!found) {
			tst_resm(TFAIL, "Did not find a fork_kid_pid "
				 "for the wait_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;
		}
	}

	memset(fork_kid_pid, 0, sizeof(fork_kid_pid));

	if (kid_count != (MAXKIDS / 2)) {
		tst_resm(TFAIL, "Wrong number of children waited on "
			 "for pid = 0");
		tst_resm(TFAIL, "Expected 4 got %d", kid_count);
		fail = 1;
	}

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

	cleanup();
	tst_exit();
}

static void setup_sigint(void)
{
	if (signal(SIGINT, inthandlr) == SIG_ERR)
		tst_brkm(TFAIL | TERRNO, NULL, "signal SIGINT failed");
}

static void setup(void)
{
	TEST_PAUSE;
}

static void cleanup(void)
{
	int i;

	for (i = 0; i < MAXKIDS; i++) {
		if (fork_kid_pid[i] > 0)
			kill(fork_kid_pid[i], SIGKILL);
	}
}

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

#ifdef UCLINUX
static void do_exit_uclinux(void)
{
	setup_sigint();
	do_exit();
}
#endif
