diff options
Diffstat (limited to 'dotfiles/system/.zsh/modules/Src/jobs.c')
| -rw-r--r-- | dotfiles/system/.zsh/modules/Src/jobs.c | 2894 |
1 files changed, 2894 insertions, 0 deletions
diff --git a/dotfiles/system/.zsh/modules/Src/jobs.c b/dotfiles/system/.zsh/modules/Src/jobs.c new file mode 100644 index 0000000..38b3d89 --- /dev/null +++ b/dotfiles/system/.zsh/modules/Src/jobs.c @@ -0,0 +1,2894 @@ +/* + * 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 at startup (equal to mypgprp, except + when we started without being process group leader */ + +/**/ +mod_export pid_t origpgrp; + +/* the process group of the shell */ + +/**/ +mod_export pid_t mypgrp; + +/* the job we are working on */ + +/**/ +mod_export int thisjob; + +/* the current job (+) */ + +/**/ +mod_export int curjob; + +/* the previous job (-) */ + +/**/ +mod_export int prevjob; + +/* the job table */ + +/**/ +mod_export struct job *jobtab; + +/* Size of the job table. */ + +/**/ +mod_export int jobtabsize; + +/* The highest numbered job in the jobtable */ + +/**/ +mod_export int maxjob; + +/* If we have entered a subshell, the original shell's job table. */ +static struct job *oldjobtab; + +/* The size of that. */ +static int oldmaxjob; + +/* shell timings */ + +/**/ +#ifdef HAVE_GETRUSAGE +/**/ +static struct rusage child_usage; +/**/ +#else +/**/ +static struct tms shtms; +/**/ +#endif + +/* 1 if ttyctl -f has been executed */ + +/**/ +mod_export int ttyfrozen; + +/* Previous values of errflag and breaks if the signal handler had to + * change them. And a flag saying if it did that. */ + +/**/ +int prev_errflag, prev_breaks, errbrk_saved; + +/**/ +int numpipestats, pipestats[MAX_PIPESTATS]; + +/* 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 0 + if (WIFSTOPPED(pn->status) && + (!(jn->stat & STAT_SUPERJOB) || pn->next)) + pn->status = SP_RUNNING; +#endif + if (WIFSTOPPED(pn->status)) + 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, int aux) +{ + Process pn; + int i; + + *jptr = NULL; + *pptr = NULL; + for (i = 1; i <= maxjob; i++) + { + /* + * We are only interested in jobs with processes still + * marked as live. Careful in case there's an identical + * process number in a job we haven't quite got around + * to deleting. + */ + if (jobtab[i].stat & STAT_DONE) + continue; + + for (pn = aux ? jobtab[i].auxprocs : jobtab[i].procs; + pn; pn = pn->next) + { + /* + * Make sure we match a process that's still running. + * + * When a job contains two pids, one terminated pid and one + * running pid, then the condition (jobtab[i].stat & + * STAT_DONE) will not stop these pids from being candidates + * for the findproc result (which is supposed to be a + * RUNNING pid), and if the terminated pid is an identical + * process number for the pid identifying the running + * process we are trying to find (after pid number + * wrapping), then we need to avoid returning the terminated + * pid, otherwise the shell would block and wait forever for + * the termination of the process which pid we were supposed + * to return in a different job. + */ + if (pn->pid == pid) { + *pptr = pn; + *jptr = jobtab + i; + if (pn->status == SP_RUNNING) + return 1; + } + } + } + + return (*pptr && *jptr); +} + +/* Does the given job number have any processes? */ + +/**/ +int +hasprocs(int job) +{ + Job jn; + + if (job < 0) { + DPUTS(1, "job number invalid in hasprocs"); + return 0; + } + jn = jobtab + job; + + return jn->procs || jn->auxprocs; +} + +/* Find the super-job of a sub-job. */ + +/**/ +static int +super_job(int sub) +{ + int i; + + for (i = 1; i <= maxjob; i++) + if ((jobtab[i].stat & STAT_SUPERJOB) && + jobtab[i].other == sub && + jobtab[i].gleader) + return i; + return 0; +} + +/**/ +static int +handle_sub(int job, int fg) +{ + /* job: superjob; sj: subjob. */ + Job jn = jobtab + job, sj = jobtab + jn->other; + + if ((sj->stat & STAT_DONE) || (!sj->procs && !sj->auxprocs)) { + struct process *p; + + for (p = sj->procs; p; p = p->next) { + if (WIFSIGNALED(p->status)) { + if (jn->gleader != mypgrp && jn->procs->next) + killpg(jn->gleader, WTERMSIG(p->status)); + else + kill(jn->procs->pid, WTERMSIG(p->status)); + kill(sj->other, SIGCONT); + kill(sj->other, WTERMSIG(p->status)); + break; + } + } + if (!p) { + int cp; + + jn->stat &= ~STAT_SUPERJOB; + jn->stat |= STAT_WASSUPER; + + if ((cp = ((WIFEXITED(jn->procs->status) || + WIFSIGNALED(jn->procs->status)) && + killpg(jn->gleader, 0) == -1))) { + Process p; + for (p = jn->procs; p->next; p = p->next); + jn->gleader = p->pid; + } + /* This deleted the job too early if the parent + shell waited for a command in a list that will + be executed by the sub-shell (e.g.: if we have + `ls|if true;then sleep 20;cat;fi' and ^Z the + sleep, the rest will be executed by a sub-shell, + but the parent shell gets notified for the + sleep. + deletejob(sj, 0); */ + /* If this super-job contains only the sub-shell, + we have to attach the tty to its process group + now. */ + if ((fg || thisjob == job) && + (!jn->procs->next || cp || jn->procs->pid != jn->gleader)) + attachtty(jn->gleader); + kill(sj->other, SIGCONT); + if (jn->stat & STAT_DISOWN) + { + deletejob(jn, 1); + } + } + curjob = jn - jobtab; + } else if (sj->stat & STAT_STOPPED) { + struct process *p; + + jn->stat |= STAT_STOPPED; + for (p = jn->procs; p; p = p->next) + if (p->status == SP_RUNNING || + (!WIFEXITED(p->status) && !WIFSIGNALED(p->status))) + p->status = sj->procs->status; + curjob = jn - jobtab; + printjob(jn, !!isset(LONGLISTJOBS), 1); + return 1; + } + return 0; +} + + +/* Get the latest usage information */ + +/**/ +void +get_usage(void) +{ +#ifdef HAVE_GETRUSAGE + getrusage(RUSAGE_CHILDREN, &child_usage); +#else + times(&shtms); +#endif +} + + +#if !defined HAVE_WAIT3 || !defined HAVE_GETRUSAGE +/* Update status of process that we have just WAIT'ed for */ + +/**/ +void +update_process(Process pn, int status) +{ + struct timezone dummy_tz; +#ifdef HAVE_GETRUSAGE + struct timeval childs = child_usage.ru_stime; + struct timeval childu = child_usage.ru_utime; +#else + long childs = shtms.tms_cstime; + long childu = shtms.tms_cutime; +#endif + + /* get time-accounting info */ + get_usage(); + gettimeofday(&pn->endtime, &dummy_tz); /* record time process exited */ + + pn->status = status; /* save the status returned by WAIT */ +#ifdef HAVE_GETRUSAGE + dtime(&pn->ti.ru_stime, &childs, &child_usage.ru_stime); + dtime(&pn->ti.ru_utime, &childu, &child_usage.ru_utime); +#else + pn->ti.st = shtms.tms_cstime - childs; /* compute process system space time */ + pn->ti.ut = shtms.tms_cutime - childu; /* compute process user space time */ +#endif +} +#endif + +/* + * Called when the current shell is behaving as if it received + * a interactively generated signal (sig). + * + * As we got the signal or are pretending we did, we need to pretend + * anything attached to a CURSH process got it, too. + */ +/**/ +void +check_cursh_sig(int sig) +{ + int i, j; + + if (!errflag) + return; + for (i = 1; i <= maxjob; i++) { + if ((jobtab[i].stat & (STAT_CURSH|STAT_DONE)) == + STAT_CURSH) { + for (j = 0; j < 2; j++) { + Process pn = j ? jobtab[i].auxprocs : jobtab[i].procs; + for (; pn; pn = pn->next) { + if (pn->status == SP_RUNNING) { + kill(pn->pid, sig); + } + } + } + } + } +} + +/**/ +void +storepipestats(Job jn, int inforeground, int fixlastval) +{ + int i, pipefail = 0, jpipestats[MAX_PIPESTATS]; + Process p; + + for (p = jn->procs, i = 0; p && i < MAX_PIPESTATS; p = p->next, i++) { + jpipestats[i] = (WIFSIGNALED(p->status) ? + 0200 | WTERMSIG(p->status) : + (WIFSTOPPED(p->status) ? + 0200 | WEXITSTATUS(p->status) : + WEXITSTATUS(p->status))); + if (jpipestats[i]) + pipefail = jpipestats[i]; + } + if (inforeground) { + memcpy(pipestats, jpipestats, sizeof(int)*i); + if ((jn->stat & STAT_CURSH) && i < MAX_PIPESTATS) + pipestats[i++] = lastval; + numpipestats = i; + } + + if (fixlastval) { + if (jn->stat & STAT_CURSH) { + if (!lastval && isset(PIPEFAIL)) + lastval = pipefail; + } else if (isset(PIPEFAIL)) + lastval = pipefail; + } +} + +/* 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->auxprocs; pn; pn = pn->next) { +#ifdef WIFCONTINUED + if (WIFCONTINUED(pn->status)) + pn->status = SP_RUNNING; +#endif + if (pn->status == SP_RUNNING) + return; + } + + for (pn = jn->procs; pn; pn = pn->next) { +#ifdef WIFCONTINUED + if (WIFCONTINUED(pn->status)) { + jn->stat &= ~STAT_STOPPED; + pn->status = SP_RUNNING; + } +#endif + 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) : + (WIFSTOPPED(pn->status) ? + 0200 | WEXITSTATUS(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) { + if (jn->stat & STAT_SUBJOB) { + /* If we have `cat foo|while read a; grep $a bar;done' + * and have hit ^Z, the sub-job is stopped, but the + * super-job may still be running, waiting to be stopped + * or to exit. So we have to send it a SIGTSTP. */ + int i; + + if ((i = super_job(job))) + killpg(jobtab[i].gleader, SIGTSTP); + } + return; + } + } + { /* job is done or stopped, remember return value */ + lastval2 = val; + /* If last process was run in the current shell, keep old status + * and let it handle its own traps, but always allow the test + * for the pgrp. + */ + if (jn->stat & STAT_CURSH) + inforeground = 1; + else if (job == thisjob) { + lastval = val; + inforeground = 2; + } + } + + if (shout && shout != stderr && !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))) { + if (list_pipe) { + if (somestopped || (pgrp > 1 && kill(-pgrp, 0) == -1)) { + attachtty(mypgrp); + /* check window size and adjust if necessary */ + adjustwinsize(0); + } else { + /* + * Oh, dear, we're right in the middle of some confusion + * of shell jobs on the righthand side of a pipeline, so + * it's death to call attachtty() just yet. Mark the + * fact in the job, so that the attachtty() will be called + * when the job is finally deleted. + */ + jn->stat |= STAT_ATTACH; + } + /* If we have `foo|while true; (( x++ )); done', and hit + * ^C, we have to stop the loop, too. */ + if ((val & 0200) && inforeground == 1 && + ((val & ~0200) == SIGINT || (val & ~0200) == SIGQUIT)) { + if (!errbrk_saved) { + errbrk_saved = 1; + prev_breaks = breaks; + prev_errflag = errflag; + } + breaks = loops; + errflag |= ERRFLAG_INT; + inerrflush(); + } + } else { + attachtty(mypgrp); + /* check window size and adjust if necessary */ + adjustwinsize(0); + } + } + } else if (list_pipe && (val & 0200) && inforeground == 1 && + ((val & ~0200) == SIGINT || (val & ~0200) == SIGQUIT)) { + if (!errbrk_saved) { + errbrk_saved = 1; + prev_breaks = breaks; + prev_errflag = errflag; + } + breaks = loops; + errflag |= ERRFLAG_INT; + inerrflush(); + } + 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)) { + /* This may be redundant with printjob() but note that inforeground + * is true here for STAT_CURSH jobs even when job != thisjob, most + * likely because thisjob = -1 from exec.c:execsimple() trickery. + * However, if we reset lastval here we break it for printjob(). + */ + storepipestats(jn, inforeground, 0); + } + if (!inforeground && + (jn->stat & (STAT_SUBJOB | STAT_DONE)) == (STAT_SUBJOB | STAT_DONE)) { + int su; + + if ((su = super_job(jn - jobtab))) + handle_sub(su, 0); + } + if ((jn->stat & (STAT_DONE | STAT_STOPPED)) == STAT_STOPPED) { + prevjob = curjob; + curjob = job; + } + if ((isset(NOTIFY) || job == thisjob) && (jn->stat & STAT_LOCKED)) { + if (printjob(jn, !!isset(LONGLISTJOBS), 0) && + zleactive) + zleentry(ZLE_CMD_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 pretend that the shell got * + * the signal too. */ + if (inforeground == 2 && 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 |= ERRFLAG_INT; + } + check_cursh_sig(sig); + } + } +} + +/* set the previous job to something reasonable */ + +/**/ +static void +setprevjob(void) +{ + int i; + + for (i = maxjob; i; i--) + if ((jobtab[i].stat & STAT_INUSE) && (jobtab[i].stat & STAT_STOPPED) && + !(jobtab[i].stat & STAT_SUBJOB) && i != curjob && i != thisjob) { + prevjob = i; + return; + } + + for (i = maxjob; i; i--) + if ((jobtab[i].stat & STAT_INUSE) && !(jobtab[i].stat & STAT_SUBJOB) && + i != curjob && i != thisjob) { + prevjob = i; + return; + } + + prevjob = -1; +} + +/**/ +long +get_clktck(void) +{ + static long clktck; + +#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 + + return clktck; +} + +/**/ +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, child_times_t *ti, char *desc) +{ + char *s; + double elapsed_time, user_time, system_time; +#ifdef HAVE_GETRUSAGE + double total_time; +#endif + int percent, desclen; + + if (!desc) + { + desc = ""; + desclen = 0; + } + else + { + desc = dupstring(desc); + unmetafy(desc, &desclen); + } + + /* go ahead and compute these, since almost every TIMEFMT will have them */ + elapsed_time = real->tv_sec + real->tv_usec / 1000000.0; + +#ifdef HAVE_GETRUSAGE + user_time = ti->ru_utime.tv_sec + ti->ru_utime.tv_usec / 1000000.0; + system_time = ti->ru_stime.tv_sec + ti->ru_stime.tv_usec / 1000000.0; + total_time = user_time + system_time; + percent = 100.0 * total_time + / (real->tv_sec + real->tv_usec / 1000000.0); +#else + { + long clktck = get_clktck(); + 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); + } +#endif + + queue_signals(); + if (!(s = getsparam("TIMEFMT"))) + s = DEFAULT_TIMEFMT; + else + s = unmetafy(s, NULL); + + 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 'm': + switch (*++s) { + case 'E': + fprintf(stderr, "%0.fms", elapsed_time * 1000.0); + break; + case 'U': + fprintf(stderr, "%0.fms", user_time * 1000.0); + break; + case 'S': + fprintf(stderr, "%0.fms", system_time * 1000.0); + break; + default: + fprintf(stderr, "%%m"); + s--; + break; + } + break; + case 'u': + switch (*++s) { + case 'E': + fprintf(stderr, "%0.fus", elapsed_time * 1000000.0); + break; + case 'U': + fprintf(stderr, "%0.fus", user_time * 1000000.0); + break; + case 'S': + fprintf(stderr, "%0.fus", system_time * 1000000.0); + break; + default: + fprintf(stderr, "%%u"); + s--; + break; + } + 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; +#ifdef HAVE_STRUCT_RUSAGE_RU_NSWAP + case 'W': + fprintf(stderr, "%ld", ti->ru_nswap); + break; +#endif +#ifdef HAVE_STRUCT_RUSAGE_RU_IXRSS + case 'X': + fprintf(stderr, "%ld", + total_time ? + (long)(ti->ru_ixrss / total_time) : + (long)0); + break; +#endif +#ifdef HAVE_STRUCT_RUSAGE_RU_IDRSS + case 'D': + fprintf(stderr, "%ld", + total_time ? + (long) ((ti->ru_idrss +#ifdef HAVE_STRUCT_RUSAGE_RU_ISRSS + + ti->ru_isrss +#endif + ) / total_time) : + (long)0); + break; +#endif +#if defined(HAVE_STRUCT_RUSAGE_RU_IDRSS) || \ + defined(HAVE_STRUCT_RUSAGE_RU_ISRSS) || \ + defined(HAVE_STRUCT_RUSAGE_RU_IXRSS) + case 'K': + /* treat as D if X not available */ + fprintf(stderr, "%ld", + total_time ? + (long) (( +#ifdef HAVE_STRUCT_RUSAGE_RU_IXRSS + ti->ru_ixrss +#else + 0 +#endif +#ifdef HAVE_STRUCT_RUSAGE_RU_IDRSS + + ti->ru_idrss +#endif +#ifdef HAVE_STRUCT_RUSAGE_RU_ISRSS + + ti->ru_isrss +#endif + ) / total_time) : + (long)0); + break; +#endif +#ifdef HAVE_STRUCT_RUSAGE_RU_MAXRSS + case 'M': + fprintf(stderr, "%ld", ti->ru_maxrss / 1024); + break; +#endif +#ifdef HAVE_STRUCT_RUSAGE_RU_MAJFLT + case 'F': + fprintf(stderr, "%ld", ti->ru_majflt); + break; +#endif +#ifdef HAVE_STRUCT_RUSAGE_RU_MINFLT + case 'R': + fprintf(stderr, "%ld", ti->ru_minflt); + break; +#endif +#ifdef HAVE_STRUCT_RUSAGE_RU_INBLOCK + case 'I': + fprintf(stderr, "%ld", ti->ru_inblock); + break; +#endif +#ifdef HAVE_STRUCT_RUSAGE_RU_OUBLOCK + case 'O': + fprintf(stderr, "%ld", ti->ru_oublock); + break; +#endif +#ifdef HAVE_STRUCT_RUSAGE_RU_MSGRCV + case 'r': + fprintf(stderr, "%ld", ti->ru_msgrcv); + break; +#endif +#ifdef HAVE_STRUCT_RUSAGE_RU_MSGSND + case 's': + fprintf(stderr, "%ld", ti->ru_msgsnd); + break; +#endif +#ifdef HAVE_STRUCT_RUSAGE_RU_NSIGNALS + case 'k': + fprintf(stderr, "%ld", ti->ru_nsignals); + break; +#endif +#ifdef HAVE_STRUCT_RUSAGE_RU_NVCSW + case 'w': + fprintf(stderr, "%ld", ti->ru_nvcsw); + break; +#endif +#ifdef HAVE_STRUCT_RUSAGE_RU_NIVCSW + case 'c': + fprintf(stderr, "%ld", ti->ru_nivcsw); + break; +#endif + case 'J': + fwrite(desc, sizeof(char), desclen, stderr); + break; + case '%': + putc('%', stderr); + break; + case '\0': + s--; + break; + default: + fprintf(stderr, "%%%c", *s); + break; + } else + putc(*s, stderr); + unqueue_signals(); + putc('\n', stderr); + fflush(stderr); +} + +/**/ +static void +dumptime(Job jn) +{ + Process pn; + struct timeval dtimeval; + + 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) +{ + struct value vbuf; + Value v; + char *s = "REPORTTIME"; + int save_errflag = errflag; + zlong reporttime = -1; +#ifdef HAVE_GETRUSAGE + char *sm = "REPORTMEMORY"; + zlong reportmemory = -1; +#endif + + /* if the time keyword was used */ + if (j->stat & STAT_TIMED) + return 1; + + queue_signals(); + errflag = 0; + if ((v = getvalue(&vbuf, &s, 0))) + reporttime = getintvalue(v); +#ifdef HAVE_GETRUSAGE + if ((v = getvalue(&vbuf, &sm, 0))) + reportmemory = getintvalue(v); +#endif + errflag = save_errflag; + unqueue_signals(); + if (reporttime < 0 +#ifdef HAVE_GETRUSAGE + && reportmemory < 0 +#endif + ) + return 0; + /* can this ever happen? */ + if (!j->procs) + return 0; + if (zleactive) + return 0; + + if (reporttime >= 0) + { +#ifdef HAVE_GETRUSAGE + reporttime -= j->procs->ti.ru_utime.tv_sec + + j->procs->ti.ru_stime.tv_sec; + if (j->procs->ti.ru_utime.tv_usec + + j->procs->ti.ru_stime.tv_usec >= 1000000) + reporttime--; + if (reporttime <= 0) + return 1; +#else + { + clktck = get_clktck(); + if ((j->procs->ti.ut + j->procs->ti.st) / clktck >= reporttime) + return 1; + } +#endif + } + +#ifdef HAVE_GETRUSAGE + if (reportmemory >= 0 && + j->procs->ti.ru_maxrss / 1024 > reportmemory) + return 1; +#endif + + return 0; +} + +/* !(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 + * synch = 3 means called synchronously from bg or fg + * + * Returns 1 if some output was done. + * + * The function also deletes the job if it was done, even it + * is not printed. + */ + +/**/ +int +printjob(Job jn, int lng, int synch) +{ + Process pn; + int job, len = 9, sig, sflag = 0, llen; + int conted = 0, lineleng = zterm_columns, skip = 0, doputnl = 0; + int doneprint = 0, skip_print = 0; + FILE *fout = (synch == 2 || !shout) ? stdout : shout; + + if (synch > 1 && oldjobtab != NULL) + job = jn - oldjobtab; + else + job = jn - jobtab; + DPUTS3(job < 0 || job > (oldjobtab && synch > 1 ? oldmaxjob : maxjob), + "bogus job number, jn = %L, jobtab = %L, oldjobtab = %L", + (long)jn, (long)jobtab, (long)oldjobtab); + + if (jn->stat & STAT_NOPRINT) { + skip_print = 1; + } + + if (lng < 0) { + conted = 1; + lng = !!isset(LONGLISTJOBS); + } + +/* 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; + if (isset(PRINTEXITVALUE) && isset(SHINSTDIN)) { + sflag = 1; + skip_print = 0; + } + } 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; + skip_print = 0; + } + } + } + + if (skip_print) { + if (jn->stat & STAT_DONE) { + /* This looks silly, but see update_job() */ + if (synch <= 1) + storepipestats(jn, job == thisjob, job == thisjob); + if (should_report_time(jn)) + dumptime(jn); + deletejob(jn, 0); + if (job == curjob) { + curjob = prevjob; + prevjob = job; + } + if (job == prevjob) + setprevjob(); + } + return 0; + } + + /* + * - Always print if called from jobs + * - Otherwise, require MONITOR option ("jobbing") and some + * change of state + * - also either the shell is interactive or this is synchronous. + */ + if (synch == 2 || + ((interact || synch) && jobbing && + ((jn->stat & STAT_STOPPED) || sflag || job != thisjob))) { + int len2, fline = 1; + /* POSIX requires just the job text for bg and fg */ + int plainfmt = (synch == 3) && isset(POSIXJOBS); + /* use special format for current job, except in `jobs' */ + int thisfmt = job == thisjob && synch != 2; + Process qn; + + if (!synch) + zleentry(ZLE_CMD_TRASH); + if (doputnl && !synch) { + doneprint = 1; + putc('\n', fout); + } + for (pn = jn->procs; pn;) { + len2 = (thisfmt ? 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; + } + doneprint = 1; + if (!plainfmt) { + if (!thisfmt || lng) { + if (fline) + fprintf(fout, "[%ld] %c ", + (long)job, + (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) { + char *txt = dupstring(pn->text); + int txtlen; + unmetafy(txt, &txtlen); + fwrite(txt, sizeof(char), txtlen, fout); + if (pn->next) + fputs(" | ", fout); + } + putc('\n', fout); + fline = 0; + } + fflush(fout); + } else if (doputnl && interact && !synch) { + doneprint = 1; + 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 && + jn->pwd && strcmp(jn->pwd, pwd))) { + doneprint = 1; + fprintf(fout, "(pwd %s: ", (lng & 4) ? "" : "now"); + fprintdir(((lng & 4) && jn->pwd) ? jn->pwd : pwd, fout); + fprintf(fout, ")\n"); + fflush(fout); + } + + /* delete job if done */ + + if (jn->stat & STAT_DONE) { + /* This looks silly, but see update_job() */ + if (synch <= 1) + storepipestats(jn, job == thisjob, job == thisjob); + if (should_report_time(jn)) + dumptime(jn); + deletejob(jn, 0); + if (job == curjob) { + curjob = prevjob; + prevjob = job; + } + if (job == prevjob) + setprevjob(); + } else + jn->stat &= ~STAT_CHANGED; + + return doneprint; +} + +/* Add a file to be deleted or fd to be closed to the current job */ + +/**/ +void +addfilelist(const char *name, int fd) +{ + Jobfile jf = (Jobfile)zalloc(sizeof(struct jobfile)); + LinkList ll = jobtab[thisjob].filelist; + + if (!ll) + ll = jobtab[thisjob].filelist = znewlinklist(); + if (name) + { + jf->u.name = ztrdup(name); + jf->is_fd = 0; + } + else + { + jf->u.fd = fd; + jf->is_fd = 1; + } + zaddlinknode(ll, jf); +} + +/* Clean up pipes no longer needed associated with a job */ + +/**/ +void +pipecleanfilelist(LinkList filelist, int proc_subst_only) +{ + LinkNode node; + + if (!filelist) + return; + node = firstnode(filelist); + while (node) { + Jobfile jf = (Jobfile)getdata(node); + if (jf->is_fd && + (!proc_subst_only || fdtable[jf->u.fd] == FDT_PROC_SUBST)) { + LinkNode next = nextnode(node); + zclose(jf->u.fd); + (void)remnode(filelist, node); + zfree(jf, sizeof(*jf)); + node = next; + } else + incnode(node); + } +} + +/* Finished with list of files for a job */ + +/**/ +void +deletefilelist(LinkList file_list, int disowning) +{ + Jobfile jf; + if (file_list) { + while ((jf = (Jobfile)getlinknode(file_list))) { + if (jf->is_fd) { + if (!disowning) + zclose(jf->u.fd); + } else { + if (!disowning) + unlink(jf->u.name); + zsfree(jf->u.name); + } + zfree(jf, sizeof(*jf)); + } + zfree(file_list, sizeof(struct linklist)); + } +} + +/**/ +void +freejob(Job jn, int deleting) +{ + struct process *pn, *nx; + + pn = jn->procs; + jn->procs = NULL; + for (; pn; pn = nx) { + nx = pn->next; + zfree(pn, sizeof(struct process)); + } + + pn = jn->auxprocs; + jn->auxprocs = NULL; + for (; pn; pn = nx) { + nx = pn->next; + zfree(pn, sizeof(struct process)); + } + + if (jn->ty) + zfree(jn->ty, sizeof(struct ttyinfo)); + if (jn->pwd) + zsfree(jn->pwd); + jn->pwd = NULL; + if (jn->stat & STAT_WASSUPER) { + /* careful in case we shrink and move the job table */ + int job = jn - jobtab; + if (deleting) + deletejob(jobtab + jn->other, 0); + else + freejob(jobtab + jn->other, 0); + jn = jobtab + job; + } + jn->gleader = jn->other = 0; + jn->stat = jn->stty_in_env = 0; + jn->filelist = NULL; + jn->ty = NULL; + + /* Find the new highest job number. */ + if (maxjob == jn - jobtab) { + while (maxjob && !(jobtab[maxjob].stat & STAT_INUSE)) + maxjob--; + } +} + +/* + * We are actually finished with this job, rather + * than freeing it to make space. + * + * If "disowning" is set, files associated with the job are not + * actually deleted --- and won't be as there is nothing left + * to clear up. + */ + +/**/ +void +deletejob(Job jn, int disowning) +{ + deletefilelist(jn->filelist, disowning); + if (jn->stat & STAT_ATTACH) { + attachtty(mypgrp); + adjustwinsize(0); + } + if (jn->stat & STAT_SUPERJOB) { + Job jno = jobtab + jn->other; + if (jno->stat & STAT_SUBJOB) + jno->stat |= STAT_SUBJOB_ORPHANED; + } + + freejob(jn, 1); +} + +/* + * Add a process to the current job. + * The third argument is 1 if we are adding a process which is not + * part of the main pipeline but an auxiliary process used for + * handling MULTIOS or process substitution. We will wait for it + * but not display job information about it. + */ + +/**/ +void +addproc(pid_t pid, char *text, int aux, struct timeval *bgtime) +{ + Process pn, *pnlist; + + DPUTS(thisjob == -1, "No valid job in addproc."); + pn = (Process) zshcalloc(sizeof *pn); + pn->pid = pid; + if (text) + strcpy(pn->text, text); + else + *pn->text = '\0'; + pn->status = SP_RUNNING; + pn->next = NULL; + + if (!aux) + { + pn->bgtime = *bgtime; + /* 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 */ + pnlist = &jobtab[thisjob].procs; + } + else + pnlist = &jobtab[thisjob].auxprocs; + + if (*pnlist) { + Process n; + + for (n = *pnlist; n->next; n = n->next); + n->next = pn; + } else { + /* first process for this job */ + *pnlist = 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. + * wait_cmd indicates this is from the interactive wait command, + * in which case the behaviour is a little different: the command + * itself can be interrupted by a trapped signal. + */ + +/**/ +int +waitforpid(pid_t pid, int wait_cmd) +{ + int first = 1, q = queue_signal_level(); + + /* child_block() around this loop in case #ifndef WNOHANG */ + dont_queue_signals(); + child_block(); /* unblocked in signal_suspend() */ + queue_traps(wait_cmd); + + /* This function should never be called with a pid that is not a + * child of the current shell. Consequently, if kill(0, pid) + * fails here with ESRCH, the child has already been reaped. In + * the loop body, we expect this to happen in signal_suspend() + * via zhandler(), after which this test terminates the loop. + */ + while (!errflag && (kill(pid, 0) >= 0 || errno != ESRCH)) { + if (first) + first = 0; + else if (!wait_cmd) + kill(pid, SIGCONT); + + last_signal = -1; + signal_suspend(SIGCHLD, wait_cmd); + if (last_signal != SIGCHLD && wait_cmd && last_signal >= 0 && + (sigtrapped[last_signal] & ZSIG_TRAPPED)) { + /* wait command interrupted, but no error: return */ + restore_queue_signals(q); + return 128 + last_signal; + } + child_block(); + } + unqueue_traps(); + child_unblock(); + restore_queue_signals(q); + + return 0; +} + +/* + * Wait for a job to finish. + * wait_cmd indicates this is from the wait builtin; see + * wait_cmd in waitforpid(). + */ + +/**/ +static int +zwaitjob(int job, int wait_cmd) +{ + int q = queue_signal_level(); + Job jn = jobtab + job; + + child_block(); /* unblocked during signal_suspend() */ + queue_traps(wait_cmd); + dont_queue_signals(); + if (jn->procs || jn->auxprocs) { /* if any forks were done */ + jn->stat |= STAT_LOCKED; + if (jn->stat & STAT_CHANGED) + printjob(jn, !!isset(LONGLISTJOBS), 1); + if (jn->filelist) { + /* + * The main shell is finished with any file descriptors used + * for process substitution associated with this job: close + * them to indicate to listeners there's no more input. + * + * Note we can't safely delete temporary files yet as these + * are directly visible to other processes. However, + * we can't deadlock on the fact that those still exist, so + * that's not a problem. + */ + pipecleanfilelist(jn->filelist, 0); + } + while (!(errflag & ERRFLAG_ERROR) && jn->stat && + !(jn->stat & STAT_DONE) && + !(interact && (jn->stat & STAT_STOPPED))) { + signal_suspend(SIGCHLD, wait_cmd); + if (last_signal != SIGCHLD && wait_cmd && last_signal >= 0 && + (sigtrapped[last_signal] & ZSIG_TRAPPED)) + { + /* builtin wait interrupted by trapped signal */ + restore_queue_signals(q); + return 128 + last_signal; + } + /* 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 + + When attempting to separate errors and interrupts, we + assumed because of the previous comment it would be OK + to remove ERRFLAG_ERROR and leave ERRFLAG_INT set, since + that's the one related to ^C. But that doesn't work. + There's something more here we don't understand. --pws + + The change above to ignore ERRFLAG_INT in the loop test + solves a problem wherein child processes that ignore the + INT signal were never waited-for. Clearing the flag here + still seems the wrong thing, but perhaps ERRFLAG_INT + should be saved and restored around signal_suspend() to + prevent it being lost within a signal trap? --Bart + + errflag = 0; */ + + if (subsh) { + killjb(jn, SIGCONT); + jn->stat &= ~STAT_STOPPED; + } + if (jn->stat & STAT_SUPERJOB) + if (handle_sub(jn - jobtab, 1)) + break; + child_block(); + } + } else { + deletejob(jn, 0); + pipestats[0] = lastval; + numpipestats = 1; + } + restore_queue_signals(q); + unqueue_traps(); + child_unblock(); + + return 0; +} + +/* wait for running job to finish */ + +/**/ +void +waitjobs(void) +{ + Job jn = jobtab + thisjob; + DPUTS(thisjob == -1, "No valid job in waitjobs."); + + if (jn->procs || jn->auxprocs) + zwaitjob(thisjob, 0); + else { + deletejob(jn, 0); + pipestats[0] = lastval; + numpipestats = 1; + } + thisjob = -1; +} + +/* clear job table when entering subshells */ + +/**/ +mod_export void +clearjobtab(int monitor) +{ + int i; + + if (isset(POSIXJOBS)) + oldmaxjob = 0; + for (i = 1; i <= maxjob; i++) { + /* + * See if there is a jobtable worth saving. + * We never free the saved version; it only happens + * once for each subshell of a shell with job control, + * so doesn't create a leak. + */ + if (monitor && !isset(POSIXJOBS) && jobtab[i].stat) + oldmaxjob = i+1; + else if (jobtab[i].stat & STAT_INUSE) + freejob(jobtab + i, 0); + } + + if (monitor && oldmaxjob) { + int sz = oldmaxjob * sizeof(struct job); + if (oldjobtab) + free(oldjobtab); + oldjobtab = (struct job *)zalloc(sz); + memcpy(oldjobtab, jobtab, sz); + + /* Don't report any job we're part of */ + if (thisjob != -1 && thisjob < oldmaxjob) + memset(oldjobtab+thisjob, 0, sizeof(struct job)); + } + + memset(jobtab, 0, jobtabsize * sizeof(struct job)); /* zero out table */ + maxjob = 0; + + /* + * Although we don't have job control in subshells, we + * sometimes needs control structures for other purposes such + * as multios. Grab a job for this purpose; any will do + * since we've freed them all up (so there's no question + * of problems with the job table size here). + */ + thisjob = initjob(); +} + +static int initnewjob(int i) +{ + jobtab[i].stat = STAT_INUSE; + if (jobtab[i].pwd) { + zsfree(jobtab[i].pwd); + jobtab[i].pwd = NULL; + } + jobtab[i].gleader = 0; + + if (i > maxjob) + maxjob = i; + + return i; +} + +/* 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) + return initnewjob(i); + if (maxjob + 1 < jobtabsize) + return initnewjob(maxjob+1); + + if (expandjobtab()) + return initnewjob(i); + + zerr("job table full or recursion limit exceeded"); + return -1; +} + +/**/ +void +setjobpwd(void) +{ + int i; + + for (i = 1; i <= maxjob; i++) + if (jobtab[i].stat && !jobtab[i].pwd) + jobtab[i].pwd = ztrdup(pwd); +} + +/* print pids for & */ + +/**/ +void +spawnjob(void) +{ + Process pn; + + DPUTS(thisjob == -1, "No valid job in spawnjob."); + /* 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 (jobbing && jobtab[thisjob].procs) { + FILE *fout = shout ? shout : stdout; + fprintf(fout, "[%d]", thisjob); + for (pn = jobtab[thisjob].procs; pn; pn = pn->next) + fprintf(fout, " %ld", (long) pn->pid); + fprintf(fout, "\n"); + fflush(fout); + } + } + if (!hasprocs(thisjob)) + deletejob(jobtab + thisjob, 0); + else { + jobtab[thisjob].stat |= STAT_LOCKED; + pipecleanfilelist(jobtab[thisjob].filelist, 0); + } + thisjob = -1; +} + +/**/ +void +shelltime(void) +{ + struct timezone dummy_tz; + struct timeval dtimeval, now; + child_times_t ti; +#ifndef HAVE_GETRUSAGE + struct tms buf; +#endif + + gettimeofday(&now, &dummy_tz); + +#ifdef HAVE_GETRUSAGE + getrusage(RUSAGE_SELF, &ti); +#else + times(&buf); + + ti.ut = buf.tms_utime; + ti.st = buf.tms_stime; +#endif + printtime(dtime(&dtimeval, &shtimer, &now), &ti, "shell"); + +#ifdef HAVE_GETRUSAGE + getrusage(RUSAGE_CHILDREN, &ti); +#else + ti.ut = buf.tms_cutime; + ti.st = buf.tms_cstime; +#endif + printtime(&dtimeval, &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, !!isset(LONGLISTJOBS), 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. */ + +/**/ +mod_export int +getjob(const char *s, const char *prog) +{ + int jobnum, returnval, mymaxjob; + Job myjobtab; + + if (oldjobtab) { + myjobtab = oldjobtab; + mymaxjob = oldmaxjob; + } else { + myjobtab= jobtab; + mymaxjob = maxjob; + } + + /* 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) { + if (prog) + zwarnnam(prog, "no current job"); + returnval = -1; + goto done; + } + returnval = curjob; + goto done; + } + /* "%-" represents the previous job */ + if (*s == '-') { + if (prevjob == -1) { + if (prog) + zwarnnam(prog, "no previous job"); + 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 <= mymaxjob && myjobtab[jobnum].stat && + !(myjobtab[jobnum].stat & STAT_SUBJOB) && + /* + * If running jobs in a subshell, we are allowed to + * refer to the "current" job (it's not really the + * current job in the subshell). It's possible we + * should reset thisjob to -1 on entering the subshell. + */ + (myjobtab == oldjobtab || jobnum != thisjob)) { + returnval = jobnum; + goto done; + } + if (prog) + zwarnnam(prog, "%%%s: no such job", s); + returnval = -1; + goto done; + } + /* "%?" introduces a search string */ + if (*s == '?') { + struct process *pn; + + for (jobnum = mymaxjob; jobnum >= 0; jobnum--) + if (myjobtab[jobnum].stat && + !(myjobtab[jobnum].stat & STAT_SUBJOB) && + jobnum != thisjob) + for (pn = myjobtab[jobnum].procs; pn; pn = pn->next) + if (strstr(pn->text, s + 1)) { + returnval = jobnum; + goto done; + } + if (prog) + zwarnnam(prog, "job not found: %s", s); + 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); + returnval = -1; + done: + return returnval; +} + +#ifndef HAVE_SETPROCTITLE +/* 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; +#endif + + +/* Initialise job handling. */ + +/**/ +void +init_jobs(char **argv, char **envp) +{ +#ifndef HAVE_SETPROCTITLE + char *p, *q; +#endif + size_t init_bytes = MAXJOBS_ALLOC*sizeof(struct job); + + /* + * Initialise the job table. If this fails, we're in trouble. + */ + jobtab = (struct job *)zalloc(init_bytes); + if (!jobtab) { + zerr("failed to allocate job table, aborting."); + exit(1); + } + jobtabsize = MAXJOBS_ALLOC; + memset(jobtab, 0, init_bytes); + +#ifndef HAVE_SETPROCTITLE + /* + * 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. + */ + hackzero = *argv; + p = strchr(hackzero, 0); + while(*++argv) { + q = *argv; + if(q != p+1) + goto done; + p = strchr(q, 0); + } +#if !defined(HAVE_PUTENV) && !defined(USE_SET_UNSET_ENV) + for(; *envp; envp++) { + q = *envp; + if(q != p+1) + goto done; + p = strchr(q, 0); + } +#endif + done: + hackspace = p - hackzero; +#endif +} + + +/* + * We have run out of space in the job table. + * Expand it by an additional MAXJOBS_ALLOC slots. + */ + +/* + * An arbitrary limit on the absolute maximum size of the job table. + * This prevents us taking over the entire universe. + * Ought to be a multiple of MAXJOBS_ALLOC, but doesn't need to be. + */ +#define MAX_MAXJOBS 1000 + +/**/ +int +expandjobtab(void) +{ + int newsize = jobtabsize + MAXJOBS_ALLOC; + struct job *newjobtab; + + if (newsize > MAX_MAXJOBS) + return 0; + + newjobtab = (struct job *)zrealloc(jobtab, newsize * sizeof(struct job)); + if (!newjobtab) + return 0; + + /* + * Clear the new section of the table; this is necessary for + * the jobs to appear unused. + */ + memset(newjobtab + jobtabsize, 0, MAXJOBS_ALLOC * sizeof(struct job)); + + jobtab = newjobtab; + jobtabsize = newsize; + + return 1; +} + + +/* + * See if we can reduce the job table. We can if we go over + * a MAXJOBS_ALLOC boundary. However, we leave a boundary, + * currently 20 jobs, so that we have a place for immediate + * expansion and don't play ping pong with the job table size. + */ + +/**/ +void +maybeshrinkjobtab(void) +{ + int jobbound; + + queue_signals(); + jobbound = maxjob + MAXJOBS_ALLOC - (maxjob % MAXJOBS_ALLOC); + if (jobbound < jobtabsize && jobbound > maxjob + 20) { + struct job *newjobtab; + + /* Hope this can't fail, but anyway... */ + newjobtab = (struct job *)zrealloc(jobtab, + jobbound*sizeof(struct job)); + + if (newjobtab) { + jobtab = newjobtab; + jobtabsize = jobbound; + } + } + unqueue_signals(); +} + +/* + * Definitions for the background process stuff recorded below. + * This would be more efficient as a hash, but + * - that's quite heavyweight for something not needed very often + * - we need some kind of ordering as POSIX allows us to limit + * the size of the list to the value of _SC_CHILD_MAX and clearly + * we want to clear the oldest first + * - cases with a long list of background jobs where the user doesn't + * wait for a large number, and then does wait for one (the only + * inefficient case) are rare + * - in the context of waiting for an external process, looping + * over a list isn't so very inefficient. + * Enough excuses already. + */ + +/* Data in the link list, a key (process ID) / value (exit status) pair. */ +struct bgstatus { + pid_t pid; + int status; +}; +typedef struct bgstatus *Bgstatus; +/* The list of those entries */ +static LinkList bgstatus_list; +/* Count of entries. Reaches value of _SC_CHILD_MAX and stops. */ +static long bgstatus_count; + +/* + * Remove and free a bgstatus entry. + */ +static void rembgstatus(LinkNode node) +{ + zfree(remnode(bgstatus_list, node), sizeof(struct bgstatus)); + bgstatus_count--; +} + +/* + * Record the status of a background process that exited so we + * can execute the builtin wait for it. + * + * We can't execute the wait builtin for something that exited in the + * foreground as it's not visible to the user, so don't bother recording. + */ + +/**/ +void +addbgstatus(pid_t pid, int status) +{ + static long child_max; + Bgstatus bgstatus_entry; + + if (!child_max) { +#ifdef _SC_CHILD_MAX + child_max = sysconf(_SC_CHILD_MAX); + if (!child_max) /* paranoia */ +#endif + { + /* Be inventive */ + child_max = 1024L; + } + } + + if (!bgstatus_list) { + bgstatus_list = znewlinklist(); + /* + * We're not always robust about memory failures, but + * this is pretty deep in the shell basics to be failing owing + * to memory, and a failure to wait is reported loudly, so test + * and fail silently here. + */ + if (!bgstatus_list) + return; + } + if (bgstatus_count == child_max) { + /* Overflow. List is in order, remove first */ + rembgstatus(firstnode(bgstatus_list)); + } + bgstatus_entry = (Bgstatus)zalloc(sizeof(*bgstatus_entry)); + if (!bgstatus_entry) { + /* See note above */ + return; + } + bgstatus_entry->pid = pid; + bgstatus_entry->status = status; + if (!zaddlinknode(bgstatus_list, bgstatus_entry)) { + zfree(bgstatus_entry, sizeof(*bgstatus_entry)); + return; + } + bgstatus_count++; +} + +/* + * See if pid has a recorded exit status. + * Note we make no guarantee that the PIDs haven't wrapped, so this + * may not be the right process. + * + * This is only used by wait, which must only work on each + * pid once, so we need to remove the entry if we find it. + */ + +static int getbgstatus(pid_t pid) +{ + LinkNode node; + Bgstatus bgstatus_entry; + + if (!bgstatus_list) + return -1; + for (node = firstnode(bgstatus_list); node; incnode(node)) { + bgstatus_entry = (Bgstatus)getdata(node); + if (bgstatus_entry->pid == pid) { + int status = bgstatus_entry->status; + rembgstatus(node); + return status; + } + } + return -1; +} + +/* 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, Options ops, int func) +{ + int job, lng, firstjob = -1, retval = 0, ofunc = func; + + if (OPT_ISSET(ops,'Z')) { + int len; + + if(isset(RESTRICTED)) { + zwarnnam(name, "-Z is restricted"); + return 1; + } + if(!argv[0] || argv[1]) { + zwarnnam(name, "-Z requires one argument"); + return 1; + } + queue_signals(); + unmetafy(*argv, &len); +#ifdef HAVE_SETPROCTITLE + setproctitle("%s", *argv); +#else + if(len > hackspace) + len = hackspace; + memcpy(hackzero, *argv, len); + memset(hackzero + len, 0, hackspace - len); +#endif + unqueue_signals(); + return 0; + } + + if (func == BIN_JOBS) { + lng = (OPT_ISSET(ops,'l')) ? 1 : (OPT_ISSET(ops,'p')) ? 2 : 0; + if (OPT_ISSET(ops,'d')) + lng |= 4; + } else { + lng = !!isset(LONGLISTJOBS); + } + + 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."); + return 1; + } + + queue_signals(); + /* + * In case any processes changed state recently, wait for them. + * This updates stopped processes (but we should have been + * signalled about those, up to inevitable races), and also + * continued processes if that feature is available. + */ + wait_for_processes(); + + /* If necessary, update job table. */ + if (unset(NOTIFY)) + scanjobs(); + + if (func != BIN_JOBS || isset(MONITOR) || !oldmaxjob) + 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"); + unqueue_signals(); + return 1; + } + firstjob = curjob; + } else if (func == BIN_JOBS) { + /* List jobs. */ + struct job *jobptr; + int curmaxjob, ignorejob; + if (unset(MONITOR) && oldmaxjob) { + jobptr = oldjobtab; + curmaxjob = oldmaxjob ? oldmaxjob - 1 : 0; + ignorejob = 0; + } else { + jobptr = jobtab; + curmaxjob = maxjob; + ignorejob = thisjob; + } + for (job = 0; job <= curmaxjob; job++, jobptr++) + if (job != ignorejob && jobptr->stat) { + if ((!OPT_ISSET(ops,'r') && !OPT_ISSET(ops,'s')) || + (OPT_ISSET(ops,'r') && OPT_ISSET(ops,'s')) || + (OPT_ISSET(ops,'r') && + !(jobptr->stat & STAT_STOPPED)) || + (OPT_ISSET(ops,'s') && jobptr->stat & STAT_STOPPED)) + printjob(jobptr, lng, 2); + } + unqueue_signals(); + return 0; + } else { /* Must be BIN_WAIT, so wait for all jobs */ + for (job = 0; job <= maxjob; job++) + if (job != thisjob && jobtab[job].stat && + !(jobtab[job].stat & STAT_NOPRINT)) + retval = zwaitjob(job, 1); + unqueue_signals(); + return retval; + } + } + + /* 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, jstat; + + func = ofunc; + + if (func == BIN_WAIT && isanum(*argv)) { + /* wait can take a pid; the others can't. */ + pid_t pid = (long)atoi(*argv); + Job j; + Process p; + + if (findproc(pid, &j, &p, 0)) { + if (j->stat & STAT_STOPPED) { + retval = (killjb(j, SIGCONT) != 0); + if (retval == 0) + makerunning(j); + } + if (retval == 0) { + /* + * returns 0 for normal exit, else signal+128 + * in which case we should return that status. + */ + retval = waitforpid(pid, 1); + } + if (retval == 0) { + if ((retval = getbgstatus(pid)) < 0) { + retval = lastval2; + } + } + } else if ((retval = getbgstatus(pid)) < 0) { + zwarnnam(name, "pid %d is not a child of this shell", pid); + /* presumably lastval2 doesn't tell us a heck of a lot? */ + retval = 1; + } + thisjob = ocj; + continue; + } + if (func != BIN_JOBS && oldjobtab != NULL) { + zwarnnam(name, "can't manipulate jobs in subshell"); + unqueue_signals(); + return 1; + } + /* 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; + } + jstat = oldjobtab ? oldjobtab[job].stat : jobtab[job].stat; + if (!(jstat & STAT_INUSE) || + (jstat & STAT_NOPRINT)) { + zwarnnam(name, "%s: no such job", *argv); + unqueue_signals(); + return 1; + } + /* If AUTO_CONTINUE is set (automatically make stopped jobs running + * on disown), we actually do a bg and then delete the job table entry. */ + + if (isset(AUTOCONTINUE) && func == BIN_DISOWN && + jstat & STAT_STOPPED) + func = BIN_BG; + + /* 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; + jobtab[job].stat &= ~STAT_CURSH; + } + if ((stopped = (jobtab[job].stat & STAT_STOPPED))) { + makerunning(jobtab + job); + if (func == BIN_BG) { + /* Set $! to indicate this was backgrounded */ + Process pn = jobtab[job].procs; + for (;;) { + Process next = pn->next; + if (!next) { + lastpid = (zlong) pn->pid; + break; + } + pn = next; + } + } + } else if (func == BIN_BG) { + /* Silly to bg a job already running. */ + zwarnnam(name, "job already in background"); + thisjob = ocj; + unqueue_signals(); + 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 : lng, 3); + if (func != BIN_BG) { /* fg or wait */ + if (jobtab[job].pwd && strcmp(jobtab[job].pwd, pwd)) { + FILE *fout = (func == BIN_JOBS || !shout) ? stdout : shout; + fprintf(fout, "(pwd : "); + fprintdir(jobtab[job].pwd, fout); + fprintf(fout, ")\n"); + fflush(fout); + } + if (func != BIN_WAIT) { /* fg */ + thisjob = job; + if ((jobtab[job].stat & STAT_SUPERJOB) && + ((!jobtab[job].procs->next || + (jobtab[job].stat & STAT_SUBLEADER) || + killpg(jobtab[job].gleader, 0) == -1)) && + jobtab[jobtab[job].other].gleader) + attachtty(jobtab[jobtab[job].other].gleader); + else + 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) + { + retval = zwaitjob(job, 1); + if (!retval) + retval = lastval2; + } + else if (func != BIN_BG) { + /* + * HERE: there used not to be an "else" above. How + * could it be right to wait for the foreground job + * when we've just been told to wait for another + * job (and done it)? + */ + waitjobs(); + retval = lastval2; + } else if (ofunc == BIN_DISOWN) + deletejob(jobtab + job, 1); + break; + case BIN_JOBS: + printjob(job + (oldjobtab ? oldjobtab : jobtab), lng, 2); + break; + case BIN_DISOWN: + if (jobtab[job].stat & STAT_SUPERJOB) { + jobtab[job].stat |= STAT_DISOWN; + continue; + } + if (jobtab[job].stat & STAT_STOPPED) { + char buf[20], *pids = ""; + + if (jobtab[job].stat & STAT_SUPERJOB) { + Process pn; + + for (pn = jobtab[jobtab[job].other].procs; pn; pn = pn->next) { + sprintf(buf, " -%d", pn->pid); + pids = dyncat(pids, buf); + } + for (pn = jobtab[job].procs; pn->next; pn = pn->next) { + sprintf(buf, " %d", pn->pid); + pids = dyncat(pids, buf); + } + if (!jobtab[jobtab[job].other].procs && pn) { + sprintf(buf, " %d", pn->pid); + pids = dyncat(pids, buf); + } + } else { + sprintf(buf, " -%d", jobtab[job].gleader); + pids = buf; + } + zwarnnam(name, +#ifdef USE_SUSPENDED + "warning: job is suspended, use `kill -CONT%s' to resume", +#else + "warning: job is stopped, use `kill -CONT%s' to resume", +#endif + pids); + } + deletejob(jobtab + job, 1); + break; + } + thisjob = ocj; + } + unqueue_signals(); + return retval; +} + +static const struct { + const char *name; + int num; +} alt_sigs[] = { +#if defined(SIGCHLD) && defined(SIGCLD) +#if SIGCHLD == SIGCLD + { "CLD", SIGCLD }, +#endif +#endif +#if defined(SIGPOLL) && defined(SIGIO) +#if SIGPOLL == SIGIO + { "IO", SIGIO }, +#endif +#endif +#if !defined(SIGERR) + /* + * If SIGERR is not defined by the operating system, use it + * as an alias for SIGZERR. + */ + { "ERR", SIGZERR }, +#endif + { NULL, 0 } +}; + +/* 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, UNUSED(Options ops), UNUSED(int func)) +{ + int sig = SIGTERM; + int returnval = 0; + + /* check for, and interpret, a signal specifier */ + if (*argv && **argv == '-') { + if (idigit((*argv)[1])) { + char *endp; + /* signal specified by number */ + sig = zstrtol(*argv + 1, &endp, 10); + if (*endp) { + zwarnnam(nam, "invalid signal number: %s", *argv); + return 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) { + if (!strncmp(signame, "SIG", 3)) + signame += 3; + for (sig = 1; sig <= SIGCOUNT; sig++) + if (!strcasecmp(sigs[sig], signame)) + break; + if (sig > SIGCOUNT) { + int i; + + for (i = 0; alt_sigs[i].name; i++) + if (!strcasecmp(alt_sigs[i].name, signame)) + { + sig = alt_sigs[i].num; + break; + } + } + if (sig > SIGCOUNT) { + zwarnnam(nam, "unknown signal: SIG%s", + signame); + returnval++; + } else + printf("%d\n", sig); + } else { + if (*signame) { + zwarnnam(nam, "unknown signal: SIG%s", + signame); + 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] == 'n' && (*argv)[2] == '\0') { + char *endp; + + if (!*++argv) { + zwarnnam(nam, "-n: argument expected"); + return 1; + } + sig = zstrtol(*argv, &endp, 10); + if (*endp) { + zwarnnam(nam, "invalid signal number: %s", *argv); + return 1; + } + } else { + if (!((*argv)[1] == 's' && (*argv)[2] == '\0')) + signame = *argv + 1; + else if (!(*++argv)) { + zwarnnam(nam, "-s: argument expected"); + return 1; + } else + signame = *argv; + if (!*signame) { + zwarnnam(nam, "-: signal name expected"); + return 1; + } + signame = casemodify(signame, CASMOD_UPPER); + if (!strncmp(signame, "SIG", 3)) + signame+=3; + + /* check for signal matching specified name */ + for (sig = 1; sig <= SIGCOUNT; sig++) + if (!strcmp(*(sigs + sig), signame)) + break; + if (*signame == '0' && !signame[1]) + sig = 0; + if (sig > SIGCOUNT) { + int i; + + for (i = 0; alt_sigs[i].name; i++) + if (!strcmp(alt_sigs[i].name, signame)) + { + sig = alt_sigs[i].num; + break; + } + } + if (sig > SIGCOUNT) { + zwarnnam(nam, "unknown signal: SIG%s", signame); + zwarnnam(nam, "type kill -l for a list of signals"); + return 1; + } + } + } + argv++; + } + + /* Discard the standard "-" and "--" option breaks */ + if (*argv && (*argv)[0] == '-' && (!(*argv)[1] || (*argv)[1] == '-')) + argv++; + + if (!*argv) { + zwarnnam(nam, "not enough arguments"); + return 1; + } + + queue_signals(); + 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) { +#ifndef WIFCONTINUED + /* With WIFCONTINUED we find this out properly */ + if (sig == SIGCONT) + makerunning(jobtab + p); +#endif + 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); + returnval++; + } else { + int pid = atoi(*argv); + if (kill(pid, sig) == -1) { + zwarnnam("kill", "kill %s failed: %e", *argv, errno); + returnval++; + } +#ifndef WIFCONTINUED + else if (sig == SIGCONT) { + Job jn; + Process pn; + /* With WIFCONTINUED we find this out properly */ + if (findproc(pid, &jn, &pn, 0)) { + if (WIFSTOPPED(pn->status)) + pn->status = SP_RUNNING; + } + } +#endif + } + } + unqueue_signals(); + + return returnval < 126 ? returnval : 1; +} +/* Get a signal number from a string */ + +/**/ +mod_export int +getsignum(const char *s) +{ + int x, i; + + /* check for a signal specified by number */ + x = atoi(s); + if (idigit(*s) && x >= 0 && x < VSIGCOUNT) + return x; + + /* search for signal by name */ + if (!strncmp(s, "SIG", 3)) + s += 3; + + for (i = 0; i < VSIGCOUNT; i++) + if (!strcmp(s, sigs[i])) + return i; + + for (i = 0; alt_sigs[i].name; i++) + { + if (!strcmp(s, alt_sigs[i].name)) + return alt_sigs[i].num; + } + + /* no matching signal */ + return -1; +} + +/* Get the name for a signal. */ + +/**/ +mod_export const char * +getsigname(int sig) +{ + if (sigtrapped[sig] & ZSIG_ALIAS) + { + int i; + for (i = 0; alt_sigs[i].name; i++) + if (sig == alt_sigs[i].num) + return alt_sigs[i].name; + } + else + return sigs[sig]; + + /* shouldn't reach here */ +#ifdef DEBUG + dputs("Bad alias flag for signal"); +#endif + return ""; +} + + +/* Get the function node for a trap, taking care about alternative names */ +/**/ +HashNode +gettrapnode(int sig, int ignoredisable) +{ + char fname[20]; + HashNode hn; + HashNode (*getptr)(HashTable ht, const char *name); + int i; + if (ignoredisable) + getptr = shfunctab->getnode2; + else + getptr = shfunctab->getnode; + + sprintf(fname, "TRAP%s", sigs[sig]); + if ((hn = getptr(shfunctab, fname))) + return hn; + + for (i = 0; alt_sigs[i].name; i++) { + if (alt_sigs[i].num == sig) { + sprintf(fname, "TRAP%s", alt_sigs[i].name); + if ((hn = getptr(shfunctab, fname))) + return hn; + } + } + + return NULL; +} + +/* Remove a TRAP function under any name for the signal */ + +/**/ +void +removetrapnode(int sig) +{ + HashNode hn = gettrapnode(sig, 1); + if (hn) { + shfunctab->removenode(shfunctab, hn->nam); + shfunctab->freenode(hn); + } +} + +/* Suspend this shell */ + +/**/ +int +bin_suspend(char *name, UNUSED(char **argv), Options ops, UNUSED(int func)) +{ + /* won't suspend a login shell, unless forced */ + if (islogin && !OPT_ISSET(ops,'f')) { + zwarnnam(name, "can't suspend login shell"); + return 1; + } + if (jobbing) { + /* stop ignoring signals */ + signal_default(SIGTTIN); + signal_default(SIGTSTP); + signal_default(SIGTTOU); + + /* Move ourselves back to the process group we came from */ + release_pgrp(); + } + + /* suspend ourselves with a SIGTSTP */ + killpg(origpgrp, SIGTSTP); + + if (jobbing) { + acquire_pgrp(); + /* restore signal handling */ + signal_ignore(SIGTTOU); + signal_ignore(SIGTSTP); + signal_ignore(SIGTTIN); + } + return 0; +} + +/* find a job named s */ + +/**/ +int +findjobnam(const char *s) +{ + int jobnum; + + for (jobnum = maxjob; jobnum >= 0; jobnum--) + if (!(jobtab[jobnum].stat & (STAT_SUBJOB | STAT_NOPRINT)) && + jobtab[jobnum].stat && jobtab[jobnum].procs && jobnum != thisjob && + jobtab[jobnum].procs->text[0] && strpfx(s, jobtab[jobnum].procs->text)) + return jobnum; + return -1; +} + + +/* make sure we are a process group leader by creating a new process + group if necessary */ + +/**/ +void +acquire_pgrp(void) +{ + long ttpgrp; + sigset_t blockset, oldset; + + if ((mypgrp = GETPGRP()) >= 0) { + long lastpgrp = mypgrp; + sigemptyset(&blockset); + sigaddset(&blockset, SIGTTIN); + sigaddset(&blockset, SIGTTOU); + sigaddset(&blockset, SIGTSTP); + oldset = signal_block(blockset); + while ((ttpgrp = gettygrp()) != -1 && ttpgrp != mypgrp) { + mypgrp = GETPGRP(); + if (mypgrp == mypid) { + if (!interact) + break; /* attachtty() will be a no-op, give up */ + signal_setmask(oldset); + attachtty(mypgrp); /* Might generate SIGT* */ + signal_block(blockset); + } + if (mypgrp == gettygrp()) + break; + signal_setmask(oldset); + if (read(0, NULL, 0) != 0) {} /* Might generate SIGT* */ + signal_block(blockset); + mypgrp = GETPGRP(); + if (mypgrp == lastpgrp && !interact) + break; /* Unlikely that pgrp will ever change */ + lastpgrp = mypgrp; + } + if (mypgrp != mypid) { + if (setpgrp(0, 0) == 0) { + mypgrp = mypid; + attachtty(mypgrp); + } else + opts[MONITOR] = 0; + } + signal_setmask(oldset); + } else + opts[MONITOR] = 0; +} + +/* revert back to the process group we came from (before acquire_pgrp) */ + +/**/ +void +release_pgrp(void) +{ + if (origpgrp != mypgrp) { + /* in linux pid namespaces, origpgrp may never have been set */ + if (origpgrp) { + attachtty(origpgrp); + setpgrp(0, origpgrp); + } + mypgrp = origpgrp; + } +} |
