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.