about summary refs log tree commit diff
path: root/Src/jobs.c
diff options
context:
space:
mode:
Diffstat (limited to 'Src/jobs.c')
-rw-r--r--Src/jobs.c1361
1 files changed, 1361 insertions, 0 deletions
diff --git a/Src/jobs.c b/Src/jobs.c
new file mode 100644
index 000000000..331902d9f
--- /dev/null
+++ b/Src/jobs.c
@@ -0,0 +1,1361 @@
+/*
+ * jobs.c - job control
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "jobs.pro"
+
+/* the process group of the shell */
+
+/**/
+pid_t mypgrp;
+ 
+/* the job we are working on */
+ 
+/**/
+int thisjob;
+
+/* the current job (+) */
+ 
+/**/
+int curjob;
+ 
+/* the previous job (-) */
+ 
+/**/
+int prevjob;
+ 
+/* the job table */
+ 
+/**/
+struct job jobtab[MAXJOB];
+ 
+/* shell timings */
+ 
+/**/
+struct tms shtms;
+ 
+/* 1 if ttyctl -f has been executed */
+ 
+/**/
+int ttyfrozen;
+ 
+/* empty job structure for quick clearing of jobtab entries */
+
+static struct job zero;		/* static variables are initialized to zero */
+
+static struct timeval dtimeval, now;
+
+/* Diff two timevals for elapsed-time computations */
+
+/**/
+static struct timeval *
+dtime(struct timeval *dt, struct timeval *t1, struct timeval *t2)
+{
+    dt->tv_sec = t2->tv_sec - t1->tv_sec;
+    dt->tv_usec = t2->tv_usec - t1->tv_usec;
+    if (dt->tv_usec < 0) {
+	dt->tv_usec += 1000000.0;
+	dt->tv_sec -= 1.0;
+    }
+    return dt;
+}
+
+/* change job table entry from stopped to running */
+
+/**/
+void
+makerunning(Job jn)
+{
+    Process pn;
+
+    jn->stat &= ~STAT_STOPPED;
+    for (pn = jn->procs; pn; pn = pn->next)
+	if (WIFSTOPPED(pn->status) && 
+	    (!(jn->stat & STAT_SUPERJOB) || pn->next))
+	    pn->status = SP_RUNNING;
+
+    if (jn->stat & STAT_SUPERJOB)
+	makerunning(jobtab + jn->other);
+}
+
+/* Find process and job associated with pid.         *
+ * Return 1 if search was successful, else return 0. */
+
+/**/
+int
+findproc(pid_t pid, Job *jptr, Process *pptr)
+{
+    Process pn;
+    int i;
+
+    for (i = 1; i < MAXJOB; i++)
+	for (pn = jobtab[i].procs; pn; pn = pn->next)
+	    if (pn->pid == pid) {
+		*pptr = pn;
+		*jptr = jobtab + i;
+		return 1;
+	    }
+
+    return 0;
+}
+
+/* Update status of process that we have just WAIT'ed for */
+
+/**/
+void
+update_process(Process pn, int status)
+{
+    struct timezone dummy_tz;
+    long childs, childu;
+
+    childs = shtms.tms_cstime;
+    childu = shtms.tms_cutime;
+    times(&shtms);                          /* get time-accounting info          */
+
+    pn->status = status;                    /* save the status returned by WAIT  */
+    pn->ti.st  = shtms.tms_cstime - childs; /* compute process system space time */
+    pn->ti.ut  = shtms.tms_cutime - childu; /* compute process user space time   */
+
+    gettimeofday(&pn->endtime, &dummy_tz);  /* record time process exited        */
+}
+
+/* Update status of job, possibly printing it */
+
+/**/
+void
+update_job(Job jn)
+{
+    Process pn;
+    int job;
+    int val = 0, status = 0;
+    int somestopped = 0, inforeground = 0;
+
+    for (pn = jn->procs; pn; pn = pn->next) {
+	if (pn->status == SP_RUNNING)      /* some processes in this job are running       */
+	    return;                        /* so no need to update job table entry         */
+	if (WIFSTOPPED(pn->status))        /* some processes are stopped                   */
+	    somestopped = 1;               /* so job is not done, but entry needs updating */
+	if (!pn->next)                     /* last job in pipeline determines exit status  */
+	    val = (WIFSIGNALED(pn->status)) ? 0200 | WTERMSIG(pn->status) :
+		WEXITSTATUS(pn->status);
+	if (pn->pid == jn->gleader)        /* if this process is process group leader      */
+	    status = pn->status;
+    }
+
+    job = jn - jobtab;   /* compute job number */
+
+    if (somestopped) {
+	if (jn->stty_in_env && !jn->ty) {
+	    jn->ty = (struct ttyinfo *) zalloc(sizeof(struct ttyinfo));
+	    gettyinfo(jn->ty);
+	}
+	if (jn->stat & STAT_STOPPED)
+	    return;
+    } else {                   /* job is done, so remember return value */
+	lastval2 = val;
+	/* If last process was run in the current shell, keep old status
+	 * and let it handle its own traps
+	 */
+	if (job == thisjob && !(jn->stat & STAT_CURSH)) {
+	  lastval = val;
+	  inforeground = 1;
+	}
+    }
+
+    if (shout && !ttyfrozen && !jn->stty_in_env && !zleactive &&
+	job == thisjob && !somestopped && !(jn->stat & STAT_NOSTTY))
+	gettyinfo(&shttyinfo);
+
+    if (isset(MONITOR)) {
+	pid_t pgrp = gettygrp();           /* get process group of tty      */
+
+	/* is this job in the foreground of an interactive shell? */
+	if (mypgrp != pgrp && inforeground &&
+	    (jn->gleader == pgrp || (pgrp > 1 && kill(-pgrp, 0) == -1))) {
+	    attachtty(mypgrp);
+	    adjustwinsize();   /* check window size and adjust if necessary */
+	}
+    }
+
+    if (somestopped && jn->stat & STAT_SUPERJOB)
+	return;
+    jn->stat |= (somestopped) ? STAT_CHANGED | STAT_STOPPED :
+	STAT_CHANGED | STAT_DONE;
+    if ((jn->stat & (STAT_DONE | STAT_STOPPED)) == STAT_STOPPED) {
+	prevjob = curjob;
+	curjob = job;
+    }
+    if ((isset(NOTIFY) || job == thisjob) && (jn->stat & STAT_LOCKED)) {
+	printjob(jn, !!isset(LONGLISTJOBS), 0);
+	if (zleactive)
+	    refresh();
+    }
+    if (sigtrapped[SIGCHLD] && job != thisjob)
+	dotrap(SIGCHLD);
+
+    /* When MONITOR is set, the foreground process runs in a different *
+     * process group from the shell, so the shell will not receive     *
+     * terminal signals, therefore we we pretend that the shell got    *
+     * the signal too.                                                 */
+    if (inforeground && isset(MONITOR) && WIFSIGNALED(status)) {
+	int sig = WTERMSIG(status);
+
+	if (sig == SIGINT || sig == SIGQUIT) {
+	    if (sigtrapped[sig]) {
+		dotrap(sig);
+		/* We keep the errflag as set or not by dotrap.
+		 * This is to fulfil the promise to carry on
+		 * with the jobs if trap returns zero.
+		 * Setting breaks = loops ensures a consistent return
+		 * status if inside a loop.  Maybe the code in loops
+		 * should be changed.
+		 */
+		if (errflag)
+		    breaks = loops;
+	    } else {
+		breaks = loops;
+		errflag = 1;
+	    }
+	}
+    }
+}
+
+/* set the previous job to something reasonable */
+
+/**/
+static void
+setprevjob(void)
+{
+    int i;
+
+    for (i = MAXJOB - 1; i; i--)
+	if ((jobtab[i].stat & STAT_INUSE) && (jobtab[i].stat & STAT_STOPPED) &&
+	    i != curjob && i != thisjob) {
+	    prevjob = i;
+	    return;
+	}
+
+    for (i = MAXJOB - 1; i; i--)
+	if ((jobtab[i].stat & STAT_INUSE) && i != curjob && i != thisjob) {
+	    prevjob = i;
+	    return;
+	}
+
+    prevjob = -1;
+}
+
+static long clktck = 0;
+
+/**/
+static void
+set_clktck(void)
+{
+#ifdef _SC_CLK_TCK
+    if (!clktck)
+	/* fetch clock ticks per second from *
+	 * sysconf only the first time       */
+	clktck = sysconf(_SC_CLK_TCK);
+#else
+# ifdef __NeXT__
+    /* NeXTStep 3.3 defines CLK_TCK wrongly */
+    clktck = 60;
+# else
+#  ifdef CLK_TCK
+    clktck = CLK_TCK;
+#  else
+#   ifdef HZ
+     clktck = HZ;
+#   else
+     clktck = 60;
+#   endif
+#  endif
+# endif
+#endif
+}
+
+/**/
+static void
+printhhmmss(double secs)
+{
+    int mins = (int) secs / 60;
+    int hours = mins / 60;
+
+    secs -= 60 * mins;
+    mins -= 60 * hours;
+    if (hours)
+	fprintf(stderr, "%d:%02d:%05.2f", hours, mins, secs);
+    else if (mins)
+	fprintf(stderr,      "%d:%05.2f",        mins, secs);
+    else
+	fprintf(stderr,           "%.3f",              secs);
+}
+
+/**/
+static void
+printtime(struct timeval *real, struct timeinfo *ti, char *desc)
+{
+    char *s;
+    double elapsed_time, user_time, system_time;
+    int percent;
+
+    if (!desc)
+	desc = "";
+
+    set_clktck();
+    /* go ahead and compute these, since almost every TIMEFMT will have them */
+    elapsed_time = real->tv_sec + real->tv_usec / 1000000.0;
+    user_time    = ti->ut / (double) clktck;
+    system_time  = ti->st / (double) clktck;
+    percent      =  100.0 * (ti->ut + ti->st)
+	/ (clktck * real->tv_sec + clktck * real->tv_usec / 1000000.0);
+
+    if (!(s = getsparam("TIMEFMT")))
+	s = DEFAULT_TIMEFMT;
+
+    for (; *s; s++)
+	if (*s == '%')
+	    switch (*++s) {
+	    case 'E':
+		fprintf(stderr, "%4.2fs", elapsed_time);
+		break;
+	    case 'U':
+		fprintf(stderr, "%4.2fs", user_time);
+		break;
+	    case 'S':
+		fprintf(stderr, "%4.2fs", system_time);
+		break;
+	    case '*':
+		switch (*++s) {
+		case 'E':
+		    printhhmmss(elapsed_time);
+		    break;
+		case 'U':
+		    printhhmmss(user_time);
+		    break;
+		case 'S':
+		    printhhmmss(system_time);
+		    break;
+		default:
+		    fprintf(stderr, "%%*");
+		    s--;
+		    break;
+		}
+		break;
+	    case 'P':
+		fprintf(stderr, "%d%%", percent);
+		break;
+	    case 'J':
+		fprintf(stderr, "%s", desc);
+		break;
+	    case '%':
+		putc('%', stderr);
+		break;
+	    case '\0':
+		s--;
+		break;
+	    default:
+		fprintf(stderr, "%%%c", *s);
+		break;
+	} else
+	    putc(*s, stderr);
+    putc('\n', stderr);
+    fflush(stderr);
+}
+
+/**/
+static void
+dumptime(Job jn)
+{
+    Process pn;
+
+    if (!jn->procs)
+	return;
+    for (pn = jn->procs; pn; pn = pn->next)
+	printtime(dtime(&dtimeval, &pn->bgtime, &pn->endtime), &pn->ti, pn->text);
+}
+
+/* Check whether shell should report the amount of time consumed   *
+ * by job.  This will be the case if we have preceded the command  *
+ * with the keyword time, or if REPORTTIME is non-negative and the *
+ * amount of time consumed by the job is greater than REPORTTIME   */
+
+/**/
+static int
+should_report_time(Job j)
+{
+    Value v;
+    char *s = "REPORTTIME";
+    int reporttime;
+
+    /* if the time keyword was used */
+    if (j->stat & STAT_TIMED)
+	return 1;
+
+    if (!(v = getvalue(&s, 0)) || (reporttime = getintvalue(v)) < 0)
+	return 0;
+
+    /* can this ever happen? */
+    if (!j->procs)
+	return 0;
+
+    set_clktck();
+    return ((j->procs->ti.ut + j->procs->ti.st) / clktck >= reporttime);
+}
+
+/* !(lng & 3) means jobs    *
+ *  (lng & 1) means jobs -l *
+ *  (lng & 2) means jobs -p
+ *  (lng & 4) means jobs -d
+ *
+ * synch = 0 means asynchronous
+ * synch = 1 means synchronous
+ * synch = 2 means called synchronously from jobs
+*/
+
+/**/
+void
+printjob(Job jn, int lng, int synch)
+{
+    Process pn;
+    int job = jn - jobtab, len = 9, sig, sflag = 0, llen;
+    int conted = 0, lineleng = columns, skip = 0, doputnl = 0;
+    FILE *fout = (synch == 2) ? stdout : shout;
+
+    if (jn->stat & STAT_NOPRINT)
+	return;
+
+    if (lng < 0) {
+	conted = 1;
+	lng = 0;
+    }
+
+/* find length of longest signame, check to see */
+/* if we really need to print this job          */
+
+    for (pn = jn->procs; pn; pn = pn->next) {
+	if (jn->stat & STAT_SUPERJOB &&
+	    jn->procs->status == SP_RUNNING && !pn->next)
+	    pn->status = SP_RUNNING;
+	if (pn->status != SP_RUNNING)
+	    if (WIFSIGNALED(pn->status)) {
+		sig = WTERMSIG(pn->status);
+		llen = strlen(sigmsg[sig]);
+		if (WCOREDUMP(pn->status))
+		    llen += 14;
+		if (llen > len)
+		    len = llen;
+		if (sig != SIGINT && sig != SIGPIPE)
+		    sflag = 1;
+		if (job == thisjob && sig == SIGINT)
+		    doputnl = 1;
+	    } else if (WIFSTOPPED(pn->status)) {
+		sig = WSTOPSIG(pn->status);
+		if ((int)strlen(sigmsg[sig]) > len)
+		    len = strlen(sigmsg[sig]);
+		if (job == thisjob && sig == SIGTSTP)
+		    doputnl = 1;
+	    } else if (isset(PRINTEXITVALUE) && isset(SHINSTDIN) &&
+		       WEXITSTATUS(pn->status))
+		sflag = 1;
+    }
+
+/* print if necessary */
+
+    if (interact && jobbing && ((jn->stat & STAT_STOPPED) || sflag ||
+				job != thisjob)) {
+	int len2, fline = 1;
+	Process qn;
+
+	if (!synch)
+	    trashzle();
+	if (doputnl && !synch)
+	    putc('\n', fout);
+	for (pn = jn->procs; pn;) {
+	    len2 = ((job == thisjob) ? 5 : 10) + len;	/* 2 spaces */
+	    if (lng & 3)
+		qn = pn->next;
+	    else
+		for (qn = pn->next; qn; qn = qn->next) {
+		    if (qn->status != pn->status)
+			break;
+		    if ((int)strlen(qn->text) + len2 + ((qn->next) ? 3 : 0) > lineleng)
+			break;
+		    len2 += strlen(qn->text) + 2;
+		}
+	    if (job != thisjob)
+		if (fline)
+		    fprintf(fout, "[%ld]  %c ",
+			    (long)(jn - jobtab),
+			    (job == curjob) ? '+'
+			    : (job == prevjob) ? '-' : ' ');
+		else
+		    fprintf(fout, (job > 9) ? "        " : "       ");
+	    else
+		fprintf(fout, "zsh: ");
+	    if (lng & 1)
+		fprintf(fout, "%ld ", (long) pn->pid);
+	    else if (lng & 2) {
+		pid_t x = jn->gleader;
+
+		fprintf(fout, "%ld ", (long) x);
+		do
+		    skip++;
+		while ((x /= 10));
+		skip++;
+		lng &= ~3;
+	    } else
+		fprintf(fout, "%*s", skip, "");
+	    if (pn->status == SP_RUNNING)
+		if (!conted)
+		    fprintf(fout, "running%*s", len - 7 + 2, "");
+		else
+		    fprintf(fout, "continued%*s", len - 9 + 2, "");
+	    else if (WIFEXITED(pn->status))
+		if (WEXITSTATUS(pn->status))
+		    fprintf(fout, "exit %-4d%*s", WEXITSTATUS(pn->status),
+			    len - 9 + 2, "");
+		else
+		    fprintf(fout, "done%*s", len - 4 + 2, "");
+	    else if (WIFSTOPPED(pn->status))
+		fprintf(fout, "%-*s", len + 2, sigmsg[WSTOPSIG(pn->status)]);
+	    else if (WCOREDUMP(pn->status))
+		fprintf(fout, "%s (core dumped)%*s",
+			sigmsg[WTERMSIG(pn->status)],
+			(int)(len - 14 + 2 - strlen(sigmsg[WTERMSIG(pn->status)])), "");
+	    else
+		fprintf(fout, "%-*s", len + 2, sigmsg[WTERMSIG(pn->status)]);
+	    for (; pn != qn; pn = pn->next)
+		fprintf(fout, (pn->next) ? "%s | " : "%s", pn->text);
+	    putc('\n', fout);
+	    fline = 0;
+	}
+	fflush(fout);
+    } else if (doputnl && interact && !synch) {
+	putc('\n', fout);
+	fflush(fout);
+    }
+
+/* print "(pwd now: foo)" messages: with (lng & 4) we are printing
+ * the directory where the job is running, otherwise the current directory
+ */
+
+    if ((lng & 4) || (interact && job == thisjob && strcmp(jn->pwd, pwd))) {
+	fprintf(shout, "(pwd %s: ", (lng & 4) ? "" : "now");
+	fprintdir((lng & 4) ? jn->pwd : pwd, shout);
+	fprintf(shout, ")\n");
+	fflush(shout);
+    }
+/* delete job if done */
+
+    if (jn->stat & STAT_DONE) {
+	if (should_report_time(jn))
+	    dumptime(jn);
+	deletejob(jn);
+	if (job == curjob) {
+	    curjob = prevjob;
+	    prevjob = job;
+	}
+	if (job == prevjob)
+	    setprevjob();
+    } else
+	jn->stat &= ~STAT_CHANGED;
+}
+
+/**/
+void
+deletefilelist(LinkList file_list)
+{
+    char *s;
+    if (file_list) {
+	while ((s = (char *)getlinknode(file_list))) {
+	    unlink(s);
+	    zsfree(s);
+	}
+	zfree(file_list, sizeof(struct linklist));
+    }
+}
+
+/**/
+void
+deletejob(Job jn)
+{
+    struct process *pn, *nx;
+
+    pn = jn->procs;
+    jn->procs = NULL;
+    for (; pn; pn = nx) {
+	nx = pn->next;
+	zfree(pn, sizeof(struct process));
+    }
+    zsfree(jn->pwd);
+
+    deletefilelist(jn->filelist);
+
+    if (jn->ty)
+	zfree(jn->ty, sizeof(struct ttyinfo));
+
+    *jn = zero;
+}
+
+/* add a process to the current job */
+
+/**/
+void
+addproc(pid_t pid, char *text)
+{
+    Process pn;
+    struct timezone dummy_tz;
+
+    pn = (Process) zcalloc(sizeof *pn);
+    pn->pid = pid;
+    if (text)
+	strcpy(pn->text, text);
+    else
+	*pn->text = '\0';
+    gettimeofday(&pn->bgtime, &dummy_tz);
+    pn->status = SP_RUNNING;
+    pn->next = NULL;
+
+    /* if this is the first process we are adding to *
+     * the job, then it's the group leader.          */
+    if (!jobtab[thisjob].gleader)
+	jobtab[thisjob].gleader = pid;
+
+    /* attach this process to end of process list of current job */
+    if (jobtab[thisjob].procs) {
+	Process n;
+
+	for (n = jobtab[thisjob].procs; n->next; n = n->next);
+	pn->next = NULL;
+	n->next = pn;
+    } else {
+	/* first process for this job */
+	jobtab[thisjob].procs = pn;
+    }
+    /* If the first process in the job finished before any others were *
+     * added, maybe STAT_DONE got set incorrectly.  This can happen if *
+     * a $(...) was waited for and the last existing job in the        *
+     * pipeline was already finished.  We need to be very careful that *
+     * there was no call to printjob() between then and now, else      *
+     * the job will already have been deleted from the table.          */
+    jobtab[thisjob].stat &= ~STAT_DONE;
+}
+
+/* Check if we have files to delete.  We need to check this to see *
+ * if it's all right to exec a command without forking in the last *
+ * component of subshells or after the `-c' option.                */
+
+/**/
+int
+havefiles(void)
+{
+    int i;
+
+    for (i = 1; i < MAXJOB; i++)
+	if (jobtab[i].stat && jobtab[i].filelist)
+	    return 1;
+    return 0;
+
+}
+
+/* wait for a particular process */
+
+/**/
+void
+waitforpid(pid_t pid)
+{
+    int first = 1;
+
+    /* child_block() around this loop in case #ifndef WNOHANG */
+    child_block();		/* unblocked in child_suspend() */
+    while (!errflag && (kill(pid, 0) >= 0 || errno != ESRCH)) {
+	if (first)
+	    first = 0;
+	else
+	    kill(pid, SIGCONT);
+
+	child_suspend(SIGINT);
+	child_block();
+    }
+    child_unblock();
+}
+
+/* wait for a job to finish */
+
+/**/
+static void
+waitjob(int job, int sig)
+{
+    Job jn = jobtab + job;
+
+    child_block();		 /* unblocked during child_suspend() */
+    if (jn->procs) {		 /* if any forks were done         */
+	jn->stat |= STAT_LOCKED;
+	if (jn->stat & STAT_CHANGED)
+	    printjob(jn, !!isset(LONGLISTJOBS), 1);
+	while (!errflag && jn->stat &&
+	       !(jn->stat & STAT_DONE) &&
+	       !(interact && (jn->stat & STAT_STOPPED))) {
+	    child_suspend(sig);
+	    /* Commenting this out makes ^C-ing a job started by a function
+	       stop the whole function again.  But I guess it will stop
+	       something else from working properly, we have to find out
+	       what this might be.  --oberon
+
+	    errflag = 0; */
+	    if (jn->stat & STAT_SUPERJOB) {
+		Job sj = jobtab + jn->other;
+		if (sj->stat & STAT_DONE) {
+		    struct process *p;
+		    
+		    for (p = sj->procs; p; p = p->next)
+			if (WIFSIGNALED(p->status)) {
+			    killpg(jn->gleader, WTERMSIG(p->status));
+			    kill(sj->other, SIGCONT);
+			    kill(sj->other, WTERMSIG(p->status));
+			    break;
+			}
+		    if (!p) {
+			jn->stat &= ~STAT_SUPERJOB;
+			kill(sj->other, SIGCONT);
+			deletejob(sj);
+		    }
+		    curjob = jn - jobtab;
+		}
+		else if (sj->stat & STAT_STOPPED) {
+		    struct process *p;
+
+		    jn->stat |= STAT_STOPPED;
+		    for (p = jn->procs; p; p = p->next)
+			p->status = sj->procs->status;
+		    curjob = jn - jobtab;
+		    printjob(jn, !!isset(LONGLISTJOBS), 1);
+		    break;
+		}
+	    }
+	    child_block();
+	}
+    } else
+	deletejob(jn);
+    child_unblock();
+}
+
+/* wait for running job to finish */
+
+/**/
+void
+waitjobs(void)
+{
+    waitjob(thisjob, 0);
+    thisjob = -1;
+}
+
+/* clear job table when entering subshells */
+
+/**/
+void
+clearjobtab(void)
+{
+    int i;
+
+    for (i = 1; i < MAXJOB; i++) {
+	if (jobtab[i].pwd)
+	    zsfree(jobtab[i].pwd);
+	if (jobtab[i].ty)
+	    zfree(jobtab[i].ty, sizeof(struct ttyinfo));
+    }
+
+    memset(jobtab, 0, sizeof(jobtab)); /* zero out table */
+}
+
+/* Get a free entry in the job table and initialize it. */
+
+/**/
+int
+initjob(void)
+{
+    int i;
+
+    for (i = 1; i < MAXJOB; i++)
+	if (!jobtab[i].stat) {
+	    jobtab[i].stat = STAT_INUSE;
+	    jobtab[i].pwd = ztrdup(pwd);
+	    jobtab[i].gleader = 0;
+	    return i;
+	}
+
+    zerr("job table full or recursion limit exceeded", NULL, 0);
+    return -1;
+}
+
+/* print pids for & */
+
+/**/
+void
+spawnjob(void)
+{
+    Process pn;
+
+    /* if we are not in a subshell */
+    if (!subsh) {
+	if (curjob == -1 || !(jobtab[curjob].stat & STAT_STOPPED)) {
+	    curjob = thisjob;
+	    setprevjob();
+	} else if (prevjob == -1 || !(jobtab[prevjob].stat & STAT_STOPPED))
+	    prevjob = thisjob;
+	if (interact && jobbing && jobtab[thisjob].procs) {
+	    fprintf(stderr, "[%d]", thisjob);
+	    for (pn = jobtab[thisjob].procs; pn; pn = pn->next)
+		fprintf(stderr, " %ld", (long) pn->pid);
+	    fprintf(stderr, "\n");
+	    fflush(stderr);
+	}
+    }
+    if (!jobtab[thisjob].procs)
+	deletejob(jobtab + thisjob);
+    else
+	jobtab[thisjob].stat |= STAT_LOCKED;
+    thisjob = -1;
+}
+
+/**/
+void
+shelltime(void)
+{
+    struct timeinfo ti;
+    struct timezone dummy_tz;
+    struct tms buf;
+
+    times(&buf);
+    ti.ut = buf.tms_utime;
+    ti.st = buf.tms_stime;
+    gettimeofday(&now, &dummy_tz);
+    printtime(dtime(&dtimeval, &shtimer, &now), &ti, "shell");
+    ti.ut = buf.tms_cutime;
+    ti.st = buf.tms_cstime;
+    printtime(dtime(&dtimeval, &shtimer, &now), &ti, "children");
+}
+
+/* see if jobs need printing */
+ 
+/**/
+void
+scanjobs(void)
+{
+    int i;
+ 
+    for (i = 1; i < MAXJOB; i++)
+        if (jobtab[i].stat & STAT_CHANGED)
+            printjob(jobtab + i, 0, 1);
+}
+
+/**** job control builtins ****/
+
+/* This simple function indicates whether or not s may represent      *
+ * a number.  It returns true iff s consists purely of digits and     *
+ * minuses.  Note that minus may appear more than once, and the empty *
+ * string will produce a `true' response.                             */
+
+/**/
+static int
+isanum(char *s)
+{
+    while (*s == '-' || idigit(*s))
+	s++;
+    return *s == '\0';
+}
+
+/* Make sure we have a suitable current and previous job set. */
+
+/**/
+static void
+setcurjob(void)
+{
+    if (curjob == thisjob ||
+	(curjob != -1 && !(jobtab[curjob].stat & STAT_INUSE))) {
+	curjob = prevjob;
+	setprevjob();
+	if (curjob == thisjob ||
+	    (curjob != -1 && !((jobtab[curjob].stat & STAT_INUSE) &&
+			       curjob != thisjob))) {
+	    curjob = prevjob;
+	    setprevjob();
+	}
+    }
+}
+
+/* Convert a job specifier ("%%", "%1", "%foo", "%?bar?", etc.) *
+ * to a job number.                                             */
+
+/**/
+static int
+getjob(char *s, char *prog)
+{
+    int jobnum, returnval;
+
+    /* if there is no %, treat as a name */
+    if (*s != '%')
+	goto jump;
+    s++;
+    /* "%%", "%+" and "%" all represent the current job */
+    if (*s == '%' || *s == '+' || !*s) {
+	if (curjob == -1) {
+	    zwarnnam(prog, "no current job", NULL, 0);
+	    returnval = -1;
+	    goto done;
+	}
+	returnval = curjob;
+	goto done;
+    }
+    /* "%-" represents the previous job */
+    if (*s == '-') {
+	if (prevjob == -1) {
+	    zwarnnam(prog, "no previous job", NULL, 0);
+	    returnval = -1;
+	    goto done;
+	}
+	returnval = prevjob;
+	goto done;
+    }
+    /* a digit here means we have a job number */
+    if (idigit(*s)) {
+	jobnum = atoi(s);
+	if (jobnum && jobnum < MAXJOB && jobtab[jobnum].stat &&
+	    !(jobtab[jobnum].stat & STAT_SUBJOB) && jobnum != thisjob) {
+	    returnval = jobnum;
+	    goto done;
+	}
+	zwarnnam(prog, "%%%s: no such job", s, 0);
+	returnval = -1;
+	goto done;
+    }
+    /* "%?" introduces a search string */
+    if (*s == '?') {
+	struct process *pn;
+
+	for (jobnum = MAXJOB - 1; jobnum >= 0; jobnum--)
+	    if (jobtab[jobnum].stat && !(jobtab[jobnum].stat & STAT_SUBJOB) &&
+		jobnum != thisjob)
+		for (pn = jobtab[jobnum].procs; pn; pn = pn->next)
+		    if (strstr(pn->text, s + 1)) {
+			returnval = jobnum;
+			goto done;
+		    }
+	zwarnnam(prog, "job not found: %s", s, 0);
+	returnval = -1;
+	goto done;
+    }
+  jump:
+    /* anything else is a job name, specified as a string that begins the
+    job's command */
+    if ((jobnum = findjobnam(s)) != -1) {
+	returnval = jobnum;
+	goto done;
+    }
+    /* if we get here, it is because none of the above succeeded and went
+    to done */
+    zwarnnam(prog, "job not found: %s", s, 0);
+    returnval = -1;
+  done:
+    return returnval;
+}
+
+/* For jobs -Z (which modifies the shell's name as seen in ps listings).  *
+ * hackzero is the start of the safely writable space, and hackspace is   *
+ * its length, excluding a final NUL terminator that will always be left. */
+
+static char *hackzero;
+static int hackspace;
+
+/* Initialise the jobs -Z system.  The technique is borrowed from perl: *
+ * check through the argument and environment space, to see how many of *
+ * the strings are in contiguous space.  This determines the value of   *
+ * hackspace.                                                           */
+
+/**/
+void
+init_hackzero(char **argv, char **envp)
+{
+    char *p, *q;
+
+    hackzero = *argv;
+    p = strchr(hackzero, 0);
+    while(*++argv) {
+	q = *argv;
+	if(q != p+1)
+	    goto done;
+	p = strchr(q, 0);
+    }
+    for(; *envp; envp++) {
+	q = *envp;
+	if(q != p+1)
+	    goto done;
+	p = strchr(q, 0);
+    }
+    done:
+    hackspace = p - hackzero;
+}
+
+/* bg, disown, fg, jobs, wait: most of the job control commands are     *
+ * here.  They all take the same type of argument.  Exception: wait can *
+ * take a pid or a job specifier, whereas the others only work on jobs. */
+
+/**/
+int
+bin_fg(char *name, char **argv, char *ops, int func)
+{
+    int job, lng, firstjob = -1, retval = 0;
+
+    if (ops['Z']) {
+	int len;
+
+	if(isset(RESTRICTED)) {
+	    zwarnnam(name, "-Z is restricted", NULL, 0);
+	    return 1;
+	}
+	if(!argv[0] || argv[1]) {
+	    zwarnnam(name, "-Z requires one argument", NULL, 0);
+	    return 1;
+	}
+	unmetafy(*argv, &len);
+	if(len > hackspace)
+	    len = hackspace;
+	memcpy(hackzero, *argv, len);
+	memset(hackzero + len, 0, hackspace - len);
+	return 0;
+    }
+
+    lng = (ops['l']) ? 1 : (ops['p']) ? 2 : 0;
+    if (ops['d'])
+	lng |= 4;
+    
+    if ((func == BIN_FG || func == BIN_BG) && !jobbing) {
+	/* oops... maybe bg and fg should have been disabled? */
+	zwarnnam(name, "no job control in this shell.", NULL, 0);
+	return 1;
+    }
+
+    /* If necessary, update job table. */
+    if (unset(NOTIFY))
+	scanjobs();
+
+    setcurjob();
+
+    if (func == BIN_JOBS)
+        /* If you immediately type "exit" after "jobs", this      *
+         * will prevent zexit from complaining about stopped jobs */
+	stopmsg = 2;
+    if (!*argv)
+	/* This block handles all of the default cases (no arguments).  bg,
+	fg and disown act on the current job, and jobs and wait act on all the
+	jobs. */
+ 	if (func == BIN_FG || func == BIN_BG || func == BIN_DISOWN) {
+	    /* W.r.t. the above comment, we'd better have a current job at this
+	    point or else. */
+	    if (curjob == -1 || (jobtab[curjob].stat & STAT_NOPRINT)) {
+		zwarnnam(name, "no current job", NULL, 0);
+		return 1;
+	    }
+	    firstjob = curjob;
+	} else if (func == BIN_JOBS) {
+	    /* List jobs. */
+	    for (job = 0; job != MAXJOB; job++)
+		if (job != thisjob && jobtab[job].stat) {
+		    if ((!ops['r'] && !ops['s']) ||
+			(ops['r'] && ops['s']) ||
+			(ops['r'] && !(jobtab[job].stat & STAT_STOPPED)) ||
+			(ops['s'] && jobtab[job].stat & STAT_STOPPED))
+			printjob(job + jobtab, lng, 2);
+		}
+	    return 0;
+	} else {   /* Must be BIN_WAIT, so wait for all jobs */
+	    for (job = 0; job != MAXJOB; job++)
+		if (job != thisjob && jobtab[job].stat)
+		    waitjob(job, SIGINT);
+	    return 0;
+	}
+
+    /* Defaults have been handled.  We now have an argument or two, or three...
+    In the default case for bg, fg and disown, the argument will be provided by
+    the above routine.  We now loop over the arguments. */
+    for (; (firstjob != -1) || *argv; (void)(*argv && argv++)) {
+	int stopped, ocj = thisjob;
+
+	if (func == BIN_WAIT && isanum(*argv)) {
+	    /* wait can take a pid; the others can't. */
+	    waitforpid((long)atoi(*argv));
+	    retval = lastval2;
+	    thisjob = ocj;
+	    continue;
+	}
+	/* The only type of argument allowed now is a job spec.  Check it. */
+	job = (*argv) ? getjob(*argv, name) : firstjob;
+	firstjob = -1;
+	if (job == -1) {
+	    retval = 1;
+	    break;
+	}
+	if (!(jobtab[job].stat & STAT_INUSE) ||
+	    (jobtab[job].stat & STAT_NOPRINT)) {
+	    zwarnnam(name, "no such job: %d", 0, job);
+	    return 1;
+	}
+	/* We have a job number.  Now decide what to do with it. */
+	switch (func) {
+	case BIN_FG:
+	case BIN_BG:
+	case BIN_WAIT:
+	    if (func == BIN_BG)
+		jobtab[job].stat |= STAT_NOSTTY;
+	    if ((stopped = (jobtab[job].stat & STAT_STOPPED)))
+		makerunning(jobtab + job);
+	    else if (func == BIN_BG) {
+		/* Silly to bg a job already running. */
+		zwarnnam(name, "job already in background", NULL, 0);
+		thisjob = ocj;
+		return 1;
+	    }
+	    /* It's time to shuffle the jobs around!  Reset the current job,
+	    and pick a sensible secondary job. */
+	    if (curjob == job) {
+		curjob = prevjob;
+		prevjob = (func == BIN_BG) ? -1 : job;
+	    }
+	    if (prevjob == job || prevjob == -1)
+		setprevjob();
+	    if (curjob == -1) {
+		curjob = prevjob;
+		setprevjob();
+	    }
+	    if (func != BIN_WAIT)
+		/* for bg and fg -- show the job we are operating on */
+		printjob(jobtab + job, (stopped) ? -1 : 0, 1);
+	    if (func != BIN_BG) {		/* fg or wait */
+		if (strcmp(jobtab[job].pwd, pwd)) {
+		    fprintf(shout, "(pwd : ");
+		    fprintdir(jobtab[job].pwd, shout);
+		    fprintf(shout, ")\n");
+		}
+		fflush(shout);
+		if (func != BIN_WAIT) {		/* fg */
+		    thisjob = job;
+		    attachtty(jobtab[job].gleader);
+		}
+	    }
+	    if (stopped) {
+		if (func != BIN_BG && jobtab[job].ty)
+		    settyinfo(jobtab[job].ty);
+		killjb(jobtab + job, SIGCONT);
+	    }
+	    if (func == BIN_WAIT)
+	        waitjob(job, SIGINT);
+	    if (func != BIN_BG) {
+		waitjobs();
+		retval = lastval2;
+	    }
+	    break;
+	case BIN_JOBS:
+	    printjob(job + jobtab, lng, 2);
+	    break;
+	case BIN_DISOWN:
+	    deletejob(jobtab + job);
+	    break;
+	}
+	thisjob = ocj;
+    }
+    return retval;
+}
+
+/* kill: send a signal to a process.  The process(es) may be specified *
+ * by job specifier (see above) or pid.  A signal, defaulting to       *
+ * SIGTERM, may be specified by name or number, preceded by a dash.    */
+
+/**/
+int
+bin_kill(char *nam, char **argv, char *ops, int func)
+{
+    int sig = SIGTERM;
+    int returnval = 0;
+
+    /* check for, and interpret, a signal specifier */
+    if (*argv && **argv == '-') {
+	if (idigit((*argv)[1]))
+	    /* signal specified by number */
+	    sig = atoi(*argv + 1);
+	else if ((*argv)[1] != '-' || (*argv)[2]) {
+	    char *signame;
+
+	    /* with argument "-l" display the list of signal names */
+	    if ((*argv)[1] == 'l' && (*argv)[2] == '\0') {
+		if (argv[1]) {
+		    while (*++argv) {
+			sig = zstrtol(*argv, &signame, 10);
+			if (signame == *argv) {
+			    for (sig = 1; sig <= SIGCOUNT; sig++)
+				if (!cstrpcmp(sigs + sig, &signame))
+				    break;
+			    if (sig > SIGCOUNT) {
+				zwarnnam(nam, "unknown signal: SIG%s",
+					 signame, 0);
+				returnval++;
+			    } else
+				printf("%d\n", sig);
+			} else {
+			    if (*signame) {
+				zwarnnam(nam, "unknown signal: SIG%s",
+					 signame, 0);
+				returnval++;
+			    } else {
+				if (WIFSIGNALED(sig))
+				    sig = WTERMSIG(sig);
+				else if (WIFSTOPPED(sig))
+				    sig = WSTOPSIG(sig);
+				if (1 <= sig && sig <= SIGCOUNT)
+				    printf("%s\n", sigs[sig]);
+				else
+				    printf("%d\n", sig);
+			    }
+			}
+		    }
+		    return returnval;
+		}
+		printf("%s", sigs[1]);
+		for (sig = 2; sig <= SIGCOUNT; sig++)
+		    printf(" %s", sigs[sig]);
+		putchar('\n');
+		return 0;
+	    }
+	    if ((*argv)[1] == 's' && (*argv)[2] == '\0')
+		signame = *++argv;
+	    else
+		signame = *argv + 1;
+
+	    /* check for signal matching specified name */
+	    for (sig = 1; sig <= SIGCOUNT; sig++)
+		if (!cstrpcmp(sigs + sig, &signame))
+		    break;
+	    if (*signame == '0' && !signame[1])
+		sig = 0;
+	    if (sig > SIGCOUNT) {
+		zwarnnam(nam, "unknown signal: SIG%s", signame, 0);
+		zwarnnam(nam, "type kill -l for a List of signals", NULL, 0);
+		return 1;
+	    }
+	}
+	argv++;
+    }
+
+    setcurjob();
+
+    /* Remaining arguments specify processes.  Loop over them, and send the
+    signal (number sig) to each process. */
+    for (; *argv; argv++) {
+	if (**argv == '%') {
+	    /* job specifier introduced by '%' */
+	    int p;
+
+	    if ((p = getjob(*argv, nam)) == -1) {
+		returnval++;
+		continue;
+	    }
+	    if (killjb(jobtab + p, sig) == -1) {
+		zwarnnam("kill", "kill %s failed: %e", *argv, errno);
+		returnval++;
+		continue;
+	    }
+	    /* automatically update the job table if sending a SIGCONT to a
+	    job, and send the job a SIGCONT if sending it a non-stopping
+	    signal. */
+	    if (jobtab[p].stat & STAT_STOPPED) {
+		if (sig == SIGCONT)
+		    jobtab[p].stat &= ~STAT_STOPPED;
+		if (sig != SIGKILL && sig != SIGCONT && sig != SIGTSTP
+		    && sig != SIGTTOU && sig != SIGTTIN && sig != SIGSTOP)
+		    killjb(jobtab + p, SIGCONT);
+	    }
+	} else if (!isanum(*argv)) {
+	    zwarnnam("kill", "illegal pid: %s", *argv, 0);
+	    returnval++;
+	} else if (kill(atoi(*argv), sig) == -1) {
+	    zwarnnam("kill", "kill %s failed: %e", *argv, errno);
+	    returnval++;
+	}
+    }
+    return returnval < 126 ? returnval : 1;
+}
+
+/* Suspend this shell */
+
+/**/
+int
+bin_suspend(char *name, char **argv, char *ops, int func)
+{
+    /* won't suspend a login shell, unless forced */
+    if (islogin && !ops['f']) {
+	zwarnnam(name, "can't suspend login shell", NULL, 0);
+	return 1;
+    }
+    if (jobbing) {
+	/* stop ignoring signals */
+	signal_default(SIGTTIN);
+	signal_default(SIGTSTP);
+	signal_default(SIGTTOU);
+    }
+    /* suspend ourselves with a SIGTSTP */
+    kill(0, SIGTSTP);
+    if (jobbing) {
+	/* stay suspended */
+	while (gettygrp() != mypgrp) {
+	    sleep(1);
+	    if (gettygrp() != mypgrp)
+		kill(0, SIGTTIN);
+	}
+	/* restore signal handling */
+	signal_ignore(SIGTTOU);
+	signal_ignore(SIGTSTP);
+	signal_ignore(SIGTTIN);
+    }
+    return 0;
+}
+
+/* find a job named s */
+
+/**/
+int
+findjobnam(char *s)
+{
+    int jobnum;
+
+    for (jobnum = MAXJOB - 1; jobnum >= 0; jobnum--)
+	if (!(jobtab[jobnum].stat & (STAT_SUBJOB | STAT_NOPRINT)) &&
+	    jobtab[jobnum].stat && jobtab[jobnum].procs && jobnum != thisjob &&
+	    jobtab[jobnum].procs->text && strpfx(s, jobtab[jobnum].procs->text))
+	    return jobnum;
+    return -1;
+}