/*
 * Process and job control
 */
#ifndef lint
static char *RCSid = "$Id: jobs.c,v 1.4 1992/04/27 07:14:26 sjg Exp $";
#endif

/*
 * based on version by Ron Natalie, BRL
 * modified by Simon J. Gerraty <sjg@melb.bull.oz.au>
 *
 * TODO:
 *	change Proc table to Job table, with array of pids.
 *	make %+ be jobs, %- be jobs->next.
 *	do not JFREE members of pipeline.
 *	consider procs[] related critical sections.
 *	signal(SIGCHLD, j_sigchld) should be
 *	sigaction(SIGCHLD, sigchld, NULL),
 *	with sigchld.sa_flags = SA_RESTART.
 *	There is a simple work-around if there is no SA_RESTART.
 */

#include "stdh.h"
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <setjmp.h>
#include <sys/times.h>
#include <sys/wait.h>
#include "sh.h"
#ifdef JOBS
#ifdef _BSD
#include <sys/ioctl.h>
#else
#include "termios.h"
#endif
#endif

#ifdef _BSD
/*
 * These macros are for the benefit of SunOS 4.0.2 and 4.0.3
 * SunOS 4.1.1 already defines most of them.
 * Clearly these are aimed at SunOS, they may work for other
 * BSD systems but I can't promise.
 */
# ifndef WIFSTOPPED
#   define WIFSTOPPED(x)	((x).w_stopval == WSTOPPED)
# endif
# ifndef WIFSIGNALED
#   define WIFSIGNALED(x) ((x).w_stopval != WSTOPPED && (x).w_termsig != 0)
# endif
# ifndef WIFEXITED
#   define WIFEXITED(x)	((x).w_stopval != WSTOPPED && (x).w_termsig == 0)
# endif
# ifndef WSTOPSIG
#   define WSTOPSIG(x)	((x).w_stopsig)
# endif
# ifndef WTERMSIG
#   define WTERMSIG(x)	((x).w_termsig)
# endif
# ifndef WIFCORED
#   define WIFCORED(x)	((x).w_coredump)
# endif
# ifndef WEXITSTATUS
#   define WEXITSTATUS(x)	((x).w_retcode)
# endif
# ifndef HAVE_WAITPID
#   define	waitpid(pid, sp, opts)	wait3(sp, opts, (void*)NULL)
# endif
#else					/* not _BSD */
# ifndef WIFCORED
#   define	WIFCORED(x)	(!!((x)&0x80)) /* non-standard */
# endif
#endif

/* as of P1003.1 Draft 12.3:
 *	pid_t getpgrp(void);		// Get process group id
 *	pid_t setsid(void);		// Create session and Set process group id
 *	int setpgid(pid_t pid, pid_t pgid); // Set process group id for job control
 */


#ifdef JOBS
#ifdef _BSD			/* _BSD 4.* */
#ifndef amigados
#define	setpgid(p, pg)	setpgrp(p, pg)
#define	getpgid(p)	getpgrp(p)
#define	tcsetpgrp(fd,p)	ioctl(fd, TIOCSPGRP, &(p))
#endif
#else				/* POSIX-compatible */
#define	getpgid(p)	getpgrp() /* 1003.1 stupidity */
#define	killpg(p, s)	kill(-(p), s)
#endif
#endif


#ifndef	SIGCHLD
#define	SIGCHLD	SIGCLD
#endif
#ifndef WAIT_T
#ifdef _BSD
#define WAIT_T union wait
#else
#define WAIT_T int
#endif
#endif
#ifndef SA_RESTART
#define SA_RESTART	0
#endif

typedef struct Proc Proc;
struct Proc {
	Proc   *next;		/* `procs' list link */
	int	job;		/* job number: %n */
	short	volatile state;	/* proc state */
	short	volatile notify; /* proc state has changed */
	Proc   *prev;		/* prev member of pipeline */
	pid_t	proc;		/* process id */
	pid_t	pgrp;		/* process group if flag[FMONITOR] */
	short	flags;		/* execute flags */
	WAIT_T	status;		/* wait status */
	clock_t	utime, stime;	/* user/system time when JDONE */
	char	com [48];	/* command */
};

/* proc states */
#define	JFREE	0		/* unused proc */
#define	JRUN	1		/* foreground */
#define JEXIT	2		/* exit termination */
#define	JSIGNAL	3		/* signal termination */
#define	JSTOP	4		/* stopped */

static	Proc *procs = NULL;	/* job/process table */

clock_t	j_utime, j_stime;	/* user and system time for last job a-waited */
#ifdef JOBS
# ifdef USE_SIGACT
sigset_t sm_default, sm_sigchld;	/* signal masks */
# else
static	int	sm_default, sm_sigchld;	/* signal masks */
# endif
static	int	our_pgrp;		/* shell's pgrp */
#endif
static	Proc   *j_lastj;		/* last proc created by exchild */
static	int	j_lastjob = 0;		/* last created job */
static	int	j_current = 0;		/* current job */
static	int	j_previous = 0;		/* previous job */

static int      j_waitj     ARGS((Proc *aj, int intr));
static void     j_print     ARGS((Proc *j));
static int      j_newjob    ARGS((void));
static Proc *   j_search    ARGS((int job));
static void	j_sigchld   ARGS((int sig));

#ifdef amigados
/* those are needed for later relocation support after vfork() */

u_int inline static 
get_a4 ()
{
  u_int res;
  asm ("movel	a4,%0" : "=g" (res));
  return res;
}
static inline int dbsize() 
{
  int res;
  asm ("movel #___data_size,%0; addl #___bss_size,%0" : "=r" (res));
  return res;
}
extern int __datadata_relocs();
#endif


	
/* initialize job control */
void
j_init()
{
#ifdef JOBS
# if defined(NTTYDISC) && defined(TIOCSETD)
	int ldisc = NTTYDISC;	/* BSD brain damage */

	if (ttyfd >= 0)
		ioctl(ttyfd, TIOCSETD, &ldisc);
# endif
	our_pgrp = getpgid(0);
	sigchld_caught = 0;
# ifdef USE_SIGACT
	sigemptyset(&sm_default);
	sigemptyset(&sm_sigchld);
	sigaddset(&sm_sigchld, SIGCHLD);
# else
	sm_default = 0;
	sm_sigchld = sigmask(SIGCHLD);
	_TRACE(5, ("j_init: sm_sigchld == 0x%x", sm_sigchld));
# endif
#endif
#ifndef JOBS
# ifdef USE_SIGACT
	sigaction(SIGCHLD, &Sigact_dfl, NULL);
# else
#   ifdef _SYSV
	signal(SIGCHLD, SIG_DFL);	/* necessary ??? */
#   endif
# endif
#endif
}

/* job cleanup before shell exit */
void
j_exit()
{
	register Proc *j;
	int killed = 0;

#ifdef JOBS
	/* kill stopped jobs */
	for (j = procs; j != NULL; j = j->next)
		if (j->state == JSTOP) {
			killed ++;
			killpg(j->pgrp, SIGHUP);
			killpg(j->pgrp, SIGCONT);
		}
	if (killed)
		sleep(1);
#endif
	j_notify();

#ifdef JOBS
	if (flag[FMONITOR]) {
		flag[FMONITOR] = 0;
		j_change();
	}
#endif
}

#ifdef JOBS
/* turn job control on or off according to flag[FMONITOR] */
void
j_change()
{
#ifdef USE_SIGACT
	static struct sigaction old_tstp, old_ttin, old_ttou;
#else
	static handler_t old_tstp, old_ttin, old_ttou;
#endif
	if (flag[FMONITOR]) {
		if (ttyfd < 0) {
			flag[FMONITOR] = 0;
			shellf("job control requires tty\n");
			return;
		}
#ifdef USE_SIGACT
		Sigact.sa_handler = j_sigchld;
		sigemptyset(&Sigact.sa_mask);
		Sigact.sa_flags = SA_RESTART;
		sigaction(SIGCHLD, &Sigact, NULL);
		Sigact.sa_flags = 0;
		sigtraps[SIGCHLD].sig_dfl = 1; /* restore on fork */
		sigaction(SIGTSTP, &Sigact_ign, &old_tstp);
		sigtraps[SIGTSTP].sig_dfl = 1;
		sigaction(SIGTTIN, &Sigact_ign, &old_ttin);
		sigtraps[SIGTTIN].sig_dfl = 1;
		sigaction(SIGTTOU, &Sigact_ign, &old_ttou);
		sigtraps[SIGTTOU].sig_dfl = 1;
#else
		(void) signal(SIGCHLD, j_sigchld);
		sigtraps[SIGCHLD].sig_dfl = 1; /* restore on fork */
		old_tstp = signal(SIGTSTP, SIG_IGN);
		sigtraps[SIGTSTP].sig_dfl = 1;
		old_ttin = signal(SIGTTIN, SIG_IGN);
		sigtraps[SIGTTIN].sig_dfl = 1;
		old_ttou = signal(SIGTTOU, SIG_IGN);
		sigtraps[SIGTTOU].sig_dfl = 1;
#endif
#ifdef USE_SIGACT
		sigprocmask(SIG_SETMASK, &sm_default, NULL);
#else
		sigsetmask(sm_default);
#endif
		tcsetpgrp(ttyfd, our_pgrp);
	} else {
#ifdef USE_SIGACT
		sigaction(SIGCHLD, &Sigact_dfl, NULL);
		sigaction(SIGTSTP, &old_tstp, NULL);
		sigtraps[SIGTSTP].sig_dfl = 0;
		sigaction(SIGTTIN, &old_ttin, NULL);
		sigtraps[SIGTTIN].sig_dfl = 0;
		sigaction(SIGTTOU, &old_ttou, NULL);
		sigtraps[SIGTTOU].sig_dfl = 0;
#else
		(void) signal(SIGCHLD, SIG_DFL);
		(void) signal(SIGTSTP, old_tstp);
		sigtraps[SIGTSTP].sig_dfl = 0;
		(void) signal(SIGTTIN, old_ttin);
		sigtraps[SIGTTIN].sig_dfl = 0;
		(void) signal(SIGTTOU, old_ttou);
		sigtraps[SIGTTOU].sig_dfl = 0;
#endif
	}
}
#endif

#ifdef amigados
/* stack slots are volatile when vfork_resume is called.... the solution
   is to buffer the parameters in global variables, that are relocated with
   the ix_resident() call (so the solution is even reentrant ;-)) */
static struct op *t;
static int flags;

static struct table *o_homedirs;
#endif

/* execute tree in child subprocess */
int
exchild(_t, _flags)
	struct op *_t;
	int _flags;
{
	register int i;
	register Proc *j;
#ifndef amigados
	register struct op *t;
	register int flags;
#endif
	int rv = 0;
	int forksleep;

	flags = _flags;
	t = _t;
#ifdef amigados
	o_homedirs = &homedirs;
#endif

	flags &= ~XFORK;
	if ((flags&XEXEC))
		return execute(t, flags);

	/* get free Proc entry */
	for (j = procs; j != NULL; j = j->next)
		if (j->state == JFREE)
			goto Found;
	j = (Proc*) alloc(sizeof(Proc), APERM);
	j->next = procs;
	j->state = JFREE;
	procs = j;
  Found:

	j->prev = ((flags&XPIPEI)) ? j_lastj : NULL;
	j->proc = j->pgrp = 0;
	j->flags = flags;
	j->job = (flags&XPIPEI) ? j_lastjob : j_newjob();
	snptreef(j->com, sizeof(j->com), "%T", t); /* save proc's command */
	j->com[sizeof(j->com)-1] = '\0';
	j->state = JRUN;

	/* stdio buffer must be flushed and invalidated */
	for (i = 0; i < NUFILE; i++)
		flushshf(i);

	/* create child process */
	forksleep = 0;
#ifdef JOBS
	/* don't allow SIGCHLD until we are ready */

#ifdef USE_SIGACT
	sigprocmask(SIG_SETMASK, &sm_sigchld, NULL);
# else
	sigsetmask(sm_sigchld);
# endif
#endif
#ifdef amigados
/* use vfork () instead of fork(), and later try to do fork() `by hand'... */
#define fork vfork
#endif
	while ((i = fork()) < 0 && errno == EAGAIN && forksleep < 32) {
		if (forksleep) {
			sleep(forksleep);
			forksleep <<= 1;
		} else
			forksleep = 1;
	}
	if (i < 0) {
		j->state = JFREE;
		errorf("cannot fork - try again\n");
	}

	j->proc = (i != 0) ? i : getpid();

#ifdef amigados
	if (i)
#endif
#ifdef JOBS
	/* job control set up */
	if (flag[FMONITOR] && !(flags&XXCOM))
	{
	  j->pgrp = !(flags&XPIPEI) ? j->proc : j_lastj->pgrp;
	  /* do in both parent and child to avoid fork race condition */
	  if (!(flags&XBGND))
	    tcsetpgrp(ttyfd, j->pgrp); /* could be trouble */
	  setpgid(j->proc, j->pgrp);
	}
#endif
	j_lastj = j;

	if (i == 0) {		/* child */
#ifdef amigados
		/* you don't really *have* to understand all the magic that's
		   happening here ;-) In short, it duplicates the data/bss
		   segment (without malloc'd data!), re-relocates data-to-data
		   relocs, reinits stdio (into new malloc'd buffers), gets
		   a fresh copy of environ, and that's about it. */

		extern char **_ctype_, **environ;
		extern int sys_nerr;
		extern int SysBase, DOSBase;
		
		/* this re-relocates the data segment */
	        ix_resident (4, get_a4(), dbsize(), __datadata_relocs);

		ix_get_vars2 (7, &_ctype_, &sys_nerr, &SysBase, &DOSBase, &__sF, &environ, &environ);

		e.type = E_NONE;
		ainit (APERM);
		/* don't copy `commands', this is just a hash cache for
		   commands, and the child just searches its paths again
		   if it doesn't find a command there */
		tinit (& commands, APERM);
		/* don't do anything to `builtins'. This table represents
		   shell builtins and is as such static. Using vfork I
		   can't allow the parent shell to quit before the child,
		   so the child can reuse the shared table. But set the
		   area of the table to 0, that way we get an error (or
		   a crash if the child nevertheless tries to manipulate
		   the table */
		builtins.areap = 0;

		/* same applies to lexicals, these are shell keywords */
		lexicals.areap = 0;
		/* although homedirs is hardly used, it's not static,
		   so copy it. */
		tbl_copy (o_homedirs, &homedirs, APERM);

	        ainit (ATEMP);
		blk_copy (e.loc);
		if (e.savefd)
		  {
		    short *old = e.savefd;
		    e.savefd = (short*) alloc(sizeofN(short, NUFILE), ATEMP);
		    bcopy (old, e.savefd, sizeofN(short, NUFILE));
		  }
		/* cancel the previous stdio pointers, they're no longer
		   valid */
		{
		  register int fd;
		  for (fd = 0; fd < NUFILE; fd++)
		    if (shf[fd])
		      {
		        shf[fd] = 0;
		        fopenshf (fd);
		      }
		}

		/* copy the argument tree */
	        t = tcopy (t, ATEMP);
	        e.temps = 0;
	        /* dup any allocated trap command strings */
	        child_dup_traps ();
		/* don't do this past vfork_resume(), as `e' is cached in a
		   register by the compiler.. */
		e.oenv = NULL;

		/* tell the parent it's ok to continue, just as if the child
		   did an exec() or an _exit(). Switch to child's stack. */
	        vfork_resume ();
#else
		e.oenv = NULL;
#endif
		if (flag[FTALKING])
			restoresigs();
		if ((flags&XBGND) && !flag[FMONITOR])
		{
#ifdef USE_SIGACT
		  sigaction(SIGINT, &Sigact_dfl, NULL);
		  sigaction(SIGQUIT, &Sigact_dfl, NULL);
		  if (flag[FTALKING])
		    sigaction(SIGTERM, &Sigact_dfl, NULL);
#else
		  signal(SIGINT, SIG_IGN);
		  signal(SIGQUIT, SIG_IGN);
		  if (flag[FTALKING])
		    signal(SIGTERM, SIG_DFL);
#endif
			if (!(flags&XPIPEI)) {
				i = open("/dev/null", 0);
				(void) dup2(i, 0);
				close(i);
			}
		}
#ifdef amigados
		/* don't need them, and don't own them, so toss them ;-)) */
		procs = 0;
#else
		for (j = procs; j != NULL; j = j->next)
			j->state = JFREE;
#endif
		ttyfd = -1;
#ifdef JOBS
		/* is this needed in the child? */
# ifdef USE_SIGACT
		sigprocmask(SIG_SETMASK, &sm_default, NULL);
# else
		sigsetmask(sm_default);
# endif
#endif
		flag[FMONITOR] = flag[FTALKING] = 0;
		cleartraps();
		execute(t, flags|XEXEC); /* no return */
		/* NOTREACHED */
	}

	/* shell (parent) stuff */
	if ((flags&XBGND)) { /* async statement */
		async = j->proc;
		j_previous = j_current;
		j_current = j->job;
		if (flag[FTALKING])
			j_print(j);
	}
#ifdef JOBS
# ifdef USE_SIGACT
	sigprocmask(SIG_SETMASK, &sm_default, NULL);
# else
	sigsetmask(sm_default);
# endif
#endif
	if (!(flags&XBGND))
	{ 		/* sync statement */
		if (!(flags&XPIPE))
			rv = j_waitj(j, 0);
	}

	return rv;
}

/* wait for last job: pipeline or $() sub-process */
int
waitlast()
{
	return j_waitj(j_lastj, 0);
}

/* wait for job to complete or change state */
static int
j_waitj(aj, intr)
	Proc *aj;
	int intr;		/* interruptable */
{
	register Proc *j;
	int rv = 1;
	int ttysig = 0;

#ifdef JOBS
	if (flag[FMONITOR])
	{
# ifdef USE_SIGACT
	  sigprocmask(SIG_SETMASK, &sm_sigchld, NULL);
# else
	  _TRACE(5, ("j_waitj: sigsetmask(sm_sigchld==0x%x)", sm_sigchld));
	  sigsetmask(sm_sigchld);
# endif
	}
#endif
	/* wait for all members of pipeline */
	for (j = aj; j != NULL; j = j->prev) {
		/* wait for job to finish, stop, or ^C of built-in wait */
		while (j->state == JRUN) {
#ifdef JOBS
			if (flag[FMONITOR])
			{
			  /*
			   * 91-07-07 <sjg@sun0>
			   * we don't want to wait for a signal
			   * that has already arrived :-)
			   */
			  if (!sigchld_caught)
			  {
# ifdef USE_SIGACT
			    sigsuspend(&sm_default);
# else
			    _TRACE(4, ("j_waitj: sigpause(%d), sigchld_caught==%d", sm_default, sigchld_caught));
			    sigpause(sm_default);
			    _TRACE(4, ("j_waitj: sigpause() returned %d, sigchld_caught==%d", errno, sigchld_caught));
# endif /* USE_SIGACT */
			  }
			}
			else
#endif /* JOBS */
				j_sigchld(0);
			/*
			 * Children to reap
			 */
			if (sigchld_caught)
			  j_reapchld();
			_TRACE(4, ("j_waitj: j->proc==%d, j->com=='%s', j->state==0x%hx, j->status==0x%x, j->notify==%hd", j->proc, j->com, j->state, j->status, j->notify));
			
			if (sigtraps[SIGINT].set && intr)
				goto Break;
		}
		if (j->state == JEXIT) { /* exit termination */
			if (!(j->flags&XPIPEO))
				rv = WEXITSTATUS(j->status);
			j->notify = 0;
		} else
		if (j->state == JSIGNAL) { /* signalled to death */
			if (!(j->flags&XPIPEO))
				rv = 0x80 + WTERMSIG(j->status);
			if (WTERMSIG(j->status) == SIGINT ||
			    (WTERMSIG(j->status) == SIGPIPE &&
			     (j->flags&XPIPEO)))
				j->notify = 0;
			if (WTERMSIG(j->status) == SIGINT ||
			    WTERMSIG(j->status) == SIGQUIT)
				ttysig = 1;
		} else
#ifdef JOBS
		if (j->state == JSTOP)
			if (WSTOPSIG(j->status) == SIGTSTP)
				ttysig = 1;
#else
		;
#endif
	}

	/* compute total child time for time statement */
	for (j = aj; j != NULL; j = j->prev)
		j_utime += j->utime, j_stime += j->stime;

	/* find new current job */
#ifdef JOBS
	if (aj->state == JSTOP) {
		j_previous = j_current;
		j_current = aj->job;
	} else {
#else
	if (1) {
#endif
		int hijob = 0;

		/* todo: this needs to be done in j_notify */
		/* todo: figure out what to do with j_previous */
		j_current = 0;
		for (j = procs; j != NULL; j = j->next)
			if ((j->state == JRUN || j->state == JSTOP)
			    && j->job > hijob) {
				hijob = j->job;
				j_current = j->job;
			}
	}

  Break:
#ifdef JOBS
	if (flag[FMONITOR])
	{
	  /* reset shell job control state */
# ifdef USE_SIGACT
	  sigprocmask(SIG_SETMASK, &sm_default, NULL);
# else
	  sigsetmask(sm_default);
# endif
	  tcsetpgrp(ttyfd, our_pgrp);
	}
#endif
	if (ttysig)
		fputc('\n', shlout);
	j_notify();

	return rv;
}

/* SIGCHLD handler to reap children */
/*
 * 91-07-07 <sjg@sun0>
 * On the Sun SS2 this appears to get called
 * too quickly!
 * So just record the event and process later.
 */
static void
j_sigchld(sig)
	int sig;
{
	sigchld_caught++;	/* acknowledge it */
}

/*
 * 91-07-07 <sjg@sun0>
 * This now gets called when j_sigchld()
 * has recorded some signals...
 */
j_reapchld()
{
	struct tms t0, t1;
#if defined(JOBS)
# ifdef USE_SIGACT
	sigset_t	sm_old;

	sigprocmask(SIG_SETMASK, NULL, &sm_old);
# else
	int sm_old;

	sm_old = sigblock(0);	/* just get current mask */
	_TRACE(5, ("j_reapchld: sm_old==0x%x, sigchld_caught==%d", sm_old, sigchld_caught));
# endif
#endif
	(void) times(&t0);

	do {
		register Proc *j;
		int pid;
		WAIT_T status;
#ifdef JOBS
		if (flag[FMONITOR])
			pid = waitpid(-1, &status, (WNOHANG|WUNTRACED));
		else
#endif
			pid = wait(&status);
		if (pid < 0 && errno == ECHILD)
		{
		  /* no children - what are we doing here? */
		  _TRACE(5, ("j_reapchld: no children"));
		  sigchld_caught = 0;
		  break;
		}
		if (pid <= 0)	/* return if would block (0) ... */
		  {
			_TRACE(5, ("j_reapchld: would block"));
			break;	/* ... or no children or interrupted (-1) */
		  }
		(void) times(&t1);

		_TRACE(5, ("j_reapchld: looking for pid==%d", pid));

		for (j = procs; j != NULL; j = j->next)
		{
		  _TRACE(6, ("j_reapchld: j->proc==%d, j->com=='%s', j->state==0x%hx, j->status==0x%x, j->notify==%hd", j->proc, j->com, j->state, j->status, j->notify));
		  if (j->state != JFREE && j->proc == pid)
		    goto Found;
		}
		_TRACE(5, ("j_reapchld: did not find pid==%d", pid));
		continue;
	  Found:
		_TRACE(5, ("j_reapchld: found pid==%d", pid));
		j->notify = 1;
		j->status = status;
#ifdef JOBS
		if (WIFSTOPPED(status))
			j->state = JSTOP;
		else
#endif
		if (WIFEXITED(status))
			j->state = JEXIT;
		else
		if (WIFSIGNALED(status))
			j->state = JSIGNAL;

		/* compute child's time */
		/* todo: what does a stopped job do? */
		j->utime = t1.tms_cutime - t0.tms_cutime;
		j->stime = t1.tms_cstime - t0.tms_cstime;
		t0 = t1;
#ifdef JOBS
# ifdef USE_SIGACT
		sigprocmask(SIG_BLOCK, &sm_sigchld, NULL);
# else
		sigblock(sm_sigchld);
# endif
#endif
		if (--sigchld_caught < 0) /* reduce the count */
		  sigchld_caught = 0;
#ifdef JOBS
# ifdef USE_SIGACT
		sigprocmask(SIG_SETMASK, &sm_old, NULL);
# else
		_TRACE(5, ("j_reapchld: j->proc==%d, j->com=='%s', j->state==0x%hx, j->status==0x%x, j->notify==%hd", j->proc, j->com, j->state, j->status, j->notify));
		sigsetmask(sm_old); /* restore old mask */
# endif
#endif
		
#ifdef JOBS
	} while (flag[FMONITOR]);
#else
	} while (0);		/* only once if wait()ing */
#endif
/*
 * this should be safe
 */
#if defined(_SYSV) && !defined(JOBS) && !defined(USE_SIGACT)
	signal(SIGCHLD, j_sigchld);
#if 0
	/* why was this here??? */
	signal(SIGCLD, SIG_DFL);
#endif
#endif
}

j_reap()
{
  if (sigchld_caught)
    j_reapchld();
/*
 * now done in j_reapchld()
 */
#if 0 && defined(_SYSV) && !defined(JOBS) && !defined(USE_SIGACT)
	signal(SIGCHLD, j_sigchld);
	signal(SIGCLD, SIG_DFL);
#endif
	return(0);
}

/* wait for child, interruptable */
int
waitfor(job)
	int job;
{
	register Proc *j;

	if (job == 0 && j_current == 0)
		errorf("no current job\n");
	j = j_search((job == 0) ? j_current : job);
	if (j == NULL)
		errorf("no such job: %d\n", job);
	if (flag[FTALKING])
		j_print(j);
	if (e.interactive) {	/* flush stdout, shlout */
		fflush(shf[1]);
		fflush(shf[2]);
	}
	return j_waitj(j, 1);
}

/* kill (built-in) a job */
void
j_kill(job, sig)
	int job;
	int sig;
{
	register Proc *j;

	j = j_search(job);
	if (j == NULL)
		errorf("cannot find job\n");
	if (j->pgrp == 0) {	/* !flag[FMONITOR] */
		if (kill(j->proc, sig) < 0) /* todo: all member of pipeline */
			errorf("kill: %s\n", strerror(errno));
#ifdef JOBS
	} else {
		if (sig == SIGTERM || sig == SIGHUP)
			(void) killpg(j->pgrp, SIGCONT);
		if (killpg(j->pgrp, sig) < 0)
			errorf("killpg: %s\n", strerror(errno));
#endif
	}
}

#ifdef JOBS

/* fg and bg built-ins */
int
j_resume(job, bg)
	int job;
	int bg;
{
	register Proc *j; 
	
	j = j_search((job == 0) ? j_current : job);
	if (j == NULL)
		errorf("cannot find job\n", job);
	if (j->pgrp == 0)
		errorf("job not job-controlled\n");

	j->state = JRUN;
	j_print(j);
	flushshf(2);

	if (!bg)
  		tcsetpgrp(ttyfd, j->pgrp); /* attach shell to job */
	if (killpg(j->pgrp, SIGCONT) < 0)
		errorf("cannot continue job %%%d\n", job);
	if (!bg)
		return j_waitj(j, 0);
	return 0;
}

#endif

/* list jobs for jobs built-in */
void
j_jobs()
{
	register Proc *j; 

	for (j = procs; j != NULL; j = j->next)
		if (j->state != JFREE)
			j_print(j);
}

/* list jobs for top-level notification */
void
j_notify()
{
	register Proc *j; 

	/*
	 * since reaping is no longer done in the signal handler
	 * we had better try here...
	 */
	if (sigchld_caught)
	  j_reapchld();
	
	for (j = procs; j != NULL; j = j->next) {
		if (j->state == JEXIT && !flag[FTALKING])
			j->notify = 0;
		if (j->state != JFREE && j->notify)
			j_print(j);
		if (j->state == JEXIT || j->state == JSIGNAL)
			j->state = JFREE;
		j->notify = 0;
	}
}

static void
j_print(j)
	register Proc *j;
{
	char buf [64], *s = buf;

	switch (j->state) {
	  case JRUN:
		s = "Running";
		break;

#ifdef JOBS
	  case JSTOP:
		strcpy(buf, "Stopped ");
		s = strchr(sigtraps[WSTOPSIG(j->status)].mess, '(');
		if (s != NULL)
			strcat(buf, s);
		s = buf;
		break;
#endif

	  case JEXIT: {
		int rv;
		rv = WEXITSTATUS(j->status);
		sprintf(buf, "Done (%d)", rv);
		if (rv == 0)
			*strchr(buf, '(') = 0;
		j->state = JFREE;
		} break;

	  case JSIGNAL: {
		int sig = WTERMSIG(j->status);
		char *n = sigtraps[sig].mess;
		if (n != NULL)
			sprintf(buf, "%s", n);
		else
			sprintf(buf, "Signal %d", sig);
		if (WIFCORED(j->status))
			strcat(buf, " - core dumped");
		j->state = JFREE;
		} break;

	  default:
		s = "Hideous job state";
		j->state = JFREE;
		break;
	}
	shellf("%%%-2d%c %5d %-20s %s%s\n", j->job,
	       (j_current==j->job) ? '+' : (j_previous==j->job) ? '-' : ' ',
	       j->proc, s, j->com, (j->flags&XPIPEO) ? "|" : "");
}

/* convert % sequence to job number */
int
j_lookup(cp)
	char *cp;
{
	register Proc *j;
	int len, job = 0;

	if (*cp == '%')		/* leading % is optional */
		cp++;
	switch (*cp) {
	  case '\0':
	  case '+':
		job = j_current;
		break;

	  case '-':
		job = j_previous;
		break;

	  case '0': case '1': case '2': case '3': case '4':
	  case '5': case '6': case '7': case '8': case '9': 
		job = atoi(cp);
		break;

	  case '?':		/* %?string */
		for (j = procs; j != NULL; j = j->next)
			if (j->state != JFREE && strstr(j->com, cp+1) != NULL)
				job = j->job;
		break;

	  default:		/* %string */
		len = strlen(cp);
		for (j = procs; j != NULL; j = j->next)
			if (j->state != JFREE && strncmp(cp, j->com, len) == 0)
				job = j->job;
		break;
	}
	if (job == 0)
		errorf("%s: no such job\n", cp);
	return job;
}

/* are any stopped jobs ? */
#ifdef JOBS
int
j_stopped()
{
	register Proc *j; 

	for (j = procs; j != NULL; j = j->next)
		if (j->state == JSTOP)
			return 1;
	return 0;
}
#endif

/* create new job number */
static int
j_newjob()
{
	register Proc *j; 
	register int max = 0;
	
	j_lastjob ++;
	for (j = procs; j != NULL; j = j->next)
		if (j->state != JFREE && j->job)
			if (j->job > max)
				max = j->job;
	if (j_lastjob > max)
		j_lastjob = max + 1;
	return j_lastjob;
}

/* search for job by job number */
static Proc *
j_search(job)
	int job;
{
	register Proc *j;

	for (j = procs; j != NULL; j = j->next)
		if (j->state != JFREE && job == j->job && !(j->flags&XPIPEO))
			return j;
	return NULL;
}

