/*
 * $Id: wait4shell.c,v 1.8 1998/12/08 17:02:48 agalat Rel $
 *
 * This file contains code that waits for a child process to die.  It
 * also chooses to intercept a couple of signals that it will kindly
 * pass on a SIGTERM to the child ;^). Waiting again for the child to
 * exit. If the child resists dying, it will SIGKILL it!
 *
 */

#include <stdio.h>
#include <signal.h>
#include <wait.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#include "../include/wait4shell.h"

#define SLEEP_TO_KILL_CHILDREN    3  /* seconds to wait after SIGTERM before
                                        SIGKILL */

int wait_for_child_caught=0;
int need_job_control=0;

static void wait_for_child_catch_sig()
{
    wait_for_child_caught = 1;
}

void prepare_for_job_control(int need_it)
{
    sigset_t ourset;

    (void) sigfillset(&ourset);
    if (sigprocmask(SIG_BLOCK, &ourset, NULL) != 0) {
	(void) fprintf(stderr,"[trouble blocking signals]\n");
	wait_for_child_caught = 1;
	return;
    }
    need_job_control = need_it;
}

int wait_for_child(pid_t child)
{
    int retval, status, exit_code;
    sigset_t ourset;

    exit_code = -1; /* no exit code yet, exit codes could be from 0 to 255 */

    /*
     * set up signal handling
     */

    if (!wait_for_child_caught) {
	struct sigaction action, defaction;

	action.sa_handler = wait_for_child_catch_sig;
	(void) sigemptyset(&action.sa_mask);
	action.sa_flags = 0;

	defaction.sa_handler = SIG_DFL;
	(void) sigemptyset(&defaction.sa_mask);
	defaction.sa_flags = 0;

	(void) sigemptyset(&ourset);
	       
	if (   sigaddset(&ourset, SIGTERM)
	    || sigaction(SIGTERM, &action, NULL)
	    || sigaddset(&ourset, SIGHUP)
	    || sigaction(SIGHUP, &action, NULL)
	    || sigaddset(&ourset, SIGALRM)          /* required by sleep(3) */
            || (need_job_control && sigaddset(&ourset, SIGTSTP))
            || (need_job_control && sigaction(SIGTSTP, &defaction, NULL))
            || (need_job_control && sigaddset(&ourset, SIGTTIN))
            || (need_job_control && sigaction(SIGTTIN, &defaction, NULL))
            || (need_job_control && sigaddset(&ourset, SIGTTOU))
            || (need_job_control && sigaction(SIGTTOU, &defaction, NULL))
	    || (need_job_control && sigaddset(&ourset, SIGCONT))
            || (need_job_control && sigaction(SIGCONT, &defaction, NULL))
	    || sigprocmask(SIG_UNBLOCK, &ourset, NULL)
	    ) {
	    (void) fprintf(stderr,"[trouble setting signal intercept]\n");
	    wait_for_child_caught = 1;
	}

	/* application should be ready for receiving a SIGTERM/HUP now */
    }

    /* this code waits for the process to actually die. If it stops,
     * then the parent attempts to mimic the behavior of the
     * child.. There is a slight bug in the code when the 'su'd user
     * attempts to restart the child independently of the parent --
     * the child dies. */

    while (!wait_for_child_caught) {

        /* parent waits for child */
	if ((retval = waitpid(child, &status, 0)) <= 0) {
            if (errno == EINTR)
                continue;             /* recovering from a 'fg' */
            (void) fprintf(stderr, "[error waiting child: %s]\n"
                           , strerror(errno));
            /*
             * Break the loop keeping exit_code undefined.
             * Do we have a chance for a successfull wait() call
             * after kill()? (SAW)
             */
            wait_for_child_caught = 1;
            break;
        }else {
	    /* the child is terminated via exit() or a fatal signal */
	    if (WIFEXITED(status))
		exit_code = WEXITSTATUS(status);
	    else
		exit_code = 1;
	    break;
	}
    }

    if (wait_for_child_caught) {
	(void) fprintf(stderr,"\nKilling shell...");
	(void) kill(child, SIGTERM);
    }

    /*
     * do we need to wait for the child to catch up?
     */

    if (wait_for_child_caught) {
	(void) sleep(SLEEP_TO_KILL_CHILDREN);
	(void) kill(child, SIGKILL);
	(void) fprintf(stderr, "killed\n");
    }

    /*
     * collect the zombie the shell was killed by ourself
     */
    if (exit_code == -1) {
	do {
	    retval = waitpid(child, &status, 0);
	}while (retval == -1 && errno == EINTR);
	if (retval == -1) {
	    (void) fprintf(stderr, "su: the final wait failed: %s\n"
			   , strerror(errno));
	}
	if (WIFEXITED(status))
	    exit_code = WEXITSTATUS(status);
	else
	    exit_code = 1;
    }

    return exit_code;
}
