/* 
 * waitpid.c --
 *
 *	This procedure emulates the POSIX waitpid kernel call on
 *	BSD systems that don't have waitpid but do have wait3.
 *	This code is based on a prototype version written by
 *	Mark Diekhans and Karl Lehenbauer.
 *
 * Copyright (c) 1993 The Regents of the University of California.
 * Copyright (c) 1994 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * SCCS: @(#) waitpid.c 1.9 96/02/15 12:08:26
 */

#include "tclInt.h"
#include "tclPort.h"

/*
 * A linked list of the following structures is used to keep track
 * of processes for which we received notification from the kernel,
 * but the application hasn't waited for them yet (this can happen
 * because wait may not return the process we really want).  We
 * save the information here until the application finally does
 * wait for the process.
 */

typedef struct WaitInfo {
    int pid;				/* Pid of process that exited. */
    WAIT_STATUS_TYPE status;		/* Status returned when child exited
					 * or suspended. */
    struct WaitInfo *nextPtr;		/* Next in list of exited processes. */
} WaitInfo;

static WaitInfo *deadList = NULL;	/* First in list of all dead
					 * processes. */

/*
 *----------------------------------------------------------------------
 *
 * waitpid --
 *
 *	This procedure emulates the functionality of the POSIX
 *	waitpid kernel call, using the BSD wait3 kernel call.
 *	Note:  it doesn't emulate absolutely all of the waitpid
 *	functionality, in that it doesn't support pid's of 0
 *	or < -1.
 *
 * Results:
 *	-1 is returned if there is an error in the wait kernel call.
 *	Otherwise the pid of an exited or suspended process is
 *	returned and *statusPtr is set to the status value of the
 *	process.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

#ifdef waitpid
#   undef waitpid
#endif

int
waitpid(pid, statusPtr, options)
    int pid;			/* The pid to wait on.  Must be -1 or
				 * greater than zero. */
    int *statusPtr;		/* Where to store wait status for the
				 * process. */
    int options;		/* OR'ed combination of WNOHANG and
				 * WUNTRACED. */
{
    register WaitInfo *waitPtr, *prevPtr;
    int result;
    WAIT_STATUS_TYPE status;

    if ((pid < -1) || (pid == 0)) {
	errno = EINVAL;
	return -1;
    }

    /*
     * See if there's a suitable process that has already stopped or
     * exited. If so, remove it from the list of exited processes and
     * return its information.
     */

    for (waitPtr = deadList, prevPtr = NULL; waitPtr != NULL;
	    prevPtr = waitPtr, waitPtr = waitPtr->nextPtr) {
	if ((pid != waitPtr->pid) && (pid != -1)) {
	    continue;
	}
	if (!(options & WUNTRACED) && (WIFSTOPPED(waitPtr->status))) {
	    continue;
	}
	result = waitPtr->pid;
	*statusPtr = *((int *) &waitPtr->status);
	if (prevPtr == NULL) {
	    deadList = waitPtr->nextPtr;
	} else {
	    prevPtr->nextPtr = waitPtr->nextPtr;
	}
	ckfree((char *) waitPtr);
	return result;
    }

    /*
     * Wait for any process to stop or exit.  If it's an acceptable one
     * then return it to the caller;  otherwise store information about it
     * in the list of exited processes and try again.  On systems that
     * have only wait but not wait3, there are several situations we can't
     * handle, but we do the best we can (e.g. can still handle some
     * combinations of options by invoking wait instead of wait3).
     */

    while (1) {
#if NO_WAIT3
	if (options & WNOHANG) {
	    return 0;
	}
	if (options != 0) {
	    errno = EINVAL;
	    return -1;
	}
	result = wait(&status);
#else
	result = wait3(&status, options, 0);
#endif
	if ((result == -1) && (errno == EINTR)) {
	    continue;
	}
	if (result <= 0) {
	    return result;
	}

	if ((pid != result) && (pid != -1)) {
	    goto saveInfo;
	}
	if (!(options & WUNTRACED) && (WIFSTOPPED(status))) {
	    goto saveInfo;
	}
	*statusPtr = *((int *) &status);
	return result;

	/*
	 * Can't return this info to caller.  Save it in the list of
	 * stopped or exited processes.  Tricky point: first check for
	 * an existing entry for the process and overwrite it if it
	 * exists (e.g. a previously stopped process might now be dead).
	 */

	saveInfo:
	for (waitPtr = deadList; waitPtr != NULL; waitPtr = waitPtr->nextPtr) {
	    if (waitPtr->pid == result) {
		waitPtr->status = status;
		goto waitAgain;
	    }
	}
	waitPtr = (WaitInfo *) ckalloc(sizeof(WaitInfo));
	waitPtr->pid = result;
	waitPtr->status = status;
	waitPtr->nextPtr = deadList;
	deadList = waitPtr;

	waitAgain: continue;
    }
}
