/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*          Copyright (c) 1982-2012 AT&T Intellectual Property          *
*                      and is licensed under the                       *
*                 Eclipse Public License, Version 1.0                  *
*                    by AT&T Intellectual Property                     *
*                                                                      *
*                A copy of the License is available at                 *
*          http://www.eclipse.org/org/documents/epl-v10.html           *
*         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                  David Korn <dgk@research.att.com>                   *
*                                                                      *
***********************************************************************/
#pragma prototyped
/*
 *  Job control for UNIX Shell
 *
 *   David Korn
 *   AT&T Labs
 *
 *  Written October, 1982
 *  Rewritten April, 1988
 *  Revised January, 1992
 */

#include	"defs.h"
#include	<wait.h>
#include	"io.h"
#include	"jobs.h"
#include	"history.h"

#if !defined(WCONTINUED) || !defined(WIFCONTINUED)
#   undef  WCONTINUED
#   define WCONTINUED	0
#   undef  WIFCONTINUED
#   define WIFCONTINUED(wstat)	(0)
#endif

#define	NJOB_SAVELIST	4

/*
 * temporary hack to get W* macros to work
 */
#undef wait
#define wait    ______wait
/*
 * This struct saves a link list of processes that have non-zero exit
 * status, have had $! saved, but haven't been waited for
 */
struct jobsave
{
	struct jobsave	*next;
	pid_t		pid;
	unsigned short	exitval;
};

static struct jobsave *job_savelist;
static int njob_savelist;
static struct process *pwfg;
static int jobfork;

pid_t	pid_fromstring(char *str)
{
	pid_t	pid;
	char	*last;
	errno = 0;
	if(sizeof(pid)==sizeof(Sflong_t))
		pid = (pid_t)strtoll(str, &last, 10);
	else
		pid = (pid_t)strtol(str, &last, 10);
	if(errno==ERANGE || *last)
		errormsg(SH_DICT,ERROR_exit(1),"%s: invalid process id",str);
	return(pid);
}

static void init_savelist(void)
{
	register struct jobsave *jp;
	while(njob_savelist < NJOB_SAVELIST)
	{
		jp = newof(0,struct jobsave,1,0);
		jp->next = job_savelist;
		job_savelist = jp;
		njob_savelist++;
	}
}

struct back_save
{
	int		count;
	struct jobsave	*list;
	struct back_save *prev;
};

#define BYTE(n)		(((n)+CHAR_BIT-1)/CHAR_BIT)
#define MAXMSG	25
#define SH_STOPSIG	(SH_EXITSIG<<1)

#ifdef VSUSP
#   ifndef CNSUSP
#	ifdef _POSIX_VDISABLE
#	   define CNSUSP	_POSIX_VDISABLE
#	else
#	   define CNSUSP	0
#	endif /* _POSIX_VDISABLE */
#   endif /* CNSUSP */
#   ifndef CSWTCH
#	ifdef CSUSP
#	    define CSWTCH	CSUSP
#	else
#	    define CSWTCH	('z'&037)
#	endif /* CSUSP */
#   endif /* CSWTCH */
#endif /* VSUSP */

/* Process states */
#define P_EXITSAVE	01
#define P_STOPPED	02
#define P_NOTIFY	04
#define P_SIGNALLED	010
#define P_STTY		020
#define P_DONE		040
#define P_COREDUMP	0100
#define P_DISOWN	0200
#define P_FG		0400
#ifdef SHOPT_BGX
#define P_BG		01000
#endif /* SHOPT_BGX */

static int		job_chksave(pid_t);
static struct process	*job_bypid(pid_t);
static struct process	*job_byjid(int);
static char		*job_sigmsg(int);
static int		job_alloc(void);
static void		job_free(int);
static struct process	*job_unpost(struct process*,int);
static void		job_unlink(struct process*);
static void		job_prmsg(struct process*);
static struct process	*freelist;
static char		beenhere;
static char		possible;
static struct process	dummy;
static char		by_number;
static Sfio_t		*outfile;
static pid_t		lastpid;
static struct back_save	bck;

#ifdef JOBS
    static void			job_set(struct process*);
    static void			job_reset(struct process*);
    static void			job_waitsafe(int);
    static struct process	*job_byname(char*);
    static struct process	*job_bystring(char*);
    static struct termios	my_stty;  /* terminal state for shell */
    static char			*job_string;
#else
    extern const char		e_coredump[];
#endif /* JOBS */

#ifdef SIGTSTP
    static void		job_unstop(struct process*);
    static void		job_fgrp(struct process*, int);
#   ifndef _lib_tcgetpgrp
#	ifdef TIOCGPGRP
	   static int _i_;
#	   define tcgetpgrp(a) (ioctl(a, TIOCGPGRP, &_i_)>=0?_i_:-1)	
#	endif /* TIOCGPGRP */
	int tcsetpgrp(int fd,pid_t pgrp)
	{
		int pgid = pgrp;
#		ifdef TIOCGPGRP
			return(ioctl(fd, TIOCSPGRP, &pgid));	
#		else
			return(-1);
#		endif /* TIOCGPGRP */
	}
#   endif /* _lib_tcgetpgrp */
#else
#   define job_unstop(pw)
#   undef CNSUSP
#endif /* SIGTSTP */

#ifndef OTTYDISC
#   undef NTTYDISC
#endif /* OTTYDISC */

#ifdef JOBS

typedef int (*Waitevent_f)(int,long,int);

#ifdef SHOPT_BGX
void job_chldtrap(Shell_t *shp, const char *trap, int unpost)
{
	register struct process *pw,*pwnext;
	pid_t bckpid;
	int oldexit,trapnote;
	job_lock();
	shp->sigflag[SIGCHLD] &= ~SH_SIGTRAP;
	trapnote = shp->trapnote;
	shp->trapnote = 0;
	for(pw=job.pwlist;pw;pw=pwnext)
	{
		pwnext = pw->p_nxtjob;
		if((pw->p_flag&(P_BG|P_DONE)) != (P_BG|P_DONE))
			continue;
		pw->p_flag &= ~P_BG;
		bckpid = shp->bckpid;
		oldexit = shp->savexit;
		shp->bckpid = pw->p_pid;
		shp->savexit = pw->p_exit;
		if(pw->p_flag&P_SIGNALLED)
			shp->savexit |= SH_EXITSIG;
		sh_trap(trap,0);
		if(pw->p_pid==bckpid && unpost)
			job_unpost(pw,0);
		shp->savexit = oldexit;
		shp->bckpid = bckpid;
	}
	shp->trapnote = trapnote;
	job_unlock();
}
#endif /* SHOPT_BGX */

/*
 * return next on link list of jobsave free list
 */
static struct jobsave *jobsave_create(pid_t pid)
{
	register struct jobsave *jp = job_savelist;
	job_chksave(pid);
	if(++bck.count > shgd->lim.child_max)
		job_chksave(0);
	if(jp)
	{
		njob_savelist--;
		job_savelist = jp->next;
	}
	else
		jp = newof(0,struct jobsave,1,0);
	if(jp)
	{
		jp->pid = pid;
		jp->next = bck.list;
		bck.list = jp;
		jp->exitval = 0;
	}
	return(jp);
}

#if SHOPT_COSHELL
    pid_t sh_copid(struct cosh *csp)
    {
	return(COPID_BIT|(csp->id<<16)|csp->cojob->id);
    }


    char  *sh_pid2str(Shell_t *shp,pid_t pid)
    {
	struct cosh  *csp=0;
	if(pid&COPID_BIT)
	{
		int id = (pid>>16) &0x3f;
		for(csp=job.colist; csp; csp = csp->next)
		{
			if(csp->id == id)
				break;
		}
	}
	if(csp)
		sfprintf(shp->strbuf,"%s.%d%c",csp->name,pid&0xff,0);
	else
		sfprintf(shp->strbuf,"%d%c",pid,0);
	return(sfstruse(shp->strbuf));
    }

    int job_cowalk(int (*fun)(struct process*,int),int arg,char *name)
    {
	Shell_t		*shp = sh_getinterp();
	struct cosh	*csp;
	struct process	*pw,*pwnext;
	pid_t		val;
	int		n,r=0;
	char		*cp = strchr(name,'.');
	if(!cp)
		n = strlen(name);
	else
		n = cp-name;
	for(csp=(struct cosh*)job.colist;csp;csp=csp->next)
	{
		if(memcmp(name,csp->name,n)==0 && csp->name[n]==0)
			break;
	}
	if(!csp)
		errormsg(SH_DICT,ERROR_exit(1),e_jobusage,name);
	if(cp)
	{
		n = pid_fromstring(cp+1);
		val = (csp->id<<16)|n|COPID_BIT;
	}
	job_reap(SIGCHLD);
	for(n=0,pw=job.pwlist; pw; pw=pwnext)
	{
		pwnext = pw->p_nxtjob;
		if((cp && val==pw->p_pid) || (pw->p_cojob && pw->p_cojob->local==(void*)csp))
		{
			if(fun)
			{
				if(pw->p_flag&P_DONE)
					continue;
				r |= (*fun)(pw,arg);
			}
			else
				job_wait(-pw->p_pid);
			n++;
		}
	}
	if(!n)
		shp->exitval = fun?1:ERROR_NOENT;
	else if(fun)
		shp->exitval = r;
	return(r);
    }

#endif /* SHOPT_COSHELL */

/*
 * Reap one job
 * When called with sig==0, it does a blocking wait
 */
int job_reap(register int sig)
{
	Shell_t *shp = sh_getinterp();
	register pid_t pid;
	register struct process *pw;
	struct process *px;
	register int flags;
	struct jobsave *jp;
	int nochild=0, oerrno, wstat;
	Waitevent_f waitevent = shp->gd->waitevent;
	static int wcontinued = WCONTINUED;
#if SHOPT_COSHELL
	Cojob_t		*cjp;
	int		cojobs;
	long		cotimeout = sig?0:-1;
	for(pw=job.pwlist;pw;pw=pw->p_nxtjob)
	{
		if(pw->p_cojob && !(pw->p_flag&P_DONE))
			break;
	}
	cojobs = (pw!=0);
	pid = 0;
#endif /* SHOPT_COSHELL */
	if (vmbusy())
	{
		errormsg(SH_DICT,ERROR_warn(0),"vmbusy() inside job_reap() -- should not happen");
		if (getenv("_AST_KSH_VMBUSY_ABORT"))
			abort();
	}
#ifdef DEBUG
	if(sfprintf(sfstderr,"ksh: job line %4d: reap pid=%d critical=%d signal=%d\n",__LINE__,getpid(),job.in_critical,sig) <=0)
		write(2,"waitsafe\n",9);
	sfsync(sfstderr);
#endif /* DEBUG */
	job.savesig = 0;
	if(sig)
		flags = WNOHANG|WUNTRACED|wcontinued;
	else
		flags = WUNTRACED|wcontinued;
	shp->gd->waitevent = 0;
	oerrno = errno;
	while(1)
	{
		if(!(flags&WNOHANG) && !sh.intrap && job.pwlist)
		{
			sh_onstate(SH_TTYWAIT);
			if(waitevent && (*waitevent)(-1,-1L,0))
				flags |= WNOHANG;
		}
#if SHOPT_COSHELL
		if(cojobs)
		{
			if(cjp = cowait(0,0,cotimeout))
			{
				struct cosh *csp;
				csp = (struct cosh*)(cjp->coshell->data);
				csp->cojob = cjp;
				pid = sh_copid(csp);
				if(cjp->status < 256)
					wstat = cjp->status <<8;
				else
					wstat = cjp->status-256;
				cotimeout = 0;
				goto cojob;
			}
			else if(copending(0)==0)
				cojobs = 0;
			cotimeout = 0;
		}
#endif /* SHOPT_COSHELL */
		pid = waitpid((pid_t)-1,&wstat,flags);
		sh_offstate(SH_TTYWAIT);
#if SHOPT_COSHELL
	cojob:
#endif /* SHOPT_COSHELL */

		/*
		 * some systems (linux 2.6) may return EINVAL
		 * when there are no continued children
		 */

		if (pid<0 && errno==EINVAL && (flags&WCONTINUED))
			pid = waitpid((pid_t)-1,&wstat,flags&=~WCONTINUED);
		sh_sigcheck(shp);
		if(pid<0 && errno==EINTR && (sig||job.savesig))
		{
			errno = 0;
			continue;
		}
		if(pid<=0)
			break;
		if(wstat==0)
			job_chksave(pid);
		flags |= WNOHANG;
		job.waitsafe++;
		jp = 0;
		lastpid = pid;
		if(!(pw=job_bypid(pid)))
		{
#ifdef DEBUG
			sfprintf(sfstderr,"ksh: job line %4d: reap pid=%d critical=%d unknown job pid=%d pw=%x\n",__LINE__,getpid(),job.in_critical,pid,pw);
#endif /* DEBUG */
			if (WIFCONTINUED(wstat) && wcontinued)
				continue;
			pw = &dummy;
			pw->p_exit = 0;
			pw->p_pgrp = 0;
			pw->p_exitmin = 0;
			if(job.toclear)
				job_clear();
			jp = jobsave_create(pid);
			pw->p_flag = 0;
			lastpid = pw->p_pid = pid;
			px = 0;
			if(jp && WIFSTOPPED(wstat))
			{
				jp->exitval = SH_STOPSIG;
				continue;
			}
		}
#ifdef SIGTSTP
		else
			px=job_byjid(pw->p_job);
		if (WIFCONTINUED(wstat) && wcontinued)
			pw->p_flag &= ~(P_NOTIFY|P_SIGNALLED|P_STOPPED);
		else if(WIFSTOPPED(wstat))
		{
			pw->p_flag |= (P_NOTIFY|P_SIGNALLED|P_STOPPED);
			pw->p_exit = WSTOPSIG(wstat);
			if(pw->p_pgrp && pw->p_pgrp==job.curpgid && sh_isstate(SH_STOPOK))
				kill(getpid(),pw->p_exit); 
			if(px)
			{
				/* move to top of job list */
				job_unlink(px);
				px->p_nxtjob = job.pwlist;
				job.pwlist = px;
			}
			continue;
		}
		else
#endif /* SIGTSTP */
		{
			/* check for coprocess completion */
			if(pid==shp->cpid)
			{
				sh_close(sh.coutpipe);
				sh_close(sh.cpipe[1]);
				sh.cpipe[1] = -1;
				sh.coutpipe = -1;
			}
			else if(shp->subshell)
				sh_subjobcheck(pid);

			pw->p_flag &= ~(P_STOPPED|P_SIGNALLED);
			if (WIFSIGNALED(wstat))
			{
				pw->p_flag |= (P_DONE|P_NOTIFY|P_SIGNALLED);
				if (WTERMCORE(wstat))
					pw->p_flag |= P_COREDUMP;
				pw->p_exit = WTERMSIG(wstat);
				/* if process in current jobs terminates from
				 * an interrupt, propogate to parent shell
				 */
				if(pw->p_pgrp && pw->p_pgrp==job.curpgid && pw->p_exit==SIGINT && sh_isstate(SH_STOPOK))
				{
					pw->p_flag &= ~P_NOTIFY;
					sh_offstate(SH_STOPOK);
					kill(getpid(),SIGINT); 
					sh_onstate(SH_STOPOK);
				}
			}
			else
			{
				pw->p_flag |= (P_DONE|P_NOTIFY);
				pw->p_exit =  pw->p_exitmin;
				if(WEXITSTATUS(wstat) > pw->p_exitmin)
					pw->p_exit = WEXITSTATUS(wstat);
			}
#ifdef SHOPT_BGX
			if((pw->p_flag&P_DONE) && (pw->p_flag&P_BG))
			{
				job.numbjob--;
				if(shp->st.trapcom[SIGCHLD])
				{
					shp->sigflag[SIGCHLD] |= SH_SIGTRAP;
					if(sig==0)
						job_chldtrap(shp,shp->st.trapcom[SIGCHLD],0);
					else
						shp->trapnote |= SH_SIGTRAP;
				}
				else
					pw->p_flag &= ~P_BG;
			}
#endif /* SHOPT_BGX */
			if(pw->p_pgrp==0)
				pw->p_flag &= ~P_NOTIFY;
		}
		if(jp && pw== &dummy)
		{
			jp->exitval = pw->p_exit;
			if(pw->p_flag&P_SIGNALLED)
				jp->exitval |= SH_EXITSIG;
		}
#ifdef DEBUG
		sfprintf(sfstderr,"ksh: job line %4d: reap pid=%d critical=%d job %d with pid %d flags=%o complete with status=%x exit=%d\n",__LINE__,getpid(),job.in_critical,pw->p_job,pid,pw->p_flag,wstat,pw->p_exit);
		sfsync(sfstderr);
#endif /* DEBUG*/
		/* only top-level process in job should have notify set */
		if(px && pw != px)
			pw->p_flag &= ~P_NOTIFY;
		if(pid==pw->p_fgrp && pid==tcgetpgrp(JOBTTY))
		{
			px = job_byjid((int)pw->p_job);
			for(; px && (px->p_flag&P_DONE); px=px->p_nxtproc);
			if(!px)
				tcsetpgrp(JOBTTY,job.mypid);
		}
#ifndef SHOPT_BGX
		if(!shp->intrap && shp->st.trapcom[SIGCHLD] && pid>0 && (pwfg!=job_bypid(pid)))
		{
			shp->sigflag[SIGCHLD] |= SH_SIGTRAP;
			shp->trapnote |= SH_SIGTRAP;
		}
#endif
	}
	if(errno==ECHILD)
	{
		errno = oerrno;
#ifdef SHOPT_BGX
		job.numbjob = 0;
#endif /* SHOPT_BGX */
		nochild = 1;
	}
	shp->gd->waitevent = waitevent;
	if(sh_isoption(SH_NOTIFY) && sh_isstate(SH_TTYWAIT))
	{
		outfile = sfstderr;
		job_list(pw,JOB_NFLAG|JOB_NLFLAG);
		job_unpost(pw,1);
		sfsync(sfstderr);
	}
	if(sig)
		signal(sig, job_waitsafe);
	return(nochild);
}

/*
 * This is the SIGCLD interrupt routine
 */
static void job_waitsafe(int sig)
{
	if(job.in_critical || vmbusy())
	{
		job.savesig = sig;
		job.waitsafe++;
	}
	else
		job_reap(sig);
}

/*
 * initialize job control if possible
 * if lflag is set the switching driver message will not print
 */
void job_init(Shell_t *shp, int lflag)
{
	register int ntry=0;
	job.fd = JOBTTY;
	signal(SIGCHLD,job_waitsafe);
#   if defined(SIGCLD) && (SIGCLD!=SIGCHLD)
	signal(SIGCLD,job_waitsafe);
#   endif
	if(njob_savelist < NJOB_SAVELIST)
		init_savelist();
	if(!sh_isoption(SH_INTERACTIVE))
		return;
	/* use new line discipline when available */
#ifdef NTTYDISC
#   ifdef FIOLOOKLD
	if((job.linedisc = ioctl(JOBTTY, FIOLOOKLD, 0)) <0)
#   else
	if(ioctl(JOBTTY,TIOCGETD,&job.linedisc) !=0)
#   endif /* FIOLOOKLD */
		return;
	if(job.linedisc!=NTTYDISC && job.linedisc!=OTTYDISC)
	{
		/* no job control when running with MPX */
#   if SHOPT_VSH
		sh_onoption(SH_VIRAW);
#   endif /* SHOPT_VSH */
		return;
	}
	if(job.linedisc==NTTYDISC)
		job.linedisc = -1;
#endif /* NTTYDISC */

	job.mypgid = getpgrp();
	/* some systems have job control, but not initialized */
	if(job.mypgid<=0)
        {
		/* Get a controlling terminal and set process group */
		/* This should have already been done by rlogin */
                register int fd;
                register char *ttynam;
#ifndef SIGTSTP
                setpgid(0,shp->gd->pid);
#endif /*SIGTSTP */
                if(job.mypgid<0 || !(ttynam=ttyname(JOBTTY)))
                        return;
                close(JOBTTY);
                if((fd = open(ttynam,O_RDWR)) <0)
                        return;
                if(fd!=JOBTTY)
                        sh_iorenumber(shp,fd,JOBTTY);
                job.mypgid = shp->gd->pid;
#ifdef SIGTSTP
                tcsetpgrp(JOBTTY,shp->gd->pid);
                setpgid(0,shp->gd->pid);
#endif /* SIGTSTP */
        }
#ifdef SIGTSTP
	if(possible = (setpgid(0,job.mypgid)>=0) || errno==EPERM)
	{
		/* wait until we are in the foreground */

		while((job.mytgid=tcgetpgrp(JOBTTY)) != job.mypgid)
		{
			if(job.mytgid <= 0)
				return;
			/* Stop this shell until continued */
			signal(SIGTTIN,SIG_DFL);
			kill(shp->gd->pid,SIGTTIN);
			/* resumes here after continue tries again */
			if(ntry++ > IOMAXTRY)
			{
				errormsg(SH_DICT,0,e_no_start);
				return;
			}
		}
	}
#endif /* SIGTTIN */

#ifdef NTTYDISC
	/* set the line discipline */
	if(job.linedisc>=0)
	{
		int linedisc = NTTYDISC;
#   ifdef FIOPUSHLD
		tty_get(JOBTTY,&my_stty);
		if (ioctl(JOBTTY, FIOPOPLD, 0) < 0)
			return;
		if (ioctl(JOBTTY, FIOPUSHLD, &linedisc) < 0)
		{
			ioctl(JOBTTY, FIOPUSHLD, &job.linedisc);
			return;
		}
		tty_set(JOBTTY,TCSANOW,&my_stty);
#   else
		if(ioctl(JOBTTY,TIOCSETD,&linedisc) !=0)
			return;
#   endif /* FIOPUSHLD */
		if(lflag==0)
			errormsg(SH_DICT,0,e_newtty);
		else
			job.linedisc = -1;
	}
#endif /* NTTYDISC */
	if(!possible)
		return;

#ifdef SIGTSTP
	/* make sure that we are a process group leader */
	setpgid(0,shp->gd->pid);
#   if defined(SA_NOCLDSTOP) || defined(SA_NOCLDWAIT)
#   	if !defined(SA_NOCLDSTOP)
#	    define SA_NOCLDSTOP	0
#   	endif
#   	if !defined(SA_NOCLDWAIT)
#	    define SA_NOCLDWAIT	0
#   	endif
	sigflag(SIGCHLD, SA_NOCLDSTOP|SA_NOCLDWAIT, 0);
#   endif /* SA_NOCLDSTOP || SA_NOCLDWAIT */
	signal(SIGTTIN,SIG_IGN);
	signal(SIGTTOU,SIG_IGN);
	/* The shell now handles ^Z */
	signal(SIGTSTP,sh_fault);
	tcsetpgrp(JOBTTY,shp->gd->pid);
#   ifdef CNSUSP
	/* set the switch character */
	tty_get(JOBTTY,&my_stty);
	job.suspend = (unsigned)my_stty.c_cc[VSUSP];
	if(job.suspend == (unsigned char)CNSUSP)
	{
		my_stty.c_cc[VSUSP] = CSWTCH;
		tty_set(JOBTTY,TCSAFLUSH,&my_stty);
	}
#   endif /* CNSUSP */
	sh_onoption(SH_MONITOR);
	job.jobcontrol++;
	job.mypid = shp->gd->pid;
#endif /* SIGTSTP */
	return;
}


/*
 * see if there are any stopped jobs
 * restore tty driver and pgrp
 */
int job_close(Shell_t* shp)
{
	register struct process *pw;
	register int count = 0, running = 0;
	if(possible && !job.jobcontrol)
		return(0);
	else if(!possible && (!sh_isstate(SH_MONITOR) || sh_isstate(SH_FORKED)))
		return(0);
	else if(getpid() != job.mypid)
		return(0);
	job_lock();
	if(!tty_check(0))
		beenhere++;
	for(pw=job.pwlist;pw;pw=pw->p_nxtjob)
	{
		if(!(pw->p_flag&P_STOPPED))
		{
			if(!(pw->p_flag&P_DONE))
				running++;
			continue;
		}
		if(beenhere)
			killpg(pw->p_pgrp,SIGTERM);
		count++;
	}
	if(beenhere++ == 0 && job.pwlist)
	{
		if(count)
		{
			errormsg(SH_DICT,0,e_terminate);
			return(-1);
		}
		else if(running && shp->login_sh)
		{
			errormsg(SH_DICT,0,e_jobsrunning);
			return(-1);
		}
	}
	job_unlock();
#   ifdef SIGTSTP
	if(possible && setpgid(0,job.mypgid)>=0)
		tcsetpgrp(job.fd,job.mypgid);
#   endif /* SIGTSTP */
#   ifdef NTTYDISC
	if(job.linedisc>=0)
	{
		/* restore old line discipline */
#	ifdef FIOPUSHLD
		tty_get(job.fd,&my_stty);
		if (ioctl(job.fd, FIOPOPLD, 0) < 0)
			return(0);
		if (ioctl(job.fd, FIOPUSHLD, &job.linedisc) < 0)
		{
			job.linedisc = NTTYDISC;
			ioctl(job.fd, FIOPUSHLD, &job.linedisc);
			return(0);
		}
		tty_set(job.fd,TCSAFLUSH,&my_stty);
#	else
		if(ioctl(job.fd,TIOCSETD,&job.linedisc) !=0)
			return(0);
#	endif /* FIOPUSHLD */
		errormsg(SH_DICT,0,e_oldtty);
	}
#   endif /* NTTYDISC */
#   ifdef CNSUSP
	if(possible && job.suspend==CNSUSP)
	{
		tty_get(job.fd,&my_stty);
		my_stty.c_cc[VSUSP] = CNSUSP;
		tty_set(job.fd,TCSAFLUSH,&my_stty);
	}
#   endif /* CNSUSP */
	job.jobcontrol = 0;
	return(0);
}

static void job_set(register struct process *pw)
{
	Shell_t *shp = pw->p_shp;
	/* save current terminal state */
	tty_get(job.fd,&my_stty);
	if(pw->p_flag&P_STTY)
	{
		/* restore terminal state for job */
		tty_set(job.fd,TCSAFLUSH,&pw->p_stty);
	}
#ifdef SIGTSTP
	if((pw->p_flag&P_STOPPED) || tcgetpgrp(job.fd) == shp->gd->pid)
		tcsetpgrp(job.fd,pw->p_fgrp);
	/* if job is stopped, resume it in the background */
	if(!shp->forked)
		job_unstop(pw);
	shp->forked = 0;
#endif	/* SIGTSTP */
}

static void job_reset(register struct process *pw)
{
	/* save the terminal state for current job */
#ifdef SIGTSTP
	job_fgrp(pw,tcgetpgrp(job.fd));
	if(tcsetpgrp(job.fd,job.mypid) !=0)
		return;
#endif	/* SIGTSTP */
	/* force the following tty_get() to do a tcgetattr() unless fg */
	if(!(pw->p_flag&P_FG))
		tty_set(-1, 0, NIL(struct termios*));
	if(pw && (pw->p_flag&P_SIGNALLED) && pw->p_exit!=SIGHUP)
	{
		if(tty_get(job.fd,&pw->p_stty) == 0)
			pw->p_flag |= P_STTY;
		/* restore terminal state for job */
		tty_set(job.fd,TCSAFLUSH,&my_stty);
	}
	beenhere = 0;
}
#endif /* JOBS */

/*
 * wait built-in command
 */

void job_bwait(char **jobs)
{
	register char *jp;
	register struct process *pw;
	register pid_t pid;
	if(*jobs==0)
		job_wait((pid_t)-1);
	else while(jp = *jobs++)
	{
#ifdef JOBS
		if(*jp == '%')
		{
			job_lock();
			pw = job_bystring(jp);
			job_unlock();
			if(pw)
				pid = pw->p_pid;
			else
				return;
		}
#   if SHOPT_COSHELL
		else if(isalpha(*jp))
		{
			job_cowalk(NULL,0,jp);
			return;
		}
#   endif /* SHOPT_COSHELL */
		else
#endif /* JOBS */
			pid = pid_fromstring(jp);
		job_wait(-pid);
	}
}

#ifdef JOBS
/*
 * execute function <fun> for each job
 */

int job_walk(Sfio_t *file,int (*fun)(struct process*,int),int arg,char *joblist[])
{
	register struct process *pw;
	register int r = 0;
	register char *jobid, **jobs=joblist;
	register struct process *px;
	job_string = 0;
	outfile = file;
	by_number = 0;
	job_lock();
	pw = job.pwlist;
#if SHOPT_COSHELL
	job_waitsafe(SIGCHLD);
#endif /* SHOPT_COSHELL */
	if(jobs==0)
	{
		/* do all jobs */
		for(;pw;pw=px)
		{
			px = pw->p_nxtjob;
			if(pw->p_env != sh.jobenv)
				continue;
			if((*fun)(pw,arg))
				r = 2;
		}
	}
	else if(*jobs==0)	/* current job */
	{
		/* skip over non-stop jobs */
		while(pw && (pw->p_env!=sh.jobenv || pw->p_pgrp==0))
			pw = pw->p_nxtjob;
		if((*fun)(pw,arg))
			r = 2;
	}
	else while(jobid = *jobs++)
	{
		job_string = jobid;
		if(*jobid==0)
			errormsg(SH_DICT,ERROR_exit(1),e_jobusage,job_string);
#if SHOPT_COSHELL
		if(isalpha(*jobid))
		{
			r = job_cowalk(fun,arg,jobid);
			by_number = 0;
			job_unlock();
			return(r);
		}
#endif /* SHOPT_COSHELL */
		if(*jobid == '%')
			pw = job_bystring(jobid);
		else
		{
			int pid = pid_fromstring(jobid);
			if(!(pw = job_bypid(pid)))
			{
				pw = &dummy;
				pw->p_shp = sh_getinterp();
				pw->p_pid = pid;
				pw->p_pgrp = pid;
			}
			by_number = 1;
		}
		if((*fun)(pw,arg))
			r = 2;
		by_number = 0;
	}
	job_unlock();
	return(r);
}

/*
 * send signal <sig> to background process group if not disowned
 */
int job_terminate(register struct process *pw,register int sig)
{
	if(pw->p_pgrp && !(pw->p_flag&P_DISOWN))
		job_kill(pw,sig);
	return(0);
}

/*
 * list the given job
 * flag JOB_LFLAG for long listing
 * flag JOB_NFLAG for list only jobs marked for notification
 * flag JOB_PFLAG for process id(s) only
 */

int job_list(struct process *pw,register int flag)
{
	Shell_t	*shp = sh_getinterp();
	register struct process *px = pw;
	register int  n;
	register const char *msg;
	register int msize;
	if(!pw || pw->p_job<=0)
		return(1);
	if(pw->p_env != shp->jobenv)
		return(0);
	if((flag&JOB_NFLAG) && (!(px->p_flag&P_NOTIFY)||px->p_pgrp==0))
		return(0);
	if((flag&JOB_PFLAG))
	{
#if SHOPT_COSHELL
		sfprintf(outfile,"%s\n",sh_pid2str(shp,px->p_pgrp?px->p_pgrp:px->p_pid));
#else
		sfprintf(outfile,"%d\n",px->p_pgrp?px->p_pgrp:px->p_pid);
#endif /* SHOPT_COSHELL */
		return(0);
	}
	if((px->p_flag&P_DONE) && job.waitall && !(flag&JOB_LFLAG))
		return(0);
	job_lock();
	n = px->p_job;
	if(px==job.pwlist)
		msize = '+';
	else if(px==job.pwlist->p_nxtjob)
		msize = '-';
	else
		msize = ' ';
	if(flag&JOB_NLFLAG)
		sfputc(outfile,'\n');
	sfprintf(outfile,"[%d] %c ",n, msize);
	do
	{
		n = 0;
		if(flag&JOB_LFLAG)
#if SHOPT_COSHELL
			sfprintf(outfile,"%s\t",sh_pid2str(shp,px->p_pid));
#else
			sfprintf(outfile,"%d\t",px->p_pid);
#endif /* SHOPT_COSHELL */
		if(px->p_flag&P_SIGNALLED)
			msg = job_sigmsg((int)(px->p_exit));
		else if(px->p_flag&P_NOTIFY)
		{
			msg = sh_translate(e_done);
			n = px->p_exit;
		}
		else
			msg = sh_translate(e_running);
		px->p_flag &= ~P_NOTIFY;
		sfputr(outfile,msg,-1);
		msize = strlen(msg);
		if(n)
		{
			sfprintf(outfile,"(%d)",(int)n);
			msize += (3+(n>10)+(n>100));
		}
		if(px->p_flag&P_COREDUMP)
		{
			msg = sh_translate(e_coredump);
			sfputr(outfile, msg, -1);
			msize += strlen(msg);
		}
		sfnputc(outfile,' ',MAXMSG>msize?MAXMSG-msize:1);
		if(flag&JOB_LFLAG)
			px = px->p_nxtproc;
		else
		{
			while(px=px->p_nxtproc)
				px->p_flag &= ~P_NOTIFY;
			px = 0;
		}
		if(!px)
			hist_list(shgd->hist_ptr,outfile,pw->p_name,0,";");
		else
			sfputr(outfile, e_nlspace, -1);
	}
	while(px);
	job_unlock();
	return(0);
}

/*
 * get the process group given the job number
 * This routine returns the process group number or -1
 */
static struct process *job_bystring(register char *ajob)
{
	register struct process *pw=job.pwlist;
	register int c;
	if(*ajob++ != '%' || !pw)
		return(NIL(struct process*));
	c = *ajob;
	if(isdigit(c))
		pw = job_byjid((int)strtol(ajob, (char**)0, 10));
	else if(c=='+' || c=='%')
		;
	else if(c=='-')
	{
		if(pw)
			pw = job.pwlist->p_nxtjob;
	}
	else
		pw = job_byname(ajob);
	if(pw && pw->p_flag)
		return(pw);
	return(NIL(struct process*));
}

/*
 * Kill a job or process
 */

int job_kill(register struct process *pw,register int sig)
{
	Shell_t	*shp = pw->p_shp;
	register pid_t pid;
	register int r;
	const char *msg;
#ifdef SIGTSTP
	int stopsig = (sig==SIGSTOP||sig==SIGTSTP||sig==SIGTTIN||sig==SIGTTOU);
#else
#	define stopsig	1
#endif	/* SIGTSTP */
	job_lock();
	errno = ECHILD;
	if(pw==0)
		goto error;
	pid = pw->p_pid;
#if SHOPT_COSHELL
	if(pw->p_cojob)
		r = cokill(pw->p_cojob->coshell,pw->p_cojob,sig);
	else
#endif /* SHOPT_COSHELL */
	if(by_number)
	{
		if(pid==0 && job.jobcontrol)
			r = job_walk(outfile, job_kill,sig, (char**)0);
#ifdef SIGTSTP
		if(sig==SIGSTOP && pid==shp->gd->pid && shp->gd->ppid==1)
		{
			/* can't stop login shell */
			errno = EPERM;
			r = -1;
		}
		else
		{
			if(pid>=0)
			{
				if((r = kill(pid,sig))>=0 && !stopsig)
				{
					if(pw->p_flag&P_STOPPED)
						pw->p_flag &= ~(P_STOPPED|P_SIGNALLED);
					if(sig)
						kill(pid,SIGCONT);
				}
			}
			else
			{
				if((r = killpg(-pid,sig))>=0 && !stopsig)
				{
					job_unstop(job_bypid(pw->p_pid));
					if(sig)
						killpg(-pid,SIGCONT);
				}
			}
		}
#else
		if(pid>=0)
			r = kill(pid,sig);
		else
			r = killpg(-pid,sig);
#endif	/* SIGTSTP */
	}
	else
	{
		if(pid = pw->p_pgrp)
		{
			r = killpg(pid,sig);
#ifdef SIGTSTP
			if(r>=0 && (sig==SIGHUP||sig==SIGTERM || sig==SIGCONT))
				job_unstop(pw);
#endif	/* SIGTSTP */
			if(r>=0)
				sh_delay(.05);
		}
		while(pw && pw->p_pgrp==0 && (r=kill(pw->p_pid,sig))>=0) 
		{
#ifdef SIGTSTP
			if(sig==SIGHUP || sig==SIGTERM)
				kill(pw->p_pid,SIGCONT);
#endif	/* SIGTSTP */
			pw = pw->p_nxtproc;
		}
	}
	if(r<0 && job_string)
	{
	error:
		if(pw && by_number)
			msg = sh_translate(e_no_proc);
		else
			msg = sh_translate(e_no_job);
		if(errno == EPERM)
			msg = sh_translate(e_access);
		sfprintf(sfstderr,"kill: %s: %s\n",job_string, msg);
		r = 2;
	}
	sh_delay(.001);
	job_unlock();
	return(r);
}

/*
 * Get process structure from first letters of jobname
 *
 */

static struct process *job_byname(char *name)
{
	register struct process *pw = job.pwlist;
	register struct process *pz = 0;
	register int *flag = 0;
	register char *cp = name;
	int offset;
	if(!shgd->hist_ptr)
		return(NIL(struct process*));
	if(*cp=='?')
		cp++,flag= &offset;
	for(;pw;pw=pw->p_nxtjob)
	{
		if(hist_match(shgd->hist_ptr,pw->p_name,cp,flag)>=0)
		{
			if(pz)
				errormsg(SH_DICT,ERROR_exit(1),e_jobusage,name-1);
			pz = pw;
		}
	}
	return(pz);
}

#else
#   define job_set(x)
#   define job_reset(x)
#endif /* JOBS */



/*
 * Initialize the process posting array
 */

void	job_clear(void)
{
	Shell_t	*shp = sh_getinterp();
	register struct process *pw, *px;
	register struct process *pwnext;
	register int j = BYTE(shp->gd->lim.child_max);
	register struct jobsave *jp,*jpnext;
	job_lock();
	for(pw=job.pwlist; pw; pw=pwnext)
	{
		pwnext = pw->p_nxtjob;
		while(px=pw)
		{
			pw = pw->p_nxtproc;
			free((void*)px);
		}
	}
	for(jp=bck.list; jp;jp=jpnext)
	{
		jpnext = jp->next;
		free((void*)jp);
	}
	bck.list = 0;
	if(njob_savelist < NJOB_SAVELIST)
		init_savelist();
	job.pwlist = NIL(struct process*);
	job.numpost=0;
#ifdef SHOPT_BGX
	job.numbjob = 0;
#endif /* SHOPT_BGX */
	job.waitall = 0;
	job.curpgid = 0;
	job.toclear = 0;
	if(!job.freejobs)
		job.freejobs = (unsigned char*)malloc((unsigned)(j+1));
	while(j >=0)
		job.freejobs[j--]  = 0;
	job_unlock();
}

/*
 * put the process <pid> on the process list and return the job number
 * if non-zero, <join> is the process id of the job to join
 */

int job_post(Shell_t *shp,pid_t pid, pid_t join)
{
	register struct process *pw;
	register History_t *hp = shp->gd->hist_ptr;
#ifdef SHOPT_BGX
	int val,bg=0;
#else
	int val;
#endif
	shp->jobenv = shp->curenv;
	if(job.toclear)
	{
		job_clear();
		return(0);
	}
	job_lock();
#ifdef SHOPT_BGX
	if(join==1)
	{
		join = 0;
		bg = P_BG;
		job.numbjob++;
	}
#endif /* SHOPT_BGX */
	if(njob_savelist < NJOB_SAVELIST)
		init_savelist();
	if(pw = job_bypid(pid))
		job_unpost(pw,0);
	if(join)
	{
		if(pw=job_bypid(join))
			val = pw->p_job;
		else
			val = job.curjobid;
		/* if job to join is not first move it to front */
		if(val && (pw=job_byjid(val)) != job.pwlist)
		{
			job_unlink(pw);
			pw->p_nxtjob = job.pwlist;
			job.pwlist = pw;
		}
	}
	if(pw=freelist)
		freelist = pw->p_nxtjob;
	else
		pw = new_of(struct process,0);
	pw->p_flag = 0;
	job.numpost++;
	if(join && job.pwlist)
	{
		/* join existing current job */
		pw->p_nxtjob = job.pwlist->p_nxtjob;
		pw->p_nxtproc = job.pwlist;
		pw->p_job = job.pwlist->p_job;
	}
	else
	{
		/* create a new job */
		while((pw->p_job = job_alloc()) < 0)
			job_wait((pid_t)1);
		pw->p_nxtjob = job.pwlist;
		pw->p_nxtproc = 0;
	}
	pw->p_exitval = job.exitval; 
#if SHOPT_COSHELL
	pw->p_cojob = 0;
	if(shp->coshell && (pid&COPID_BIT))
	{
		pw->p_cojob = ((struct cosh*)shp->coshell)->cojob;
		job.curpgid = sh_isstate(SH_MONITOR)?pid:0;
	}
#endif /* SHOPT_COSHELL */
	job.pwlist = pw;
	pw->p_shp = shp;
	pw->p_env = shp->curenv;
	pw->p_pid = pid;
	if(!shp->outpipe || shp->cpid==pid)
		pw->p_flag = P_EXITSAVE;
	pw->p_exitmin = shp->xargexit;
	pw->p_exit = 0;
	if(sh_isstate(SH_MONITOR))
	{
		if(killpg(job.curpgid,0)<0 && errno==ESRCH)
			job.curpgid = pid;
		pw->p_fgrp = job.curpgid;
	}
	else
		pw->p_fgrp = 0;
	pw->p_pgrp = pw->p_fgrp;
#ifdef DEBUG
	sfprintf(sfstderr,"ksh: job line %4d: post pid=%d critical=%d job=%d pid=%d pgid=%d savesig=%d join=%d\n",__LINE__,getpid(),job.in_critical,pw->p_job,
		pw->p_pid,pw->p_pgrp,job.savesig,join);
	sfsync(sfstderr);
#endif /* DEBUG */
#ifdef JOBS
	if(hp && !sh_isstate(SH_PROFILE))
		pw->p_name=hist_tell(shgd->hist_ptr,(int)hp->histind-1);
	else
		pw->p_name = -1;
#endif /* JOBS */
	if ((val = job_chksave(pid))>=0 && !jobfork)
	{
		pw->p_exit = val;
		if(pw->p_exit==SH_STOPSIG)
		{
			pw->p_flag |= (P_SIGNALLED|P_STOPPED);
			pw->p_exit = 0;
		}
		else if(pw->p_exit >= SH_EXITSIG)
		{
			pw->p_flag |= P_DONE|P_SIGNALLED;
			pw->p_exit &= SH_EXITMASK;
		}
		else
			pw->p_flag |= (P_DONE|P_NOTIFY);
	}
#ifdef SHOPT_BGX
	if(bg)
	{
		if(pw->p_flag&P_DONE)
			job.numbjob--;
		else
			pw->p_flag |= P_BG;
	}
#endif /* SHOPT_BGX */
	lastpid = 0;
	job_unlock();
	return(pw->p_job);
}

/*
 * Returns a process structure give a process id
 */

static struct process *job_bypid(pid_t pid)
{
	register struct process  *pw, *px;
	for(pw=job.pwlist; pw; pw=pw->p_nxtjob)
		for(px=pw; px; px=px->p_nxtproc)
		{
			if(px->p_pid==pid)
				return(px);
		}
	return(NIL(struct process*));
}

/*
 * return a pointer to a job given the job id
 */

static struct process *job_byjid(int jobid)
{
	register struct process *pw;
	for(pw=job.pwlist;pw; pw = pw->p_nxtjob)
	{
		if(pw->p_job==jobid)
			break;
	}
	return(pw);
}

/*
 * print a signal message
 */
static void job_prmsg(register struct process *pw)
{
	if(pw->p_exit!=SIGINT && pw->p_exit!=SIGPIPE)
	{
		register const char *msg, *dump;
		msg = job_sigmsg((int)(pw->p_exit));
		msg = sh_translate(msg);
		if(pw->p_flag&P_COREDUMP)
			dump =  sh_translate(e_coredump);
		else
			dump = "";
		if(sh_isstate(SH_INTERACTIVE))
			sfprintf(sfstderr,"%s%s\n",msg,dump);
		else
			errormsg(SH_DICT,2,"%d: %s%s",pw->p_pid,msg,dump);
	}
}

/*
 * Wait for process pid to complete
 * If pid < -1, then wait can be interrupted, -pid is waited for (wait builtin)
 * pid=0 to unpost all done processes
 * pid=1 to wait for at least one process to complete
 * pid=-1 to wait for all runing processes
 */

int	job_wait(register pid_t pid)
{
	Shell_t		*shp = sh_getinterp();
	register struct process *pw=0,*px;
	register int	jobid = 0;
	int		nochild = 1;
	char		intr = 0;
	if(pid < 0)
	{
		pid = -pid;
		intr = 1;
	}
	job_lock();
	if(pid==0)
	{
		if(!job.waitall || !job.curjobid || !(pw = job_byjid(job.curjobid)))
		{
			job_unlock();
			goto done;
		}
		jobid = pw->p_job;
		job.curjobid = 0;
		if(!(pw->p_flag&(P_DONE|P_STOPPED)))
			job_reap(job.savesig);
	}
	if(pid > 1)
	{
		if(pid==shp->spid)
			shp->spid = 0;
		if(!(pw=job_bypid(pid)))
		{
			/* check to see whether job status has been saved */
			if((shp->exitval = job_chksave(pid)) < 0)
				shp->exitval = ERROR_NOENT;
			exitset();
			job_unlock();
			return(nochild);
		}
		else if(intr && pw->p_env!=shp->curenv)
		{
			shp->exitval = ERROR_NOENT;
			job_unlock();
			return(nochild);
		}
		jobid = pw->p_job;
		if(!intr)
			pw->p_flag &= ~P_EXITSAVE;
		if(pw->p_pgrp && job.parent!= (pid_t)-1)
			job_set(job_byjid(jobid));
	}
	pwfg = pw;
#ifdef DEBUG
	sfprintf(sfstderr,"ksh: job line %4d: wait pid=%d critical=%d job=%d pid=%d\n",__LINE__,getpid(),job.in_critical,jobid,pid);
	if(pw)
		sfprintf(sfstderr,"ksh: job line %4d: wait pid=%d critical=%d flags=%o\n",__LINE__,getpid(),job.in_critical,pw->p_flag);
#endif /* DEBUG*/
	errno = 0;
	if(shp->coutpipe>=0 && lastpid && shp->cpid==lastpid)
	{
		sh_close(shp->coutpipe);
		sh_close(shp->cpipe[1]);
		shp->cpipe[1] = shp->coutpipe = -1;
	}
	while(1)
	{
		if(job.waitsafe)
		{
			for(px=job.pwlist;px; px = px->p_nxtjob)
			{
				if(px!=pw && (px->p_flag&P_NOTIFY))
				{
					if(sh_isoption(SH_NOTIFY))
					{
						outfile = sfstderr;
						job_list(px,JOB_NFLAG|JOB_NLFLAG);
						sfsync(sfstderr);
					}
					else if(!sh_isoption(SH_INTERACTIVE) && (px->p_flag&P_SIGNALLED))
					{
						job_prmsg(px);
						px->p_flag &= ~P_NOTIFY;
					}
				}
			}
		}
		if(pw && (pw->p_flag&(P_DONE|P_STOPPED)))
		{
#ifdef SIGTSTP
			if(pw->p_flag&P_STOPPED)
			{
				pw->p_flag |= P_EXITSAVE;
				if(sh_isoption(SH_INTERACTIVE) && !sh_isstate(SH_FORKED))
				{
					if( pw->p_exit!=SIGTTIN && pw->p_exit!=SIGTTOU)
						break;

					killpg(pw->p_pgrp,SIGCONT);
				}
				else /* ignore stop when non-interactive */
					pw->p_flag &= ~(P_NOTIFY|P_SIGNALLED|P_STOPPED|P_EXITSAVE);
			}
			else
#endif /* SIGTSTP */
			{
				if(pw->p_flag&P_SIGNALLED)
				{
					pw->p_flag &= ~P_NOTIFY;
					job_prmsg(pw);
				}
				else if(pw->p_flag&P_DONE)
					pw->p_flag &= ~P_NOTIFY;
				if(pw->p_job==jobid)
				{
					px = job_byjid(jobid);
					/* last process in job */
					if(px!=pw)
						px = 0;
					if(px)
					{
						shp->exitval=px->p_exit;
						if(px->p_flag&P_SIGNALLED)
							shp->exitval |= SH_EXITSIG;
						if(intr)
							px->p_flag &= ~P_EXITSAVE;
					}
				}
				px = job_unpost(pw,1);
				if(!px || !job.waitall)
					break;
				pw = px;
				continue;
			}
		}
		sfsync(sfstderr);
		job.waitsafe = 0;
		nochild = job_reap(job.savesig);
		if(job.waitsafe)
			continue;
		if(nochild)
			break;
		if(shp->sigflag[SIGALRM]&SH_SIGTRAP)
			sh_timetraps(shp);
		if((intr && shp->trapnote) || (pid==1 && !intr))
			break;
	}
	if(intr && shp->trapnote)
		shp->exitval = 1;
	pwfg = 0;
	job_unlock();
	if(pid==1)
		return(nochild);
	exitset();
	if(pid==0)
		goto done;
	if(pw->p_pgrp)
	{
		job_reset(pw);
		/* propogate keyboard interrupts to parent */
		if((pw->p_flag&P_SIGNALLED) && pw->p_exit==SIGINT && !(shp->sigflag[SIGINT]&SH_SIGOFF))
			kill(getpid(),SIGINT); 
#ifdef SIGTSTP
		else if((pw->p_flag&P_STOPPED) && pw->p_exit==SIGTSTP)
		{
			job.parent = 0;
			kill(getpid(),SIGTSTP); 
		}
#endif /* SIGTSTP */
	}
	else
	{
		if(pw->p_pid == tcgetpgrp(JOBTTY))
		{
			if(pw->p_pgrp==0)
				pw->p_pgrp = pw->p_pid;
			job_reset(pw);
		}
		tty_set(-1, 0, NIL(struct termios*));
	}
done:
	if(!job.waitall && sh_isoption(SH_PIPEFAIL))
		return(nochild);
	if(!shp->intrap)
	{
		job_lock();
		for(pw=job.pwlist; pw; pw=px)
		{
			px = pw->p_nxtjob;
			job_unpost(pw,0);
		}
		job_unlock();
	}
	return(nochild);
}

/*
 * move job to foreground if bgflag == 'f'
 * move job to background if bgflag == 'b'
 * disown job if bgflag == 'd'
 */

int job_switch(register struct process *pw,int bgflag)
{
	register const char *msg;
	job_lock();
	if(!pw || !(pw=job_byjid((int)pw->p_job)))
	{
		job_unlock();
		return(1);
	}
	if(bgflag=='d')
	{
		for(; pw; pw=pw->p_nxtproc)
			pw->p_flag |= P_DISOWN;
		job_unlock();
		return(0);
	}
#ifdef SIGTSTP
	if(bgflag=='b')
	{
		sfprintf(outfile,"[%d]\t",(int)pw->p_job);
		sh.bckpid = pw->p_pid;
#ifdef SHOPT_BGX
		pw->p_flag |= P_BG;
#endif
		msg = "&";
	}
	else
	{
		job_unlink(pw);
		pw->p_nxtjob = job.pwlist;
		job.pwlist = pw;
		msg = "";
	}
	hist_list(shgd->hist_ptr,outfile,pw->p_name,'&',";");
	sfputr(outfile,msg,'\n');
	sfsync(outfile);
	if(bgflag=='f')
	{
		if(!(pw=job_unpost(pw,1)))
		{
			job_unlock();
			return(1);
		}
		job.waitall = 1;
		pw->p_flag |= P_FG;
#ifdef SHOPT_BGX
		pw->p_flag &= ~P_BG;
#endif
		job_wait(pw->p_pid);
		job.waitall = 0;
	}
	else if(pw->p_flag&P_STOPPED)
		job_unstop(pw);
#endif /* SIGTSTP */
	job_unlock();
	return(0);
}


#ifdef SIGTSTP
/*
 * Set the foreground group associated with a job
 */

static void job_fgrp(register struct process *pw, int newgrp)
{
	for(; pw; pw=pw->p_nxtproc)
		pw->p_fgrp = newgrp;
}

/*
 * turn off STOP state of a process group and send CONT signals
 */

static void job_unstop(register struct process *px)
{
	register struct process *pw;
	register int num = 0;
	for(pw=px ;pw ;pw=pw->p_nxtproc)
	{
		if(pw->p_flag&P_STOPPED)
		{
			num++;
			pw->p_flag &= ~(P_STOPPED|P_SIGNALLED|P_NOTIFY);
		}
	}
	if(num!=0)
	{
		if(px->p_fgrp != px->p_pgrp)
			killpg(px->p_fgrp,SIGCONT);
		killpg(px->p_pgrp,SIGCONT);
	}
}
#endif	/* SIGTSTP */

/*
 * remove a job from table
 * If all the processes have not completed, unpost first non-completed  process
 * Otherwise the job is removed and job_unpost returns NULL.
 * pwlist is reset if the first job is removed
 * if <notify> is non-zero, then jobs with pending notifications are unposted
 */

static struct process *job_unpost(register struct process *pwtop,int notify)
{
	register struct process *pw;
	/* make sure all processes are done */
#ifdef DEBUG
	sfprintf(sfstderr,"ksh: job line %4d: drop pid=%d critical=%d pid=%d env=%d\n",__LINE__,getpid(),job.in_critical,pwtop->p_pid,pwtop->p_env);
	sfsync(sfstderr);
#endif /* DEBUG */
	pwtop = pw = job_byjid((int)pwtop->p_job);
#ifdef SHOPT_BGX
	if(pw->p_flag&P_BG) 
		return(pw);
#endif /* SHOPT_BGX */
	for(; pw && (pw->p_flag&P_DONE)&&(notify||!(pw->p_flag&P_NOTIFY)||pw->p_env); pw=pw->p_nxtproc);
	if(pw)
		return(pw);
	if(pwtop->p_job == job.curjobid)
		return(0);
	/* all processes complete, unpost job */
	job_unlink(pwtop);
	for(pw=pwtop; pw; pw=pw->p_nxtproc)
	{
		if(pw && pw->p_exitval)
			*pw->p_exitval = pw->p_exit;
		/* save the exit status for background jobs */
		if((pw->p_flag&P_EXITSAVE) ||  pw->p_pid==sh.spid)
		{
			struct jobsave *jp;
			/* save status for future wait */
			if(jp = jobsave_create(pw->p_pid))
			{
				jp->exitval = pw->p_exit;
				if(pw->p_flag&P_SIGNALLED)
					jp->exitval |= SH_EXITSIG;
			}
			pw->p_flag &= ~P_EXITSAVE;
		}
		pw->p_flag &= ~P_DONE;
		job.numpost--;
		pw->p_nxtjob = freelist;
		freelist = pw;
	}
	pwtop->p_pid = 0;
#ifdef DEBUG
	sfprintf(sfstderr,"ksh: job line %4d: free pid=%d critical=%d job=%d\n",__LINE__,getpid(),job.in_critical,pwtop->p_job);
	sfsync(sfstderr);
#endif /* DEBUG */
	job_free((int)pwtop->p_job);
	return((struct process*)0);
}

/*
 * unlink a job form the job list
 */
static void job_unlink(register struct process *pw)
{
	register struct process *px;
	if(pw==job.pwlist)
	{
		job.pwlist = pw->p_nxtjob;
		job.curpgid = 0;
		return;
	}
	for(px=job.pwlist;px;px=px->p_nxtjob)
		if(px->p_nxtjob == pw)
		{
			px->p_nxtjob = pw->p_nxtjob;
			return;
		}
}

/*
 * get an unused job number
 * freejobs is a bit vector, 0 is unused
 */

static int job_alloc(void)
{
	register int j=0;
	register unsigned mask = 1;
	register unsigned char *freeword;
	register int jmax = BYTE(shgd->lim.child_max);
	/* skip to first word with a free slot */
	for(j=0;job.freejobs[j] == UCHAR_MAX; j++);
	if(j >= jmax)
	{
		register struct process *pw;
		for(j=1; j < shgd->lim.child_max; j++)
		{
			if((pw=job_byjid(j))&& !job_unpost(pw,0))
				break;
		}
		j /= CHAR_BIT;
		if(j >= jmax)
			return(-1);
	}
	freeword = &job.freejobs[j];
	j *= CHAR_BIT;
	for(j++;mask&(*freeword);j++,mask <<=1);
	*freeword  |= mask;
	return(j);
}

/*
 * return a job number
 */

static void job_free(register int n)
{
	register int j = (--n)/CHAR_BIT;
	register unsigned mask;
	n -= j*CHAR_BIT;
	mask = 1 << n;
	job.freejobs[j]  &= ~mask;
}

static char *job_sigmsg(int sig)
{
	static char signo[40];
#ifdef apollo
	/*
	 * This code handles the formatting for the apollo specific signal
	 * SIGAPOLLO. 
	 */
	extern char *apollo_error(void);
	
	if ( sig == SIGAPOLLO )
		return( apollo_error() );
#endif /* apollo */
	if(sig<=shgd->sigmax && shgd->sigmsg[sig])
		return(shgd->sigmsg[sig]);
#if defined(SIGRTMIN) && defined(SIGRTMAX)
	if(sig>=sh.gd->sigruntime[SH_SIGRTMIN] && sig<=sh.gd->sigruntime[SH_SIGRTMAX])
	{
		static char sigrt[20];
		if(sig>sh.gd->sigruntime[SH_SIGRTMIN]+(sh.gd->sigruntime[SH_SIGRTMAX]-sig<=sh.gd->sigruntime[SH_SIGRTMIN])/2)
			sfsprintf(sigrt,sizeof(sigrt),"SIGRTMAX-%d",sh.gd->sigruntime[SH_SIGRTMAX]-sig);
		else
			sfsprintf(sigrt,sizeof(sigrt),"SIGRTMIN+%d",sig-sh.gd->sigruntime[SH_SIGRTMIN]);
		return(sigrt);
	}
#endif
	sfsprintf(signo,sizeof(signo),sh_translate(e_signo),sig);
	return(signo);
}

/*
 * see whether exit status has been saved and delete it
 * if pid==0, then oldest saved process is deleted
 * If pid is not found a -1 is returned.
 */
static int job_chksave(register pid_t pid)
{
	register struct jobsave *jp = bck.list, *jpold=0;
	register int r= -1;
	register int count=bck.count;
	struct back_save *bp= &bck;
again:
	while(jp && count-->0)
	{
		if(jp->pid==pid)
			break;
		if(pid==0 && !jp->next)
			break;
		jpold = jp;
		jp = jp->next;
	}
	if(!jp && pid && (bp=bp->prev))
	{
		count = bp->count;
		jp = bp->list;
		goto again;
	}
	if(jp)
	{
		r = 0;
		if(pid)
			r = jp->exitval;
		if(jpold)
			jpold->next = jp->next;
		else
			bp->list = jp->next;
		bp->count--;
		if(njob_savelist < NJOB_SAVELIST)
		{
			njob_savelist++;
			jp->next = job_savelist;
			job_savelist = jp;
		}
		else
			free((void*)jp);
	}
	return(r);
}

void *job_subsave(void)
{
	struct back_save *bp = new_of(struct back_save,0);
	job_lock();
	*bp = bck;
	bp->prev = bck.prev;
	bck.count = 0;
	bck.list = 0;
	bck.prev = bp;
	job_unlock();
	return((void*)bp);
}

void job_subrestore(void* ptr)
{
	register struct jobsave *jp;
	register struct back_save *bp = (struct back_save*)ptr;
	register struct process *pw, *px, *pwnext;
	struct jobsave *end=NULL;
	job_lock();
	for(jp=bck.list; jp; jp=jp->next)
	{
		if (!jp->next)
			end = jp;
	}
	if(end)
		end->next = bp->list;
	else
		bck.list = bp->list;
	bck.count += bp->count;
	bck.prev = bp->prev;
	while(bck.count > shgd->lim.child_max)
		job_chksave(0);
	for(pw=job.pwlist; pw; pw=pwnext)
	{
		pwnext = pw->p_nxtjob;
		if(pw->p_env != sh.curenv || pw->p_pid==sh.pipepid)
			continue;
		for(px=pw; px; px=px->p_nxtproc)
			px->p_flag |= P_DONE;
		job_unpost(pw,0);
	}

	free((void*)bp);
	job_unlock();
}

int sh_waitsafe(void)
{
	return(job.waitsafe);
}

void job_fork(pid_t parent)
{
#ifdef DEBUG
	sfprintf(sfstderr,"ksh: job line %4d: fork pid=%d critical=%d parent=%d\n",__LINE__,getpid(),job.in_critical,parent);
#endif /* DEBUG */
	switch (parent)
	{
	case -1:
		job_lock();
		jobfork++;
		break;
	case 0:
		jobfork=0;
		job_unlock();
		job.waitsafe = 0;
		job.in_critical = 0;
		break;
	default:
		job_chksave(parent);
		jobfork=0;
		job_unlock();
		break;
	}
}

