root/usr/src/cmd/csh/sh.proc.c
/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*      Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T     */
/*        All Rights Reserved   */

/*
 * Copyright (c) 1980 Regents of the University of California.
 * All rights reserved. The Berkeley Software License Agreement
 * specifies the terms and conditions for redistribution.
 */

#include "sh.h"
#include "sh.dir.h"
#include "sh.proc.h"
#include "wait.h"
#include "sh.tconst.h"

/*
 * C Shell - functions that manage processes, handling hanging, termination
 */

bool    neednote;
bool    pjobs;
static struct process *pprevious;
static short    pmaxindex;
static struct process proclist;
static struct process *pcurrent;
struct process *pcurrjob;
static struct process *pholdjob;

#define BIGINDEX        9       /* largest desirable job index */

void    pjwait(struct process *);
void    pflush(struct process *);
void    pclrcurr(struct process *);
void    padd(struct command *);
void    pads(tchar *);
void    ptprint(struct process *);
void    pkill(tchar **, int);
void    pstart(struct process *, int);
void    okpcntl(void);
struct process  *pgetcurr(struct process *);
struct process  *pfind(tchar *);

/*
 * pchild - called at interrupt level by the SIGCHLD signal
 *      indicating that at least one child has terminated or stopped
 *      thus at least one wait system call will definitely return a
 *      childs status.  Top level routines (like pwait) must be sure
 *      to mask interrupts when playing with the proclist data structures!
 */
void
pchild(int signal __unused)
{
        struct process *pp;
        struct process  *fp;
        int pid;
        union wait w;
        int jobflags;
        struct rusage ru;

#ifdef TRACE
        tprintf("TRACE- pchile()\n");
#endif
loop:
        pid = csh_wait3(&w, (setintr ? WNOHANG|WUNTRACED:WNOHANG), &ru);
        /*
         * SysV sends a SIGCHLD when the child process
         * receives a SIGCONT, and result of that action is ignored here
         */
        if (w.w_status == WCONTFLG)
                return;
        if (pid <= 0) {
                if (errno == EINTR) {
                        errno = 0;
                        goto loop;
                }
                return;
        }
        for (pp = proclist.p_next; pp != PNULL; pp = pp->p_next)
                if (pid == pp->p_pid)
                        goto found;
        goto loop;
found:
        if (pid == atoi_(value(S_child /* "child" */)))
                unsetv(S_child /* "child" */);
        pp->p_flags &= ~(PRUNNING|PSTOPPED|PREPORTED);
        if (WIFSTOPPED(w)) {
                pp->p_flags |= PSTOPPED;
                pp->p_reason = w.w_stopsig;
        } else {
                if (pp->p_flags & (PTIME|PPTIME) || adrof(S_time /* "time" */))
                        (void) gettimeofday(&pp->p_etime, (struct timezone *)0);
                pp->p_rusage = ru;
                if (WIFSIGNALED(w)) {
                        if (w.w_termsig == SIGINT)
                                pp->p_flags |= PINTERRUPTED;
                        else
                                pp->p_flags |= PSIGNALED;
                        if (w.w_coredump)
                                pp->p_flags |= PDUMPED;
                        pp->p_reason = w.w_termsig;
                } else {
                        pp->p_reason = w.w_retcode;
                        if (pp->p_reason != 0)
                                pp->p_flags |= PAEXITED;
                        else
                                pp->p_flags |= PNEXITED;
                }
        }
        jobflags = 0;
        fp = pp;
        do {
                if ((fp->p_flags & (PPTIME|PRUNNING|PSTOPPED)) == 0 &&
                    !child && adrof(S_time /* "time" */) &&
                    fp->p_rusage.ru_utime.tv_sec+fp->p_rusage.ru_stime.tv_sec >=
                    atoi_(value(S_time /* "time" */)))
                        fp->p_flags |= PTIME;
                jobflags |= fp->p_flags;
        } while ((fp = fp->p_friends) != pp);
        pp->p_flags &= ~PFOREGND;
        if (pp == pp->p_friends && (pp->p_flags & PPTIME)) {
                pp->p_flags &= ~PPTIME;
                pp->p_flags |= PTIME;
        }
        if ((jobflags & (PRUNNING|PREPORTED)) == 0) {
                fp = pp;
                do {
                        if (fp->p_flags&PSTOPPED)
                                fp->p_flags |= PREPORTED;
                } while ((fp = fp->p_friends) != pp);
                while (fp->p_pid != fp->p_jobid)
                        fp = fp->p_friends;
                if (jobflags&PSTOPPED) {
                        if (pcurrent && pcurrent != fp)
                                pprevious = pcurrent;
                        pcurrent = fp;
                } else
                        pclrcurr(fp);
                if (jobflags&PFOREGND) {
                        if (jobflags & (PSIGNALED|PSTOPPED|PPTIME) ||
#ifdef IIASA
                            jobflags & PAEXITED ||
#endif
                            !eq(dcwd->di_name, fp->p_cwd->di_name)) {
                                ;       /* print in pjwait */
                        }
                } else {
                        if (jobflags&PNOTIFY || adrof(S_notify /* "notify" */)) {
                                write_string("\015\n");
                                flush();
                                (void) pprint(pp, NUMBER|NAME|REASON);
                                if ((jobflags&PSTOPPED) == 0)
                                        pflush(pp);
                        } else {
                                fp->p_flags |= PNEEDNOTE;
                                neednote++;
                        }
                }
        }
        goto loop;
}

void
pnote(void)
{
        struct process *pp;
        int flags, omask;

#ifdef TRACE
        tprintf("TRACE- pnote()\n");
#endif
        neednote = 0;
        for (pp = proclist.p_next; pp != PNULL; pp = pp->p_next) {
                if (pp->p_flags & PNEEDNOTE) {
                        omask = sigblock(sigmask(SIGCHLD));
                        pp->p_flags &= ~PNEEDNOTE;
                        flags = pprint(pp, NUMBER|NAME|REASON);
                        if ((flags&(PRUNNING|PSTOPPED)) == 0)
                                pflush(pp);
                        (void) sigsetmask(omask);
                }
        }
}

/*
 * pwait - wait for current job to terminate, maintaining integrity
 *      of current and previous job indicators.
 */
void
pwait(void)
{
        struct process *fp, *pp;
        int omask;

#ifdef TRACE
        tprintf("TRACE- pwait()\n");
#endif
        /*
         * Here's where dead procs get flushed.
         */
        omask = sigblock(sigmask(SIGCHLD));
        for (pp = (fp = &proclist)->p_next; pp != PNULL; pp = (fp = pp)->p_next)
                if (pp->p_pid == 0) {
                        fp->p_next = pp->p_next;
                        xfree(pp->p_command);
                        if (pp->p_cwd && --pp->p_cwd->di_count == 0)
                                if (pp->p_cwd->di_next == 0)
                                        dfree(pp->p_cwd);
                        xfree((tchar *)pp);
                        pp = fp;
                }
        (void) sigsetmask(omask);
        pjwait(pcurrjob);
}

/*
 * pjwait - wait for a job to finish or become stopped
 *      It is assumed to be in the foreground state (PFOREGND)
 */
void
pjwait(struct process *pp)
{
        struct process *fp;
        int jobflags, reason, omask;

#ifdef TRACE
        tprintf("TRACE- pjwait()\n");
#endif
        while (pp->p_pid != pp->p_jobid)
                pp = pp->p_friends;
        fp = pp;
        do {
                if ((fp->p_flags&(PFOREGND|PRUNNING)) == PRUNNING)
                        printf("BUG: waiting for background job!\n");
        } while ((fp = fp->p_friends) != pp);
        /*
         * Now keep pausing as long as we are not interrupted (SIGINT),
         * and the target process, or any of its friends, are running
         */
        fp = pp;
        omask = sigblock(sigmask(SIGCHLD));
        for (;;) {
                jobflags = 0;
                do
                        jobflags |= fp->p_flags;
                while ((fp = (fp->p_friends)) != pp);
                if ((jobflags & PRUNNING) == 0)
                        break;
                /*
                 * At this point, csh used to call:
                 *      sigpause(sigblock(0) &~ sigmask(SIGCHLD));
                 * expecting to receive a SIGCHLD signal from the
                 * termination of the child and to invoke the
                 * signal handler, pchild(), as a result.
                 *
                 * However, vfork() now causes a vfork()'d child to
                 * have all of its active signal handlers reset to
                 * SIG_DFL, to forstall parent memory corruption due
                 * to race conditions with signal handling.
                 *
                 * If this instance of csh is itself a child of vfork(),
                 * which can happen when the top-level csh performs a
                 * command substitution inside an i/o redirection, like:
                 *      /bin/echo foo >`/bin/echo trash`
                 * then we will never receive SIGCHLD.  To accommodate
                 * this, we wait until one of our children terminates
                 * (without actually reaping the child) and call the
                 * SIGCHLD signal handler (pchild()) directly.
                 */
                if (csh_wait_noreap() > 0)
                        pchild(SIGCHLD);        /* simulate receipt of SIGCHLD */
        }
        (void) sigsetmask(omask);
        if (tpgrp > 0)                  /* get tty back */
                (void) ioctl(FSHTTY, TIOCSPGRP,  (char *)&tpgrp);
        if ((jobflags&(PSIGNALED|PSTOPPED|PTIME)) ||
            !eq(dcwd->di_name, fp->p_cwd->di_name)) {
                if (jobflags&PSTOPPED)
                        printf("\n");
                (void) pprint(pp, AREASON|SHELLDIR);
        }
        if ((jobflags&(PINTERRUPTED|PSTOPPED)) && setintr &&
            (!gointr || !eq(gointr, S_MINUS /* "-" */))) {
                if ((jobflags & PSTOPPED) == 0)
                        pflush(pp);
                pintr1(0);
                /*NOTREACHED*/
        }
        reason = 0;
        fp = pp;
        do {
                if (fp->p_reason)
                        reason = fp->p_flags & (PSIGNALED|PINTERRUPTED) ?
                                fp->p_reason | ABN_TERM : fp->p_reason;
        } while ((fp = fp->p_friends) != pp);
        set(S_status /* "status" */, putn(reason));
        if (reason && exiterr)
                exitstat();
        pflush(pp);
}

/*
 * dowait - wait for all processes to finish
 */
void
dowait(void)
{
        struct process *pp;
        int omask;

#ifdef TRACE
        tprintf("TRACE- dowait()\n");
#endif
        pjobs++;
        omask = sigblock(sigmask(SIGCHLD));
loop:
        for (pp = proclist.p_next; pp; pp = pp->p_next)
                if (pp->p_pid && /* pp->p_pid == pp->p_jobid && */
                    pp->p_flags&PRUNNING) {
                        sigpause(0);
                        goto loop;
                }
        (void) sigsetmask(omask);
        pjobs = 0;
}

/*
 * pflushall - flush all jobs from list (e.g. at fork())
 */
void
pflushall(void)
{
        struct process  *pp;

#ifdef TRACE
        tprintf("TRACE- pflush()\n");
#endif
        for (pp = proclist.p_next; pp != PNULL; pp = pp->p_next)
                if (pp->p_pid)
                        pflush(pp);
}

/*
 * pflush - flag all process structures in the same job as the
 *      the argument process for deletion.  The actual free of the
 *      space is not done here since pflush is called at interrupt level.
 */
void
pflush(struct process *pp)
{
        struct process *np;
        int index;

#ifdef TRACE
        tprintf("TRACE- pflush()\n");
#endif
        if (pp->p_pid == 0) {
                printf("BUG: process flushed twice");
                return;
        }
        while (pp->p_pid != pp->p_jobid)
                pp = pp->p_friends;
        pclrcurr(pp);
        if (pp == pcurrjob)
                pcurrjob = 0;
        index = pp->p_index;
        np = pp;
        do {
                np->p_index = np->p_pid = 0;
                np->p_flags &= ~PNEEDNOTE;
        } while ((np = np->p_friends) != pp);
        if (index == pmaxindex) {
                for (np = proclist.p_next, index = 0; np; np = np->p_next)
                        if (np->p_index > (tchar)index)
                                index = np->p_index;
                pmaxindex = index;
        }
}

/*
 * pclrcurr - make sure the given job is not the current or previous job;
 *      pp MUST be the job leader
 */
void
pclrcurr(struct process *pp)
{

#ifdef TRACE
        tprintf("TRACE- pclrcurr()\n");
#endif
        if (pp == pcurrent)
                if (pprevious != PNULL) {
                        pcurrent = pprevious;
                        pprevious = pgetcurr(pp);
                } else {
                        pcurrent = pgetcurr(pp);
                        pprevious = pgetcurr(pp);
                }
        else if (pp == pprevious)
                pprevious = pgetcurr(pp);
}

/* +4 here is 1 for '\0', 1 ea for << >& >> */
tchar   command[PMAXLEN+4];
int     cmdlen;
tchar   *cmdp;
/*
 * palloc - allocate a process structure and fill it up.
 *      an important assumption is made that the process is running.
 */
void
palloc(int pid, struct command *t)
{
        struct process  *pp;
        int i;

#ifdef TRACE
        tprintf("TRACE- palloc()\n");
#endif
        pp = (struct process *)xcalloc(1, sizeof (struct process));
        pp->p_pid = pid;
        pp->p_flags = t->t_dflg & FAND ? PRUNNING : PRUNNING|PFOREGND;
        if (t->t_dflg & FTIME)
                pp->p_flags |= PPTIME;
        cmdp = command;
        cmdlen = 0;
        padd(t);
        *cmdp++ = 0;
        if (t->t_dflg & FPOU) {
                pp->p_flags |= PPOU;
                if (t->t_dflg & FDIAG)
                        pp->p_flags |= PDIAG;
        }
        pp->p_command = savestr(command);
        if (pcurrjob) {
                struct process *fp;
                /* careful here with interrupt level */
                pp->p_cwd = 0;
                pp->p_index = pcurrjob->p_index;
                pp->p_friends = pcurrjob;
                pp->p_jobid = pcurrjob->p_pid;
                for (fp = pcurrjob; fp->p_friends != pcurrjob; fp = fp->p_friends)
                        ;
                fp->p_friends = pp;
        } else {
                pcurrjob = pp;
                pp->p_jobid = pid;
                pp->p_friends = pp;
                pp->p_cwd = dcwd;
                dcwd->di_count++;
                if (pmaxindex < BIGINDEX)
                        pp->p_index = ++pmaxindex;
                else {
                        struct process *np;

                        for (i = 1; ; i++) {
                                for (np = proclist.p_next; np; np = np->p_next)
                                        if (np->p_index == i)
                                                goto tryagain;
                                pp->p_index = i;
                                if (i > pmaxindex)
                                        pmaxindex = i;
                                break;
                        tryagain:;
                        }
                }
                pprevious = pcurrent;
                pcurrent = pp;
        }
        pp->p_next = proclist.p_next;
        proclist.p_next = pp;
        (void) gettimeofday(&pp->p_btime, (struct timezone *)0);
}

void
padd(struct command *t)
{
        tchar **argp;

#ifdef TRACE
        tprintf("TRACE- padd()\n");
#endif
        if (t == 0)
                return;
        switch (t->t_dtyp) {

        case TPAR:
                pads(S_LBRASP /* "( " */);
                padd(t->t_dspr);
                pads(S_SPRBRA /* " )" */);
                break;

        case TCOM:
                for (argp = t->t_dcom; *argp; argp++) {
                        pads(*argp);
                        if (argp[1])
                                pads(S_SP /* " " */);
                }
                break;

        case TOR:
        case TAND:
        case TFIL:
        case TLST:
                padd(t->t_dcar);
                switch (t->t_dtyp) {
                case TOR:
                        pads(S_SPBARBARSP /* " || " */);
                        break;
                case TAND:
                        pads(S_SPANDANDSP /* " && " */);
                        break;
                case TFIL:
                        pads(S_SPBARSP /* " | " */);
                        break;
                case TLST:
                        pads(S_SEMICOLONSP /* "; " */);
                        break;
                }
                padd(t->t_dcdr);
                return;
        }
        if ((t->t_dflg & FPIN) == 0 && t->t_dlef) {
                pads((t->t_dflg & FHERE) ? S_SPLESLESSP /* " << " */ : S_SPLESSP /* " < " */);
                pads(t->t_dlef);
        }
        if ((t->t_dflg & FPOU) == 0 && t->t_drit) {
                pads((t->t_dflg & FCAT) ? S_SPGTRGTRSP /* " >>" */ : S_SPGTR /* " >" */);
                if (t->t_dflg & FDIAG)
                        pads(S_AND /* "&" */);
                pads(S_SP /* " " */);
                pads(t->t_drit);
        }
}

void
pads(tchar *cp)
{
        int i = strlen_(cp);

#ifdef TRACE
        tprintf("TRACE- pads()\n");
#endif
        if (cmdlen >= PMAXLEN)
                return;
        if (cmdlen + i >= PMAXLEN) {
                (void) strcpy_(cmdp, S_SPPPP /* " ..." */);
                cmdlen = PMAXLEN;
                cmdp += 4;
                return;
        }
        (void) strcpy_(cmdp, cp);
        cmdp += i;
        cmdlen += i;
}

/*
 * psavejob - temporarily save the current job on a one level stack
 *      so another job can be created.  Used for { } in exp6
 *      and `` in globbing.
 */
void
psavejob(void)
{

#ifdef TRACE
        tprintf("TRACE- psavejob()\n");
#endif
        pholdjob = pcurrjob;
        pcurrjob = PNULL;
}

/*
 * prestjob - opposite of psavejob.  This may be missed if we are interrupted
 *      somewhere, but pendjob cleans up anyway.
 */
void
prestjob(void)
{

#ifdef TRACE
        tprintf("TRACE- prestjob()\n");
#endif
        pcurrjob = pholdjob;
        pholdjob = PNULL;
}

/*
 * pendjob - indicate that a job (set of commands) has been completed
 *      or is about to begin.
 */
void
pendjob(void)
{
        struct process *pp, *tp;

#ifdef TRACE
        tprintf("TRACE- pendjob()\n");
#endif
        if (pcurrjob && (pcurrjob->p_flags&(PFOREGND|PSTOPPED)) == 0) {
                pp = pcurrjob;
                while (pp->p_pid != pp->p_jobid)
                        pp = pp->p_friends;
                printf("[%d]", pp->p_index);
                tp = pp;
                do {
                        printf(" %d", pp->p_pid);
                        pp = pp->p_friends;
                } while (pp != tp);
                printf("\n");
        }
        pholdjob = pcurrjob = 0;
}

/*
 * pprint - print a job
 */
int
pprint(struct process *pp, int flag)
{
        int status, reason;
        struct process *tp;
        extern char *linp, linbuf[];
        int jobflags, pstatus;
        char *format;

#ifdef TRACE
        tprintf("TRACE- pprint()\n");
#endif
        while (pp->p_pid != pp->p_jobid)
                pp = pp->p_friends;
        if (pp == pp->p_friends && (pp->p_flags & PPTIME)) {
                pp->p_flags &= ~PPTIME;
                pp->p_flags |= PTIME;
        }
        tp = pp;
        status = reason = -1;
        jobflags = 0;
        do {
                jobflags |= pp->p_flags;
                pstatus = pp->p_flags & PALLSTATES;
                if (tp != pp && linp != linbuf && !(flag&FANCY) &&
                    (pstatus == status && pp->p_reason == reason ||
                    !(flag&REASON)))
                        printf(" ");
                else {
                        if (tp != pp && linp != linbuf)
                                printf("\n");
                        if (flag&NUMBER)
                                if (pp == tp)
                                        printf("[%d]%s %c ", pp->p_index,
                                            pp->p_index < 10 ? " " : "",
                                            pp == pcurrent ? '+' :
                                                (pp == pprevious ? (tchar) '-'
                                                        : (tchar) ' '));
                                else
                                        printf("       ");
                        if (flag&FANCY)
                                printf("%5d ", pp->p_pid);
                        if (flag&(REASON|AREASON)) {
                                if (flag&NAME)
                                        format = "%-21s";
                                else
                                        format = "%s";
                                if (pstatus == status)
                                        if (pp->p_reason == reason) {
                                                printf(format, "");
                                                goto prcomd;
                                        } else
                                                reason = pp->p_reason;
                                else {
                                        status = pstatus;
                                        reason = pp->p_reason;
                                }
                                switch (status) {

                                case PRUNNING:
                                        printf(format, "Running ");
                                        break;

                                case PINTERRUPTED:
                                case PSTOPPED:
                                case PSIGNALED:
                                        if ((flag&(REASON|AREASON))
                                            && reason != SIGINT
                                            && reason != SIGPIPE)
                                                printf(format,
                                                    strsignal(pp->p_reason));
                                        break;

                                case PNEXITED:
                                case PAEXITED:
                                        if (flag & REASON)
                                                if (pp->p_reason)
                                                        printf("Exit %-16d", pp->p_reason);
                                                else
                                                        printf(format, "Done");
                                        break;

                                default:
                                        printf("BUG: status=%-9o", status);
                                }
                        }
                }
prcomd:
                if (flag&NAME) {
                        printf("%t", pp->p_command);
                        if (pp->p_flags & PPOU)
                                printf(" |");
                        if (pp->p_flags & PDIAG)
                                printf("&");
                }
                if (flag&(REASON|AREASON) && pp->p_flags&PDUMPED)
                        printf(" (core dumped)");
                if (tp == pp->p_friends) {
                        if (flag&AMPERSAND)
                                printf(" &");
                        if (flag&JOBDIR &&
                            !eq(tp->p_cwd->di_name, dcwd->di_name)) {
                                printf(" (wd: ");
                                dtildepr(value(S_home /* "home" */), tp->p_cwd->di_name);
                                printf(")");
                        }
                }
                if (pp->p_flags&PPTIME && !(status&(PSTOPPED|PRUNNING))) {
                        if (linp != linbuf)
                                printf("\n\t");
                        { static struct rusage zru;
                            prusage(&zru, &pp->p_rusage, &pp->p_etime,
                            &pp->p_btime);
                        }
                }
                if (tp == pp->p_friends) {
                        if (linp != linbuf)
                                printf("\n");
                        if (flag&SHELLDIR && !eq(tp->p_cwd->di_name, dcwd->di_name)) {
                                printf("(wd now: ");
                                dtildepr(value(S_home /* "home" */), dcwd->di_name);
                                printf(")\n");
                        }
                }
        } while ((pp = pp->p_friends) != tp);
        if (jobflags&PTIME && (jobflags&(PSTOPPED|PRUNNING)) == 0) {
                if (jobflags & NUMBER)
                        printf("       ");
                ptprint(tp);
        }
        return (jobflags);
}

void
ptprint(struct process *tp)
{
        struct timeval tetime, diff;
        static struct timeval ztime;
        struct rusage ru;
        static struct rusage zru;
        struct process *pp = tp;

#ifdef TRACE
        tprintf("TRACE- ptprint()\n");
#endif
        ru = zru;
        tetime = ztime;
        do {
                ruadd(&ru, &pp->p_rusage);
                tvsub(&diff, &pp->p_etime, &pp->p_btime);
                if (timercmp(&diff, &tetime, >))
                        tetime = diff;
        } while ((pp = pp->p_friends) != tp);
        prusage(&zru, &ru, &tetime, &ztime);
}

/*
 * dojobs - print all jobs
 */
void
dojobs(tchar **v)
{
        struct process *pp;
        int flag = NUMBER|NAME|REASON;
        int i;

#ifdef TRACE
        tprintf("TRACE- dojobs()\n");
#endif
        if (chkstop)
                chkstop = 2;
        if (*++v) {
                if (v[1] || !eq(*v, S_DASHl /* "-l" */))
                        error("Usage: jobs [ -l ]");
                flag |= FANCY|JOBDIR;
        }
        for (i = 1; i <= pmaxindex; i++)
                for (pp = proclist.p_next; pp; pp = pp->p_next)
                        if (pp->p_index == i && pp->p_pid == pp->p_jobid) {
                                pp->p_flags &= ~PNEEDNOTE;
                                if (!(pprint(pp, flag) & (PRUNNING|PSTOPPED)))
                                        pflush(pp);
                                break;
                        }
}

/*
 * dofg - builtin - put the job into the foreground
 */
void
dofg(tchar **v)
{
        struct process *pp;

#ifdef TRACE
        tprintf("TRACE- dofg()\n");
#endif
        okpcntl();
        ++v;
        do {
                pp = pfind(*v);
                pstart(pp, 1);
                pjwait(pp);
        } while (*v && *++v);
}

/*
 * %... - builtin - put the job into the foreground
 */
void
dofg1(tchar **v)
{
        struct process *pp;

#ifdef TRACE
        tprintf("TRACE- untty()\n");
#endif
        okpcntl();
        pp = pfind(v[0]);
        pstart(pp, 1);
        pjwait(pp);
}

/*
 * dobg - builtin - put the job into the background
 */
void
dobg(tchar **v)
{
        struct process *pp;

#ifdef TRACE
        tprintf("TRACE- dobg()\n");
#endif
        okpcntl();
        ++v;
        do {
                pp = pfind(*v);
                pstart(pp, 0);
        } while (*v && *++v);
}

/*
 * %... & - builtin - put the job into the background
 */
void
dobg1(tchar **v)
{
        struct process *pp;

#ifdef TRACE
        tprintf("TRACE- dobg1()\n");
#endif
        pp = pfind(v[0]);
        pstart(pp, 0);
}

/*
 * dostop - builtin - stop the job
 */
void
dostop(tchar **v)
{

#ifdef TRACE
        tprintf("TRACE- dostop()\n");
#endif
        pkill(++v, SIGSTOP);
}

/*
 * dokill - builtin - superset of kill (1)
 */
void
dokill(tchar **v)
{
        int signum;
        tchar *name;

#ifdef TRACE
        tprintf("TRACE- dokill()\n");
#endif
        v++;
        if (v[0] && v[0][0] == '-') {
                if (v[0][1] == 'l') {
                        for (signum = 1; signum <= NSIG-1; signum++) {
                                char    sbuf[BUFSIZ];
                                if (sig2str(signum, sbuf) == 0)
                                        printf("%s ", sbuf);
                                if (signum % 8 == 0)
                                        Putchar('\n');
                        }
                        Putchar('\n');
                        return;
                }
                if (digit(v[0][1])) {
                        if (chkalldigit_(v[0]+1) != 0) {
                                setname(v[0]+1);
                                bferr("Unknown signal; kill -l lists signals");
                        }
                        signum = atoi_(v[0]+1);
                        if (signum < 0 || signum > NSIG)
                                bferr("Bad signal number");
                } else {
                        int     signo;
                        char    sbuf[BUFSIZ];
                        name = &v[0][1];
                        tstostr(sbuf, name);
                        if (str2sig(sbuf, &signo) == 0) {
                                signum = signo;
                                goto gotsig;
                        }
                        if (eq(name, S_IOT /* "IOT" */)) {
                                signum = SIGABRT;
                                goto gotsig;
                        }
                        setname(name);
                        bferr("Unknown signal; kill -l lists signals");
                }
gotsig:
                v++;
        } else
                signum = SIGTERM;
        pkill(v, signum);
}

void
pkill(tchar **v, int signum)
{
        struct process *pp, *np;
        int jobflags = 0;
        int omask, pid, err = 0;
        tchar *cp;

#ifdef TRACE
        tprintf("TRACE- pkill()\n");
#endif
        omask = sigmask(SIGCHLD);
        if (setintr)
                omask |= sigmask(SIGINT);
        omask = sigblock(omask) & ~omask;
        while (*v) {
                cp = globone(*v);
                if (*cp == '%') {
                        np = pp = pfind(cp);
                        do
                                jobflags |= np->p_flags;
                        while ((np = np->p_friends) != pp);
                        switch (signum) {

                        case SIGSTOP:
                        case SIGTSTP:
                        case SIGTTIN:
                        case SIGTTOU:
                                if ((jobflags & PRUNNING) == 0) {
                                        /* %s -> %t */
                                        printf("%t: Already stopped\n", cp);
                                        err++;
                                        goto cont;
                                }
                        }
                        if (killpg(pp->p_jobid, signum) < 0) {
                                /* %s -> %t */
                                printf("%t: ", cp);
                                printf("%s\n", strerror(errno));
                                err++;
                        }
                        if (signum == SIGTERM || signum == SIGHUP)
                                (void) killpg(pp->p_jobid, SIGCONT);
                } else if (!(digit(*cp) || *cp == '-'))
                        bferr("Arguments should be jobs or process id's");
                else {
                        pid = atoi_(cp);
                        if (kill(pid, signum) < 0) {
                                printf("%d: ", pid);
                                printf("%s\n", strerror(errno));
                                err++;
                                goto cont;
                        }
                        if (signum == SIGTERM || signum == SIGHUP)
                                (void) kill(pid, SIGCONT);
                }
cont:
                xfree(cp);
                v++;
        }
        (void) sigsetmask(omask);
        if (err)
                error(NULL);
}

/*
 * pstart - start the job in foreground/background
 */
void
pstart(struct process *pp, int foregnd)
{
        struct process *np;
        int omask, jobflags = 0;

#ifdef TRACE
        tprintf("TRACE- pstart()\n");
#endif
        omask = sigblock(sigmask(SIGCHLD));
        np = pp;
        do {
                jobflags |= np->p_flags;
                if (np->p_flags&(PRUNNING|PSTOPPED)) {
                        np->p_flags |= PRUNNING;
                        np->p_flags &= ~PSTOPPED;
                        if (foregnd)
                                np->p_flags |= PFOREGND;
                        else
                                np->p_flags &= ~PFOREGND;
                }
        } while ((np = np->p_friends) != pp);

        if (foregnd)
                pclrcurr(pp);
        else
        {
                if (pprevious && (pprevious->p_flags & PSTOPPED))
                {
                        pcurrent = pprevious;
                        pprevious = pgetcurr(PNULL);
                }
                else
                {
                        pcurrent = pgetcurr(pp);
                        if (!pcurrent || (pcurrent->p_flags & PRUNNING))
                                pcurrent = pp;
                        else
                                pprevious = pp;
                }
        }
        (void) pprint(pp, foregnd ? NAME|JOBDIR : NUMBER|NAME|AMPERSAND);
        if (foregnd)
                (void) ioctl(FSHTTY, TIOCSPGRP,  (char *)&pp->p_jobid);
        if (jobflags&PSTOPPED)
                (void) killpg(pp->p_jobid, SIGCONT);
        (void) sigsetmask(omask);
}

void
panystop(int neednl)
{
        struct process *pp;

#ifdef TRACE
        tprintf("TRACE- panystop()\n");
#endif
        chkstop = 2;
        for (pp = proclist.p_next; pp; pp = pp->p_next)
                if (pp->p_flags & PSTOPPED)
                        error("\nThere are stopped jobs" + 1 - neednl);
}

struct process *
pfind(tchar *cp)
{
        struct process *pp, *np;

#ifdef TRACE
        tprintf("TRACE- pfind()\n");
#endif
        if (cp == 0 || cp[1] == 0 || eq(cp, S_PARCENTPARCENT /* "%%" */) ||
                                        eq(cp, S_PARCENTPLUS /* "%+" */)) {
                if (pcurrent == PNULL)
                        if ((pcurrent = pgetcurr(PNULL)) == PNULL)
                                bferr("No current job");
                return (pcurrent);
        }
        if (eq(cp, S_PARCENTMINUS /* "%-" */) ||
            eq(cp, S_PARCENTSHARP /* "%#" */)) {
                if (pprevious == PNULL)
                        bferr("No previous job");
                return (pprevious);
        }
        if (digit(cp[1])) {
                int index = atoi_(cp+1);
                for (pp = proclist.p_next; pp; pp = pp->p_next)
                        if (pp->p_index == index && pp->p_pid == pp->p_jobid)
                                return (pp);
                bferr("No such job");
        }
        np = PNULL;
        for (pp = proclist.p_next; pp; pp = pp->p_next)
                if (pp->p_pid == pp->p_jobid) {
                        if (cp[1] == '?') {
                                tchar *dp;
                                for (dp = pp->p_command; *dp; dp++) {
                                        if (*dp != cp[2])
                                                continue;
                                        if (prefix(cp+2, dp))
                                                goto match;
                                }
                        } else if (prefix(cp+1, pp->p_command)) {
match:
                                if (np)
                                        bferr("Ambiguous");
                                np = pp;
                        }
                }
        if (np)
                return (np);
        if (cp[1] == '?')
                bferr("No job matches pattern");
        else
                bferr("No such job");
        /*NOTREACHED*/
}

/*
 * pgetcurr - find most recent job that is not pp, preferably stopped
 */
struct process *
pgetcurr(struct process *pp)
{
        struct process *np;
        struct process *xp = PNULL;

#ifdef TRACE
        tprintf("TRACE- pgetcurr()\n");
#endif
        for (np = proclist.p_next; np; np = np->p_next)
                if (np != pcurrent && np != pp && np->p_pid &&
                    np->p_pid == np->p_jobid) {
                        if (np->p_flags & PSTOPPED)
                                return (np);
                        if (xp == PNULL)
                                xp = np;
                }
        return (xp);
}

/*
 * donotify - flag the job so as to report termination asynchronously
 */
void
donotify(tchar **v)
{
        struct process *pp;

#ifdef TRACE
        tprintf("TRACE- donotify()\n");
#endif
        pp = pfind(*++v);
        pp->p_flags |= PNOTIFY;
}

/*
 * Do the fork and whatever should be done in the child side that
 * should not be done if we are not forking at all (like for simple builtin's)
 * Also do everything that needs any signals fiddled with in the parent side
 *
 * Wanttty tells whether process and/or tty pgrps are to be manipulated:
 *      -1:     leave tty alone; inherit pgrp from parent
 *       0:     already have tty; manipulate process pgrps only
 *       1:     want to claim tty; manipulate process and tty pgrps
 * It is usually just the value of tpgrp.
 *
 * argument:
 *       t:     command we are forking for
 */
int
pfork(struct command *t, int wanttty)
{
        int pid;
        bool ignint = 0;
        int pgrp, omask;
        int child_pid;

#ifdef TRACE
        tprintf("TRACE- pfork()\n");
#endif
        /*
         * A child will be uninterruptible only under very special
         * conditions. Remember that the semantics of '&' is
         * implemented by disconnecting the process from the tty so
         * signals do not need to ignored just for '&'.
         * Thus signals are set to default action for children unless:
         *      we have had an "onintr -" (then specifically ignored)
         *      we are not playing with signals (inherit action)
         */
        if (setintr)
                ignint = (tpgrp == -1 && (t->t_dflg&FINT))
                    || (gointr && eq(gointr, S_MINUS /* "-" */));
        /*
         * Hold SIGCHLD until we have the process installed in our table.
         */
        omask = sigblock(sigmask(SIGCHLD));
        while ((pid = fork()) < 0)
                if (setintr == 0)
                        sleep(FORKSLEEP);
                else {
                        (void) sigsetmask(omask);
                        error("Fork failed");
                }

        /*
         * setup the process group
         */
        if (pid == 0)
                child_pid = getpid();
        else
                child_pid = pid;
        pgrp = pcurrjob ? pcurrjob->p_jobid : child_pid;

        if (pid == 0) {
                int sigttou;
                settimes();
                pflushall();
                pcurrjob = PNULL;
                child++;
                if (setintr) {
                        setintr = 0;            /* until I think otherwise */
                        /*
                         * Children just get blown away on SIGINT, SIGQUIT
                         * unless "onintr -" seen.
                         */
                        (void) signal(SIGINT, ignint ? SIG_IGN : SIG_DFL);
                        (void) signal(SIGQUIT, ignint ? SIG_IGN : SIG_DFL);
                        if (wanttty >= 0) {
                                /* make stoppable */
                                (void) signal(SIGTSTP, SIG_DFL);
                                (void) signal(SIGTTIN, SIG_DFL);
                                (void) signal(SIGTTOU, SIG_DFL);
                        }
                        (void) signal(SIGTERM, parterm);
                } else if (tpgrp == -1 && (t->t_dflg&FINT)) {
                        (void) signal(SIGINT, SIG_IGN);
                        (void) signal(SIGQUIT, SIG_IGN);
                }
                if (wanttty >= 0 && tpgrp >= 0)
                        (void) setpgid(0, pgrp);
                if (wanttty > 0) {
                        sigttou = sigblock(sigmask(SIGTTOU) |
                                            sigmask(SIGTTIN) |
                                            sigmask(SIGTSTP));
                        (void) ioctl(FSHTTY, TIOCSPGRP,  (char *)&pgrp);
                        sigsetmask(sigttou);
                }
                if (tpgrp > 0)
                        tpgrp = 0;              /* gave tty away */
                /*
                 * Nohup and nice apply only to TCOM's but it would be
                 * nice (?!?) if you could say "nohup (foo;bar)"
                 * Then the parser would have to know about nice/nohup/time
                 */
                if (t->t_dflg & FNOHUP)
                        (void) signal(SIGHUP, SIG_IGN);
                if (t->t_dflg & FNICE)
                        (void) setpriority(PRIO_PROCESS, 0, t->t_nice);
        } else {
                if (wanttty >= 0 && tpgrp >= 0)
                        setpgid(pid, pgrp);
                palloc(pid, t);
                (void) sigsetmask(omask);
        }

        return (pid);
}

void
okpcntl(void)
{
#ifdef TRACE
        tprintf("TRACE- okpcntl()\n");
#endif

        if (tpgrp == -1)
                error("No job control in this shell");
        if (tpgrp == 0)
                error("No job control in subshells");
}

void
hupforegnd(void)
{
        struct process *pp;
        int omask;

        omask = sigblock(sigmask(SIGCHLD));
        for (pp = (&proclist)->p_next; pp != PNULL; pp = pp->p_next)
                if (pp->p_pid > 0) {
                        if (pp->p_flags & PFOREGND)
                                (void) kill(pp->p_pid, SIGHUP);
                }
        (void) sigsetmask(omask);
}