diff options
author | Tanaka Akira <akr@users.sourceforge.net> | 1999-04-15 18:05:38 +0000 |
---|---|---|
committer | Tanaka Akira <akr@users.sourceforge.net> | 1999-04-15 18:05:38 +0000 |
commit | e74702b467171dbdafb56dfe354794a212e020d9 (patch) | |
tree | c295b3e9b2e93e2de10331877442615b0f37e779 /Src/jobs.c | |
parent | c175751b501a3a4cb40ad4787340a597ea769be4 (diff) | |
download | zsh-e74702b467171dbdafb56dfe354794a212e020d9.tar.gz zsh-e74702b467171dbdafb56dfe354794a212e020d9.tar.xz zsh-e74702b467171dbdafb56dfe354794a212e020d9.zip |
Initial revision
Diffstat (limited to 'Src/jobs.c')
-rw-r--r-- | Src/jobs.c | 1361 |
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; +} |