/******************************** -*- C -*- ****************************
 *
 *	waitpid substitute for BSD systems
 *
 *	$Revision: 1.7.5$
 *	$Date: 2000/05/28 16:56:52$
 *	$Author: pb$
 *
 ***********************************************************************/

/***********************************************************************
 *
 * Copyright 1988-92, 1994-95, 1999, 2000 Free Software Foundation, Inc.
 * Written by Paolo Bonzini.
 *
 * This file is part of GNU Smalltalk.
 *
 * GNU Smalltalk 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, or (at your option) any later 
 * version.
 * 
 * GNU Smalltalk 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
 * GNU Smalltalk; see the file COPYING.  If not, write to the Free Software
 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  
 *
 */

#include "gstconf.h"

#ifndef HAVE_WAITPID

#include "gst.h"
#include "alloc.h"
#include "sysdep.h"
#include <errno.h>
#include <sys/signal.h>

#if !defined (SIGCHLD) && defined (SIGCLD)
#define SIGCHLD SIGCLD
#endif /* SIGCHLD */

#ifdef HAVE_SYS_WAIT_H
# include <sys/wait.h>
# ifdef waitpid
#  undef waitpid
# endif
#else
# ifdef HAVE_WAIT3
    extern pid_t		wait3();
# else
    extern pid_t		wait();
# endif
#endif

#ifdef _AIX
typedef pid_t			 WaitStatusType;
#define WAIT_CAST(w)		 ((int) (w))
#else
typedef int			 WaitStatusType;
#define WAIT_CAST(w)		 (w)
#endif

/* Supply definitions for sys/wait.h if not included, or if
 * already defined in header file above.
 */

#ifndef WIFEXITED
#define WIFEXITED(w)	((WAIT_CAST(w) & 0xff) == 0)
#endif

#ifndef WEXITSTATUS
#define WEXITSTATUS(w)	((WAIT_CAST(w) >> 8) & 0xff)
#endif

#ifndef WIFSIGNALED
#define WIFSIGNALED(w)	((WAIT_CAST(w) & 0x7f) > 0 && ((WAIT_CAST(w) & 0x7f) < 0x7f))
#endif

#ifndef WTERMSIG
#define WTERMSIG(w)	(WAIT_CAST(w) & 0x7f)
#endif

#ifndef WIFSTOPPED
#define WIFSTOPPED(w)	((WAIT_CAST(w) & 0xff) == 0x7f)
#endif

#ifndef WSTOPSIG
#define WSTOPSIG	WEXITSTATUS
#endif

#ifndef WNOHANG
#define WNOHANG 1
#endif
#ifndef WUNTRACED
#define WUNTRACED 2
#endif

/*
 * 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 child that exited. */
  WaitStatusType status;	/* Status returned by child  */
  struct WaitInfo *nextPtr;	/* Next in the dead processes list */
} WaitInfo;

static WaitInfo *deadList = NULL;	/* Head of dead processes list. */


static RETSIGTYPE		childSignalHandler();

static void			saveInfo();

/* Just a convenience */
#ifndef HAVE_WAIT3
#define wait3(statusPtr, options, rusage)	wait(statusPtr)
#endif

RETSIGTYPE
childSignalHandler(sig)
     int	sig;
{
  int result;
  int status;
  result = wait3(&status, 0, NULL);

  /* Only set the signal handler *AFTER* waiting.  On a USG system
   * the SIGCLD handler MUST NOT call signal() before executing at
   * least one wait(), otherwise the handler will be called again,
   * resulting in an infinite loop (note found in Emacs).  The
   * relevant portion of the documentation reads "SIGCLD signals
   * will [also] be queued [as soon as signal() is called], and the
   * signal-catching function will be continually reentered until
   * the queue is empty". */
  setSignalHandler(sig, childSignalHandler);
  saveInfo(result, status);
}


void
saveInfo(pid, status)
     int pid;
     int status;
{
  /* Tricky point: first check for an existing entry for the process
   * the process and overwrite it if it exists (e.g. a previously 
   * stopped process might now be dead).
   */

  register WaitInfo *waitPtr;

  for (waitPtr = deadList; waitPtr; waitPtr = waitPtr->nextPtr) {
    if (waitPtr->pid == pid) {
      waitPtr->status = status;
      return;
    }
  }
  waitPtr = (WaitInfo *) xmalloc(sizeof(WaitInfo));
  waitPtr->pid = pid;
  waitPtr->status = status;
  waitPtr->nextPtr = deadList;
  deadList = waitPtr;
}


/*
 *----------------------------------------------------------------------
 *
 * waitpid
 *
 *	This procedure emulates the functionality of the POSIX
 *	waitpid kernel call, using the wait or wait3 kernel calls.
 *	Note:  it doesn't emulate absolutely all of the waitpid
 *	functionality, in that it doesn't support pid's of 0
 *	or < -1.
 *
 * Input:
 *	pid	     pid to wait on.  Must be -1 or greater than zero.
 *	statusPtr    where to store wait status for the process.
 *	options	     OR'ed combination of WNOHANG and WUNTRACED.
 *
 * Output:
 *	-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:
 *	A SIGCHLD handler is set.
 *
 *---------------------------------------------------------------------*/

int
waitpid(pid, statusPtr, options)
     int pid;
     int *statusPtr;
     int options;
{
  static mst_Boolean	signalHandler = false;
  register WaitInfo	*waitPtr, **refPtr;
  int			result;
  WaitStatusType	status;

  if (!signalHandler) {
    signalHandler = true;
    setSignalHandler(SIGCHLD, childSignalHandler);
  }

  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.
   * Note the additional layer of indirection which refPtr introduces:
   * it makes the code a bit harder to read if you want to follow it
   * bit for bit, but the overall view of the code is clearer because
   * there are no checks for waitPtr to be the first item in the list.
   */

  for (refPtr = &deadList; waitPtr = *refPtr; refPtr = &waitPtr->nextPtr) {
    if ((pid == -1) || (pid == waitPtr->pid)) {
      if ((options & WUNTRACED) || !WIFSTOPPED(waitPtr->status)) {
	result = waitPtr->pid;
	*statusPtr = WAIT_CAST(waitPtr->status);
	*refPtr = waitPtr->nextPtr;
	xfree((char *) waitPtr);
	return result;
      }
    }
  }

  if (options & WNOHANG) {
    /* When WNOHANG is specified, only check the list (filled by 
     * the SIGCHLD handler and by non-blocking calls to waitpid) */
    return 0;
  }

  /* Wait for any process to stop or exit.  If it's acceptable, then
   * return it to the caller;  otherwise store information about it
   * in the list of exited processes and try again.
   */
  for (;;) {
    result = wait3(&status, options, NULL);
    if (result <= 0) {
      if (errno == EINTR) {
        errno = 0;
        continue;
      } else {
        return result;
      }
    }

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

    /* Can't return this info to caller.  Save it in the dead process
     * list and try again... */
    saveInfo(result, status);
  }
}

#endif /* HAVE_WAITPID */
