/***********************************************************************
* *
* This software is part of the ast package *
* Copyright (c) 1982-2011 AT&T Intellectual Property *
* and is licensed under the *
* Common Public License, Version 1.0 *
* by AT&T Intellectual Property *
* *
* A copy of the License is available at *
* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) *
* *
* 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)
# define WCONTINUED 0
#endif
/*
* temporary hack to get W* macros to work
*/
/*
* 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
{
unsigned short exitval;
};
static int njob_savelist;
static void init_savelist(void)
{
while(njob_savelist < NJOB_SAVELIST)
{
job_savelist = jp;
}
}
struct back_save
{
int count;
};
#ifdef VSUSP
# ifndef CNSUSP
# ifdef _POSIX_VDISABLE
# else
# define CNSUSP 0
# endif /* _POSIX_VDISABLE */
# endif /* CNSUSP */
# ifndef CSWTCH
# ifdef CSUSP
# else
# endif /* CSUSP */
# endif /* CSWTCH */
#endif /* VSUSP */
/* Process states */
#ifdef SHOPT_BGX
#endif /* SHOPT_BGX */
static int job_chksave(pid_t);
static char *job_sigmsg(int);
static int job_alloc(void);
static void job_free(int);
static void job_unlink(struct process*);
static char beenhere;
static char possible;
static char by_number;
#ifdef JOBS
static void job_waitsafe(int);
static struct process *job_byname(char*);
static struct process *job_bystring(char*);
static char *job_string;
#else
extern const char e_coredump[];
#endif /* JOBS */
#ifdef SIGTSTP
static void job_unstop(struct process*);
# ifndef _lib_tcgetpgrp
# ifdef TIOCGPGRP
static int _i_;
# endif /* TIOCGPGRP */
{
# ifdef TIOCGPGRP
# else
return(-1);
# endif /* TIOCGPGRP */
}
# endif /* _lib_tcgetpgrp */
#else
#endif /* SIGTSTP */
#ifndef OTTYDISC
#endif /* OTTYDISC */
#ifdef JOBS
typedef int (*Waitevent_f)(int,long,int);
#ifdef SHOPT_BGX
{
job_lock();
{
continue;
job_unpost(pw,0);
}
job_unlock();
}
#endif /* SHOPT_BGX */
/*
* return next on link list of jobsave free list
*/
{
job_chksave(0);
if(jp)
{
}
else
if(jp)
{
}
return(jp);
}
#if SHOPT_COSHELL
{
}
{
{
{
break;
}
}
else
}
{
int n,r=0;
if(!cp)
else
{
break;
}
if(!csp)
if(cp)
{
}
{
{
if(fun)
{
continue;
}
else
n++;
}
}
if(!n)
else if(fun)
return(r);
}
#endif /* SHOPT_COSHELL */
/*
* Reap one job
* When called with sig==0, it does a blocking wait
*/
{
register int flags;
#if SHOPT_COSHELL
int cojobs;
{
break;
}
pid = 0;
#endif /* SHOPT_COSHELL */
if (vmbusy())
{
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)
#endif /* DEBUG */
if(sig)
else
while(1)
{
{
}
#if SHOPT_COSHELL
if(cojobs)
{
{
else
cotimeout = 0;
goto cojob;
}
else if(copending(0)==0)
cojobs = 0;
cotimeout = 0;
}
#endif /* SHOPT_COSHELL */
#if SHOPT_COSHELL
#endif /* SHOPT_COSHELL */
/*
* some systems (linux 2.6) may return EINVAL
* when there are no continued children
*/
{
errno = 0;
continue;
}
if(pid<=0)
break;
jp = 0;
{
#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 */
continue;
job_clear();
px = 0;
{
continue;
}
}
#ifdef SIGTSTP
else
else if(WIFSTOPPED(wstat))
{
if(px)
{
/* move to top of job list */
job_unlink(px);
}
continue;
}
else
#endif /* SIGTSTP */
{
/* check for coprocess completion */
{
}
if (WIFSIGNALED(wstat))
{
/* if process in current jobs terminates from
* an interrupt, propogate to parent shell
*/
{
}
}
else
{
}
#ifdef SHOPT_BGX
{
{
if(sig==0)
else
}
else
}
#endif /* SHOPT_BGX */
}
{
}
#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);
#endif /* DEBUG*/
/* only top-level process in job should have notify set */
{
if(!px)
}
#ifndef SHOPT_BGX
{
}
#endif
}
{
#ifdef SHOPT_BGX
#endif /* SHOPT_BGX */
nochild = 1;
}
{
}
if(sig)
return(nochild);
}
/*
* This is the SIGCLD interrupt routine
*/
{
{
}
else
}
/*
* initialize job control if possible
* if lflag is set the switching driver message will not print
*/
{
register int ntry=0;
# endif
if(njob_savelist < NJOB_SAVELIST)
if(!sh_isoption(SH_INTERACTIVE))
return;
/* use new line discipline when available */
#ifdef NTTYDISC
# ifdef FIOLOOKLD
# else
# endif /* FIOLOOKLD */
return;
{
/* no job control when running with MPX */
# if SHOPT_VSH
# endif /* SHOPT_VSH */
return;
}
#endif /* NTTYDISC */
/* some systems have job control, but not initialized */
{
/* Get a controlling terminal and set process group */
/* This should have already been done by rlogin */
register int fd;
register char *ttynam;
#ifndef SIGTSTP
#endif /*SIGTSTP */
return;
return;
#ifdef SIGTSTP
#endif /* SIGTSTP */
}
#ifdef SIGTSTP
{
/* wait until we are in the foreground */
{
return;
/* Stop this shell until continued */
/* resumes here after continue tries again */
{
return;
}
}
}
#endif /* SIGTTIN */
#ifdef NTTYDISC
/* set the line discipline */
{
# ifdef FIOPUSHLD
return;
{
return;
}
# else
return;
# endif /* FIOPUSHLD */
if(lflag==0)
else
}
#endif /* NTTYDISC */
if(!possible)
return;
#ifdef SIGTSTP
/* make sure that we are a process group leader */
# 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
# endif /* SA_NOCLDSTOP || SA_NOCLDWAIT */
/* The shell now handles ^Z */
# ifdef CNSUSP
/* set the switch character */
{
}
# endif /* CNSUSP */
job.jobcontrol++;
#endif /* SIGTSTP */
return;
}
/*
* see if there are any stopped jobs
* restore tty driver and pgrp
*/
{
return(0);
return(0);
return(0);
job_lock();
if(!tty_check(0))
beenhere++;
{
{
running++;
continue;
}
if(beenhere)
count++;
}
{
if(count)
{
return(-1);
}
{
return(-1);
}
}
job_unlock();
# ifdef SIGTSTP
# endif /* SIGTSTP */
# ifdef NTTYDISC
{
/* restore old line discipline */
# ifdef FIOPUSHLD
return(0);
{
return(0);
}
# else
return(0);
# endif /* FIOPUSHLD */
}
# endif /* NTTYDISC */
# ifdef CNSUSP
{
}
# endif /* CNSUSP */
job.jobcontrol = 0;
return(0);
}
{
/* save current terminal state */
{
/* restore terminal state for job */
}
#ifdef SIGTSTP
/* if job is stopped, resume it in the background */
job_unstop(pw);
#endif /* SIGTSTP */
}
{
/* save the terminal state for current job */
#ifdef SIGTSTP
return;
#endif /* SIGTSTP */
/* force the following tty_get() to do a tcgetattr() unless fg */
{
/* restore terminal state for job */
}
beenhere = 0;
}
#endif /* JOBS */
/*
* wait built-in command
*/
{
register char *jp;
if(*jobs==0)
{
#ifdef JOBS
if(*jp == '%')
{
job_lock();
job_unlock();
if(pw)
else
return;
}
# if SHOPT_COSHELL
{
return;
}
# endif /* SHOPT_COSHELL */
else
#endif /* JOBS */
}
}
#ifdef JOBS
/*
* execute function <fun> for each job
*/
{
register int r = 0;
job_string = 0;
by_number = 0;
job_lock();
#if SHOPT_COSHELL
#endif /* SHOPT_COSHELL */
if(jobs==0)
{
/* do all jobs */
{
continue;
r = 2;
}
}
else if(*jobs==0) /* current job */
{
/* skip over non-stop jobs */
r = 2;
}
{
job_string = jobid;
if(*jobid==0)
#if SHOPT_COSHELL
{
by_number = 0;
job_unlock();
return(r);
}
#endif /* SHOPT_COSHELL */
if(*jobid == '%')
else
{
if(pid<0)
jobid++;
jobid++;
if(*jobid)
{
}
by_number = 1;
}
r = 2;
by_number = 0;
}
job_unlock();
return(r);
}
/*
* send signal <sig> to background process group if not disowned
*/
{
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
*/
{
register int n;
register const char *msg;
register int msize;
return(1);
return(0);
return(0);
{
#if SHOPT_COSHELL
#else
#endif /* SHOPT_COSHELL */
return(0);
}
return(0);
job_lock();
msize = '+';
msize = '-';
else
msize = ' ';
if(flag&JOB_NLFLAG)
do
{
n = 0;
#if SHOPT_COSHELL
#else
#endif /* SHOPT_COSHELL */
{
}
else
if(n)
{
}
{
}
else
{
px = 0;
}
if(!px)
else
}
while(px);
job_unlock();
return(0);
}
/*
* get the process group given the job number
* This routine returns the process group number or -1
*/
{
register int c;
c = *ajob;
if(isdigit(c))
else if(c=='+' || c=='%')
;
else if(c=='-')
{
if(pw)
}
else
return(pw);
}
/*
* Kill a job or process
*/
{
register int r;
const char *msg;
#ifdef SIGTSTP
#else
#endif /* SIGTSTP */
job_lock();
if(pw==0)
goto error;
#if SHOPT_COSHELL
else
#endif /* SHOPT_COSHELL */
if(by_number)
{
#ifdef SIGTSTP
{
/* can't stop login shell */
r = -1;
}
else
{
if(pid>=0)
{
{
if(sig)
}
}
else
{
{
if(sig)
}
}
}
#else
if(pid>=0)
else
#endif /* SIGTSTP */
}
else
{
{
#ifdef SIGTSTP
job_unstop(pw);
#endif /* SIGTSTP */
if(r>=0)
sh_delay(.05);
}
{
#ifdef SIGTSTP
#endif /* SIGTSTP */
}
}
if(r<0 && job_string)
{
else
r = 2;
}
sh_delay(.001);
job_unlock();
return(r);
}
/*
* Get process structure from first letters of jobname
*
*/
{
register int *flag = 0;
int offset;
if(*cp=='?')
{
{
if(pz)
}
}
return(pz);
}
#else
# define job_set(x)
# define job_reset(x)
#endif /* JOBS */
/*
* Initialize the process posting array
*/
void job_clear(void)
{
job_lock();
{
{
}
}
{
}
if(njob_savelist < NJOB_SAVELIST)
#ifdef SHOPT_BGX
#endif /* SHOPT_BGX */
while(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
*/
{
#ifdef SHOPT_BGX
#else
int val;
#endif
{
job_clear();
return(0);
}
job_lock();
#ifdef SHOPT_BGX
if(join==1)
{
join = 0;
}
#endif /* SHOPT_BGX */
if(njob_savelist < NJOB_SAVELIST)
job_unpost(pw,0);
{
/* if job to join is not first move it to front */
{
job_unlink(pw);
}
}
else
{
/* join existing current job */
}
else
{
/* create a new job */
}
#if SHOPT_COSHELL
{
}
#endif /* SHOPT_COSHELL */
if(sh_isstate(SH_MONITOR))
{
}
else
#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,
#endif /* DEBUG */
#ifdef JOBS
else
#endif /* JOBS */
{
{
}
{
}
else
}
#ifdef SHOPT_BGX
if(bg)
{
else
}
#endif /* SHOPT_BGX */
lastpid = 0;
job_unlock();
}
/*
* Returns a process structure give a process id
*/
{
{
return(px);
}
}
/*
* return a pointer to a job given the job id
*/
{
{
break;
}
return(pw);
}
/*
* print a signal message
*/
{
{
else
dump = "";
if(sh_isstate(SH_INTERACTIVE))
else
}
}
/*
* 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
*/
{
register int jobid = 0;
char intr = 0;
if(pid <= 0)
{
if(pid==0)
goto done;
intr = 1;
}
job_lock();
if(pid > 1)
{
{
/* check to see whether job status has been saved */
exitset();
job_unlock();
return(nochild);
}
{
job_unlock();
return(nochild);
}
if(!intr)
}
#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;
{
}
while(1)
{
{
{
{
if(sh_isoption(SH_NOTIFY))
{
}
{
}
}
}
}
{
#ifdef SIGTSTP
{
{
break;
}
else /* ignore stop when non-interactive */
}
else
#endif /* SIGTSTP */
{
{
}
{
/* last process in job */
if(sh_isoption(SH_PIPEFAIL))
{
/* last non-zero exit */
{
break;
}
if(!px)
}
px = 0;
if(px)
{
if(intr)
}
}
break;
continue;
}
}
continue;
if(nochild)
break;
break;
}
pwfg = 0;
job_unlock();
if(pid==1)
return(nochild);
exitset();
{
/* propogate keyboard interrupts to parent */
#ifdef SIGTSTP
{
}
#endif /* SIGTSTP */
}
else
{
{
}
}
done:
return(nochild);
{
job_lock();
{
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'
*/
{
register const char *msg;
job_lock();
{
job_unlock();
return(1);
}
if(bgflag=='d')
{
job_unlock();
return(0);
}
#ifdef SIGTSTP
if(bgflag=='b')
{
#ifdef SHOPT_BGX
#endif
msg = "&";
}
else
{
job_unlink(pw);
msg = "";
}
if(bgflag=='f')
{
{
job_unlock();
return(1);
}
#ifdef SHOPT_BGX
#endif
}
job_unstop(pw);
#endif /* SIGTSTP */
job_unlock();
return(0);
}
#ifdef SIGTSTP
/*
* Set the foreground group associated with a job
*/
{
}
/*
* turn off STOP state of a process group and send CONT signals
*/
{
register int num = 0;
{
{
num++;
}
}
if(num!=0)
{
}
}
#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
*/
{
/* 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);
#endif /* DEBUG */
#ifdef SHOPT_BGX
return(pw);
#endif /* SHOPT_BGX */
if(pw)
return(pw);
/* all processes complete, unpost job */
{
/* save the exit status for background jobs */
{
/* save status for future wait */
{
}
}
}
#ifdef DEBUG
sfprintf(sfstderr,"ksh: job line %4d: free pid=%d critical=%d job=%d\n",__LINE__,getpid(),job.in_critical,pwtop->p_job);
#endif /* DEBUG */
return((struct process*)0);
}
/*
* unlink a job form the job list
*/
{
{
return;
}
{
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 char *freeword;
/* skip to first word with a free slot */
if(j >= jmax)
{
{
break;
}
j /= CHAR_BIT;
if(j >= jmax)
return(-1);
}
j *= CHAR_BIT;
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;
}
{
#ifdef apollo
/*
* This code handles the formatting for the apollo specific signal
* SIGAPOLLO.
*/
extern char *apollo_error(void);
return( apollo_error() );
#endif /* apollo */
{
if(sig>sh.gd->sigruntime[SH_SIGRTMIN]+(sh.gd->sigruntime[SH_SIGRTMAX]-sig<=sh.gd->sigruntime[SH_SIGRTMIN])/2)
else
return(sigrt);
}
#endif
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.
*/
{
register int r= -1;
{
break;
break;
}
if(jp)
{
r = 0;
if(pid)
if(jpold)
else
if(njob_savelist < NJOB_SAVELIST)
{
job_savelist = jp;
}
else
}
return(r);
}
void *job_subsave(void)
{
job_lock();
job_unlock();
return((void*)bp);
}
{
job_lock();
{
{
}
else
}
{
continue;
job_unpost(pw,0);
}
/*
* queue up old lists for disposal by job_reap()
*/
job_unlock();
}
int sh_waitsafe(void)
{
}
{
#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();
break;
case 0:
job_unlock();
job.in_critical = 0;
break;
default:
job_unlock();
break;
}
}