diff -r 000000000000 -r 2e3d3ce01487 openenvutils/commandshell/shell/src/jobs.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/openenvutils/commandshell/shell/src/jobs.c Tue Feb 02 10:12:00 2010 +0200 @@ -0,0 +1,2222 @@ +// jobs.c - job control +// +// © Portions Copyright (c) Symbian Software Ltd 2007. All rights reserved. +// +/* + * 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" + +#ifdef __SYMBIAN32__ +#ifdef __WINSCW__ +#pragma warn_unusedarg off +#pragma warn_possunwant off +#endif//__WINSCW__ +#endif//__SYMBIAN32__ + +#ifdef __SYMBIAN32__ +#include "dummy.h" +#endif //__SYMBIAN32__ + +/* 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 */ + +/**/ +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; + + for (i = 1; i <= maxjob; i++) + { + for (pn = aux ? jobtab[i].auxprocs : jobtab[i].procs; + pn; pn = pn->next) + if (pn->pid == pid) { + *pptr = pn; + *jptr = jobtab + i; + return 1; + } + } + + return 0; +} + +/* Does the given job number have any processes? */ + +/**/ +int +hasprocs(int job) +{ + Job 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 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); */ + /* 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); + } + 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 +} + + +#ifndef HAVE_GETRUSAGE +/* 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; + /* get time-accounting info */ + get_usage(); + gettimeofday(&pn->endtime, &dummy_tz); /* record time process exited */ + + 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 */ +} +#endif + +/* 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) + if (pn->status == SP_RUNNING) + return; + + 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) { + 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) { + if (!errbrk_saved) { + errbrk_saved = 1; + prev_breaks = breaks; + prev_errflag = errflag; + } + breaks = loops; + errflag = 1; + inerrflush(); + } + } else { + attachtty(mypgrp); + /* check window size and adjust if necessary */ + adjustwinsize(0); + } + } + } else if (list_pipe && (val & 0200) && inforeground == 1) { + if (!errbrk_saved) { + errbrk_saved = 1; + prev_breaks = breaks; + prev_errflag = errflag; + } + breaks = loops; + errflag = 1; + inerrflush(); + } + if (somestopped && jn->stat & STAT_SUPERJOB) + return; + jn->stat |= (somestopped) ? STAT_CHANGED | STAT_STOPPED : + STAT_CHANGED | STAT_DONE; + if (job == thisjob && (jn->stat & STAT_DONE)) { + int i; + Process p; + + for (p = jn->procs, i = 0; p && i < MAX_PIPESTATS; p = p->next, i++) + pipestats[i] = ((WIFSIGNALED(p->status)) ? + 0200 | WTERMSIG(p->status) : + WEXITSTATUS(p->status)); + if ((jn->stat & STAT_CURSH) && i < MAX_PIPESTATS) + pipestats[i++] = lastval; + numpipestats = i; + } + 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)) { + printjob(jn, !!isset(LONGLISTJOBS), 0); + if (zleactive) + zrefresh(); + } + 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 == 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 = 1; + } + } + } +} + +/* 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; +} + +/**/ +#ifndef HAVE_GETRUSAGE +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 +} +/**/ +#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, child_times_t *ti, char *desc) +{ + char *s; + double elapsed_time, user_time, system_time; +#ifdef HAVE_GETRUSAGE + double total_time; +#endif + int percent; + + if (!desc) + desc = ""; + + /* 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 + set_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; + + 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; +#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", (long)(ti->ru_ixrss / total_time)); + break; +#endif +#ifdef HAVE_STRUCT_RUSAGE_RU_IDRSS + case 'D': + fprintf(stderr, "%ld", + (long) ((ti->ru_idrss +#ifdef HAVE_STRUCT_RUSAGE_RU_ISRSS + + ti->ru_isrss +#endif + ) / total_time)); + 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", + (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)); + 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': + fprintf(stderr, "%s", desc); + 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"; + zlong reporttime; + + /* if the time keyword was used */ + if (j->stat & STAT_TIMED) + return 1; + + queue_signals(); + if (!(v = getvalue(&vbuf, &s, 0)) || + (reporttime = getintvalue(v)) < 0) { + unqueue_signals(); + return 0; + } + unqueue_signals(); + /* can this ever happen? */ + if (!j->procs) + return 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--; + return reporttime <= 0; +#else + set_clktck(); + return ((j->procs->ti.ut + j->procs->ti.st) / clktck >= reporttime); +#endif +} + +/* !(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, 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; + + /* + * Wow, what a hack. Did I really write this? --- pws + */ + if (jn < jobtab || jn >= jobtab + jobtabsize) + job = jn - oldjobtab; + else + job = jn - jobtab; + + 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: ignore option state on explicit call to `jobs'. */ + + if (synch == 2 || + (interact && jobbing && + ((jn->stat & STAT_STOPPED) || sflag || job != thisjob))) { + int len2, fline = 1; + /* use special format for current job, except in `jobs' */ + int thisfmt = job == thisjob && synch != 2; + Process qn; + + if (!synch) + trashzle(); + if (doputnl && !synch) + 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; + } + if (!thisfmt) { + 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) + 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 && + jn->pwd && strcmp(jn->pwd, pwd))) { + fprintf(shout, "(pwd %s: ", (lng & 4) ? "" : "now"); + fprintdir(((lng & 4) && jn->pwd) ? 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 +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); + 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. + */ + +/**/ +void +deletejob(Job jn) +{ + deletefilelist(jn->filelist); + if (jn->stat & STAT_ATTACH) { + attachtty(mypgrp); + adjustwinsize(0); + } + + 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 */ + +/**/ +void +waitforpid(pid_t pid) +{ + 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() */ + while (!errflag && (kill(pid, 0) >= 0 || errno != ESRCH)) { + if (first) + first = 0; + else + kill(pid, SIGCONT); + + signal_suspend(SIGCHLD, SIGINT); + child_block(); + } + child_unblock(); + restore_queue_signals(q); +} + +/* wait for a job to finish */ + +/**/ +static void +zwaitjob(int job, int sig) +{ + int q = queue_signal_level(); + Job jn = jobtab + job; + + dont_queue_signals(); + child_block(); /* unblocked during signal_suspend() */ + if (jn->procs || jn->auxprocs) { /* 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))) { + signal_suspend(SIGCHLD, 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 (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); + pipestats[0] = lastval; + numpipestats = 1; + } + child_unblock(); + restore_queue_signals(q); +} + +/* 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); + pipestats[0] = lastval; + numpipestats = 1; + } + thisjob = -1; +} + +/* clear job table when entering subshells */ + +/**/ +mod_export void +clearjobtab(int monitor) +{ + int i; + + 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 && 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); + 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; +} + +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", NULL, 0); + 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 (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 (!hasprocs(thisjob)) + deletejob(jobtab + thisjob); + else + jobtab[thisjob].stat |= STAT_LOCKED; + 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, 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; 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 job handling. */ + +/**/ +void +init_jobs(char **argv, char **envp) +{ + char *p, *q; + 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.", NULL, 0); + exit(1); + } + jobtabsize = MAXJOBS_ALLOC; + memset(jobtab, 0, init_bytes); + + /* + * 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); + } + for(; *envp; envp++) { + q = *envp; + if(q != p+1) + goto done; + p = strchr(q, 0); + } + done: + hackspace = p - hackzero; +} + + +/* + * 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(); +} + +#ifndef __SYMBIAN32__ +/* 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", NULL, 0); + return 1; + } + if(!argv[0] || argv[1]) { + zwarnnam(name, "-Z requires one argument", NULL, 0); + return 1; + } + queue_signals(); + unmetafy(*argv, &len); + if(len > hackspace) + len = hackspace; + memcpy(hackzero, *argv, len); + memset(hackzero + len, 0, hackspace - len); + unqueue_signals(); + return 0; + } + + lng = (OPT_ISSET(ops,'l')) ? 1 : (OPT_ISSET(ops,'p')) ? 2 : 0; + if (OPT_ISSET(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; + } + + queue_signals(); + /* 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", NULL, 0); + 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) + zwaitjob(job, SIGINT); + unqueue_signals(); + 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; + + 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)) + waitforpid(pid); + else + zwarnnam(name, "pid %d is not a child of this shell", 0, pid); + 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); + 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 && + jobtab[job].stat & 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; + 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; + 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 : 0, 1); + if (func != BIN_BG) { /* fg or wait */ + if (jobtab[job].pwd && 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; + 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) + zwaitjob(job, SIGINT); + if (func != BIN_BG) { + waitjobs(); + retval = lastval2; + } else if (ofunc == BIN_DISOWN) + deletejob(jobtab + job); + break; + case BIN_JOBS: + printjob(job + jobtab, lng, 2); + break; + case BIN_DISOWN: + 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, 0); + } + deletejob(jobtab + job); + break; + } + thisjob = ocj; + } + unqueue_signals(); + return retval; +} +#endif //__SYMBIAN32__ +#if defined(SIGCHLD) && defined(SIGCLD) +#if SIGCHLD == SIGCLD +#define ALT_SIGS 1 +#endif +#endif +#if defined(SIGPOLL) && defined(SIGIO) +#if SIGPOLL == SIGIO +#define ALT_SIGS 1 +#endif +#endif + +#ifdef ALT_SIGS +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 + { NULL, 0 } +}; +#endif + +#ifndef __SYMBIAN32__ +/* 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])) + /* signal specified by number */ + sig = atoi(*argv + 1); + else if ((*argv)[1] != '-' || (*argv)[2]) { + char *signame = NULL; + + /* 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; +#ifdef ALT_SIGS + if (sig > SIGCOUNT) { + int i; + + for (i = 0; alt_sigs[i].name; i++) + if (!cstrpcmp(&alt_sigs[i].name, &signame)) + { + sig = alt_sigs[i].num; + break; + } + } +#endif + 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] == 'n' && (*argv)[2] == '\0') { + char *endp; + + if (!*++argv) { + zwarnnam(nam, "-n: argument expected", NULL, 0); + return 1; + } + sig = zstrtol(*argv, &endp, 10); + if (*endp) { + zwarnnam(nam, "invalid signal number", signame, 0); + return 1; + } + } else { + if (!((*argv)[1] == 's' && (*argv)[2] == '\0')) + signame = *argv + 1; + else if (!(*++argv)) { + zwarnnam(nam, "-s: argument expected", NULL, 0); + return 1; + } else + signame = *argv; + makeuppercase(&signame); + 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; +#ifdef ALT_SIGS + 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; + } + } +#endif + 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++; + } + + if (!*argv) { + zwarnnam(nam, "not enough arguments", NULL, 0); + 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) { + 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++; + } + } + unqueue_signals(); + + return returnval < 126 ? returnval : 1; +} +#endif //__SYMBIAN32__ +/* Get a signal number from a string */ + +/**/ +mod_export int +getsignum(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 */ + for (i = 0; i < VSIGCOUNT; i++) + if (!strcmp(s, sigs[i])) + return i; + +#ifdef ALT_SIGS + for (i = 0; alt_sigs[i].name; i++) + { + if (!strcmp(s, alt_sigs[i].name)) + return alt_sigs[i].num; + } +#endif + + /* no matching signal */ + return -1; +} + +/* 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, char *name); +#ifdef ALT_SIGS + int i; +#endif + if (ignoredisable) + getptr = shfunctab->getnode2; + else + getptr = shfunctab->getnode; + + sprintf(fname, "TRAP%s", sigs[sig]); + if ((hn = getptr(shfunctab, fname))) + return hn; + +#ifdef ALT_SIGS + 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; + } + } +#endif + + 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", NULL, 0); + 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(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 && 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) { + 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) { + signal_setmask(oldset); + attachtty(mypgrp); /* Might generate SIGT* */ + signal_block(blockset); + } + if (mypgrp == gettygrp()) + break; + signal_setmask(oldset); + read(0, NULL, 0); /* Might generate SIGT* */ + signal_block(blockset); + mypgrp = GETPGRP(); + } + 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) { + attachtty(origpgrp); + setpgrp(0, origpgrp); + mypgrp = origpgrp; + } +}