diff options
| author | Craig Jennings <c@cjennings.net> | 2025-05-21 22:01:35 -0500 | 
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2025-05-21 22:01:35 -0500 | 
| commit | b4463015b97912658d630377fafbf630f7588d1e (patch) | |
| tree | d04b66d992fe2ce88391889c21c5d8dc97acd0ef /dotfiles/system/.zsh/modules/Src/jobs.c | |
| parent | 548154ea395356868e87980b149dfc0abdc84e17 (diff) | |
moving arch dotfiles into archsetup
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; +    } +} | 
