This is jobs.c in view mode; [Download] [Up]
/*
* $Id: jobs.c,v 2.17 1996/10/15 20:16:35 hzoli Exp $
*
* jobs.c - job control
*
* This file is part of zsh, the Z shell.
*
* Copyright (c) 1992-1996 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.h"
/* empty job structure for quick clearing of jobtab entries */
static struct job zero; /* static variables are initialized to zero */
struct timeval dtimeval, now;
/* Diff two timevals for elapsed-time computations */
/**/
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 (shout && job == thisjob) {
if (!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 &&
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(0); /* 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;
}
}
}
}
/* lng = 0 means jobs *
* lng = 1 means jobs -l *
* lng = 2 means jobs -p
*
* 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)
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)
if (lng == 1)
fprintf(fout, "%ld ", (long) pn->pid);
else {
pid_t x = jn->gleader;
fprintf(fout, "%ld ", (long) x);
do
skip++;
while ((x /= 10));
skip++;
lng = 0;
} 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 */
if (interact && job == thisjob && strcmp(jn->pwd, pwd)) {
fprintf(shout, "(pwd now: ");
fprintdir(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;
for (pn = jn->procs; 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;
}
/* set the previous job to something reasonable */
/**/
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;
}
/* 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;
}
}
/* 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 */
/**/
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;
}
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
}
/* 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 */
/**/
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);
}
/**/
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);
}
/**/
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);
}
/**/
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);
}
/**/
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);
}
/* check to see if user has jobs running/stopped */
/**/
void
checkjobs(void)
{
int i;
for (i = 1; i < MAXJOB; i++)
if (i != thisjob && (jobtab[i].stat & STAT_LOCKED) &&
!(jobtab[i].stat & STAT_NOPRINT))
break;
if (i < MAXJOB) {
if (jobtab[i].stat & STAT_STOPPED) {
#ifdef USE_SUSPENDED
zerr("you have suspended jobs.", NULL, 0);
#else
zerr("you have stopped jobs.", NULL, 0);
#endif
} else
zerr("you have running jobs.", NULL, 0);
stopmsg = 1;
}
}
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.