ftp.nice.ch/pub/next/unix/shell/zsh.3.0.5.NIHS.bs.tar.gz#/zsh.3.0.5.NIHS.bs/src/Src/jobs.c

This is jobs.c in view mode; [Download] [Up]

/*
 * $Id: jobs.c,v 2.17 1996/10/15 20:16:35 hzoli Exp $
 *
 * jobs.c - job control
 *
 * This file is part of zsh, the Z shell.
 *
 * Copyright (c) 1992-1996 Paul Falstad
 * All rights reserved.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and to distribute modified versions of this software for any
 * purpose, provided that the above copyright notice and the following
 * two paragraphs appear in all copies of this software.
 *
 * In no event shall Paul Falstad or the Zsh Development Group be liable
 * to any party for direct, indirect, special, incidental, or consequential
 * damages arising out of the use of this software and its documentation,
 * even if Paul Falstad and the Zsh Development Group have been advised of
 * the possibility of such damage.
 *
 * Paul Falstad and the Zsh Development Group specifically disclaim any
 * warranties, including, but not limited to, the implied warranties of
 * merchantability and fitness for a particular purpose.  The software
 * provided hereunder is on an "as is" basis, and Paul Falstad and the
 * Zsh Development Group have no obligation to provide maintenance,
 * support, updates, enhancements, or modifications.
 *
 */

#include "zsh.h"

/* empty job structure for quick clearing of jobtab entries */

static struct job zero;		/* static variables are initialized to zero */

struct timeval dtimeval, now;

/* Diff two timevals for elapsed-time computations */

/**/
struct timeval *
dtime(struct timeval *dt, struct timeval *t1, struct timeval *t2)
{
    dt->tv_sec = t2->tv_sec - t1->tv_sec;
    dt->tv_usec = t2->tv_usec - t1->tv_usec;
    if (dt->tv_usec < 0) {
	dt->tv_usec += 1000000.0;
	dt->tv_sec -= 1.0;
    }
    return dt;
}

/* change job table entry from stopped to running */

/**/
void
makerunning(Job jn)
{
    Process pn;

    jn->stat &= ~STAT_STOPPED;
    for (pn = jn->procs; pn; pn = pn->next)
	if (WIFSTOPPED(pn->status) && 
	    (!(jn->stat & STAT_SUPERJOB) || pn->next))
	    pn->status = SP_RUNNING;

    if (jn->stat & STAT_SUPERJOB)
	makerunning(jobtab + jn->other);
}

/* Find process and job associated with pid.         *
 * Return 1 if search was successful, else return 0. */

/**/
int
findproc(pid_t pid, Job *jptr, Process *pptr)
{
    Process pn;
    int i;

    for (i = 1; i < MAXJOB; i++)
	for (pn = jobtab[i].procs; pn; pn = pn->next)
	    if (pn->pid == pid) {
		*pptr = pn;
		*jptr = jobtab + i;
		return 1;
	    }

    return 0;
}

/* Update status of process that we have just WAIT'ed for */

/**/
void
update_process(Process pn, int status)
{
    struct timezone dummy_tz;
    long childs, childu;

    childs = shtms.tms_cstime;
    childu = shtms.tms_cutime;
    times(&shtms);                          /* get time-accounting info          */

    pn->status = status;                    /* save the status returned by WAIT  */
    pn->ti.st  = shtms.tms_cstime - childs; /* compute process system space time */
    pn->ti.ut  = shtms.tms_cutime - childu; /* compute process user space time   */

    gettimeofday(&pn->endtime, &dummy_tz);  /* record time process exited        */
}

/* Update status of job, possibly printing it */

/**/
void
update_job(Job jn)
{
    Process pn;
    int job;
    int val = 0, status = 0;
    int somestopped = 0, inforeground = 0;

    for (pn = jn->procs; pn; pn = pn->next) {
	if (pn->status == SP_RUNNING)      /* some processes in this job are running       */
	    return;                        /* so no need to update job table entry         */
	if (WIFSTOPPED(pn->status))        /* some processes are stopped                   */
	    somestopped = 1;               /* so job is not done, but entry needs updating */
	if (!pn->next)                     /* last job in pipeline determines exit status  */
	    val = (WIFSIGNALED(pn->status)) ? 0200 | WTERMSIG(pn->status) :
		WEXITSTATUS(pn->status);
	if (pn->pid == jn->gleader)        /* if this process is process group leader      */
	    status = pn->status;
    }

    job = jn - jobtab;   /* compute job number */

    if (somestopped) {
	if (shout && job == thisjob) {
	    if (!jn->ty)
		jn->ty = (struct ttyinfo *) zalloc(sizeof(struct ttyinfo));
	    gettyinfo(jn->ty);
	}
	if (jn->stat & STAT_STOPPED)
	    return;
    } else {                   /* job is done, so remember return value */
	lastval2 = val;
	/* If last process was run in the current shell, keep old status
	 * and let it handle its own traps
	 */
	if (job == thisjob && !(jn->stat & STAT_CURSH)) {
	  lastval = val;
	  inforeground = 1;
	}
    }

    if (shout && !ttyfrozen && !jn->stty_in_env &&
	job == thisjob && !somestopped && !(jn->stat & STAT_NOSTTY))
	gettyinfo(&shttyinfo);

    if (isset(MONITOR)) {
	pid_t pgrp = gettygrp();           /* get process group of tty      */

	/* is this job in the foreground of an interactive shell? */
	if (mypgrp != pgrp && inforeground &&
	    (jn->gleader == pgrp || (pgrp > 1 && kill(-pgrp, 0) == -1))) {
	    attachtty(mypgrp);
	    adjustwinsize(0);   /* check window size and adjust if necessary */
	}
    }

    if (somestopped && jn->stat & STAT_SUPERJOB)
	return;
    jn->stat |= (somestopped) ? STAT_CHANGED | STAT_STOPPED :
	STAT_CHANGED | STAT_DONE;
    if ((jn->stat & (STAT_DONE | STAT_STOPPED)) == STAT_STOPPED) {
	prevjob = curjob;
	curjob = job;
    }
    if ((isset(NOTIFY) || job == thisjob) && (jn->stat & STAT_LOCKED)) {
	printjob(jn, !!isset(LONGLISTJOBS), 0);
	if (zleactive)
	    refresh();
    }
    if (sigtrapped[SIGCHLD] && job != thisjob)
	dotrap(SIGCHLD);

    /* When MONITOR is set, the foreground process runs in a different *
     * process group from the shell, so the shell will not receive     *
     * terminal signals, therefore we we pretend that the shell got    *
     * the signal too.                                                 */
    if (inforeground && isset(MONITOR) && WIFSIGNALED(status)) {
	int sig = WTERMSIG(status);

	if (sig == SIGINT || sig == SIGQUIT) {
	    if (sigtrapped[sig]) {
		dotrap(sig);
		/* We keep the errflag as set or not by dotrap.
		 * This is to fulfil the promise to carry on
		 * with the jobs if trap returns zero.
		 * Setting breaks = loops ensures a consistent return
		 * status if inside a loop.  Maybe the code in loops
		 * should be changed.
		 */
		if (errflag)
		    breaks = loops;
	    } else {
		breaks = loops;
		errflag = 1;
	    }
	}
    }
}

/* lng = 0 means jobs    *
 * lng = 1 means jobs -l *
 * lng = 2 means jobs -p 
 *
 * synch = 0 means asynchronous
 * synch = 1 means synchronous
 * synch = 2 means called synchronously from jobs
*/

/**/
void
printjob(Job jn, int lng, int synch)
{
    Process pn;
    int job = jn - jobtab, len = 9, sig, sflag = 0, llen;
    int conted = 0, lineleng = columns, skip = 0, doputnl = 0;
    FILE *fout = (synch == 2) ? stdout : shout;

    if (jn->stat & STAT_NOPRINT)
	return;

    if (lng < 0) {
	conted = 1;
	lng = 0;
    }

/* find length of longest signame, check to see */
/* if we really need to print this job          */

    for (pn = jn->procs; pn; pn = pn->next) {
	if (jn->stat & STAT_SUPERJOB &&
	    jn->procs->status == SP_RUNNING && !pn->next)
	    pn->status = SP_RUNNING;
	if (pn->status != SP_RUNNING)
	    if (WIFSIGNALED(pn->status)) {
		sig = WTERMSIG(pn->status);
		llen = strlen(sigmsg[sig]);
		if (WCOREDUMP(pn->status))
		    llen += 14;
		if (llen > len)
		    len = llen;
		if (sig != SIGINT && sig != SIGPIPE)
		    sflag = 1;
		if (job == thisjob && sig == SIGINT)
		    doputnl = 1;
	    } else if (WIFSTOPPED(pn->status)) {
		sig = WSTOPSIG(pn->status);
		if ((int)strlen(sigmsg[sig]) > len)
		    len = strlen(sigmsg[sig]);
		if (job == thisjob && sig == SIGTSTP)
		    doputnl = 1;
	    } else if (isset(PRINTEXITVALUE) && isset(SHINSTDIN) &&
		       WEXITSTATUS(pn->status))
		sflag = 1;
    }

/* print if necessary */

    if (interact && jobbing && ((jn->stat & STAT_STOPPED) || sflag ||
				job != thisjob)) {
	int len2, fline = 1;
	Process qn;

	if (!synch)
	    trashzle();
	if (doputnl && !synch)
	    putc('\n', fout);
	for (pn = jn->procs; pn;) {
	    len2 = ((job == thisjob) ? 5 : 10) + len;	/* 2 spaces */
	    if (lng)
		qn = pn->next;
	    else
		for (qn = pn->next; qn; qn = qn->next) {
		    if (qn->status != pn->status)
			break;
		    if ((int)strlen(qn->text) + len2 + ((qn->next) ? 3 : 0) > lineleng)
			break;
		    len2 += strlen(qn->text) + 2;
		}
	    if (job != thisjob)
		if (fline)
		    fprintf(fout, "[%ld]  %c ",
			    (long)(jn - jobtab),
			    (job == curjob) ? '+'
			    : (job == prevjob) ? '-' : ' ');
		else
		    fprintf(fout, (job > 9) ? "        " : "       ");
	    else
		fprintf(fout, "zsh: ");
	    if (lng)
		if (lng == 1)
		    fprintf(fout, "%ld ", (long) pn->pid);
		else {
		    pid_t x = jn->gleader;

		    fprintf(fout, "%ld ", (long) x);
		    do
			skip++;
		    while ((x /= 10));
		    skip++;
		    lng = 0;
	    } else
		fprintf(fout, "%*s", skip, "");
	    if (pn->status == SP_RUNNING)
		if (!conted)
		    fprintf(fout, "running%*s", len - 7 + 2, "");
		else
		    fprintf(fout, "continued%*s", len - 9 + 2, "");
	    else if (WIFEXITED(pn->status))
		if (WEXITSTATUS(pn->status))
		    fprintf(fout, "exit %-4d%*s", WEXITSTATUS(pn->status),
			    len - 9 + 2, "");
		else
		    fprintf(fout, "done%*s", len - 4 + 2, "");
	    else if (WIFSTOPPED(pn->status))
		fprintf(fout, "%-*s", len + 2, sigmsg[WSTOPSIG(pn->status)]);
	    else if (WCOREDUMP(pn->status))
		fprintf(fout, "%s (core dumped)%*s",
			sigmsg[WTERMSIG(pn->status)],
			(int)(len - 14 + 2 - strlen(sigmsg[WTERMSIG(pn->status)])), "");
	    else
		fprintf(fout, "%-*s", len + 2, sigmsg[WTERMSIG(pn->status)]);
	    for (; pn != qn; pn = pn->next)
		fprintf(fout, (pn->next) ? "%s | " : "%s", pn->text);
	    putc('\n', fout);
	    fline = 0;
	}
	fflush(fout);
    } else if (doputnl && interact && !synch) {
	putc('\n', fout);
	fflush(fout);
    }

/* print "(pwd now: foo)" messages */

    if (interact && job == thisjob && strcmp(jn->pwd, pwd)) {
	fprintf(shout, "(pwd now: ");
	fprintdir(pwd, shout);
	fprintf(shout, ")\n");
	fflush(shout);
    }
/* delete job if done */

    if (jn->stat & STAT_DONE) {
	if (should_report_time(jn))
	    dumptime(jn);
	deletejob(jn);
	if (job == curjob) {
	    curjob = prevjob;
	    prevjob = job;
	}
	if (job == prevjob)
	    setprevjob();
    } else
	jn->stat &= ~STAT_CHANGED;
}

/**/
void
deletefilelist(LinkList file_list)
{
    char *s;
    if (file_list) {
	while ((s = (char *)getlinknode(file_list))) {
	    unlink(s);
	    zsfree(s);
	}
	zfree(file_list, sizeof(struct linklist));
    }
}

/**/
void
deletejob(Job jn)
{
    struct process *pn, *nx;

    for (pn = jn->procs; pn; pn = nx) {
	nx = pn->next;
	zfree(pn, sizeof(struct process));
    }
    zsfree(jn->pwd);

    deletefilelist(jn->filelist);

    if (jn->ty)
	zfree(jn->ty, sizeof(struct ttyinfo));

    *jn = zero;
}

/* set the previous job to something reasonable */

/**/
void
setprevjob(void)
{
    int i;

    for (i = MAXJOB - 1; i; i--)
	if ((jobtab[i].stat & STAT_INUSE) && (jobtab[i].stat & STAT_STOPPED) &&
	    i != curjob && i != thisjob) {
	    prevjob = i;
	    return;
	}

    for (i = MAXJOB - 1; i; i--)
	if ((jobtab[i].stat & STAT_INUSE) && i != curjob && i != thisjob) {
	    prevjob = i;
	    return;
	}

    prevjob = -1;
}

/* add a process to the current job */

/**/
void
addproc(pid_t pid, char *text)
{
    Process pn;
    struct timezone dummy_tz;

    pn = (Process) zcalloc(sizeof *pn);
    pn->pid = pid;
    if (text)
	strcpy(pn->text, text);
    else
	*pn->text = '\0';
    gettimeofday(&pn->bgtime, &dummy_tz);
    pn->status = SP_RUNNING;
    pn->next = NULL;

    /* if this is the first process we are adding to *
     * the job, then it's the group leader.          */
    if (!jobtab[thisjob].gleader)
	jobtab[thisjob].gleader = pid;

    /* attach this process to end of process list of current job */
    if (jobtab[thisjob].procs) {
	Process n;

	for (n = jobtab[thisjob].procs; n->next; n = n->next);
	pn->next = NULL;
	n->next = pn;
    } else {
	/* first process for this job */
	jobtab[thisjob].procs = pn;
    }
}

/* Check if we have files to delete.  We need to check this to see *
 * if it's all right to exec a command without forking in the last *
 * component of subshells or after the `-c' option.                */

/**/
int
havefiles(void)
{
    int i;

    for (i = 1; i < MAXJOB; i++)
	if (jobtab[i].stat && jobtab[i].filelist)
	    return 1;
    return 0;

}

/* wait for a particular process */

/**/
void
waitforpid(pid_t pid)
{
    int first = 1;

    /* child_block() around this loop in case #ifndef WNOHANG */
    child_block();		/* unblocked in child_suspend() */
    while (!errflag && (kill(pid, 0) >= 0 || errno != ESRCH)) {
	if (first)
	    first = 0;
	else
	    kill(pid, SIGCONT);

	child_suspend(SIGINT);
	child_block();
    }
    child_unblock();
}

/* wait for a job to finish */

/**/
void
waitjob(int job, int sig)
{
    Job jn = jobtab + job;

    child_block();		 /* unblocked during child_suspend() */
    if (jn->procs) {		 /* if any forks were done         */
	jn->stat |= STAT_LOCKED;
	if (jn->stat & STAT_CHANGED)
	    printjob(jn, !!isset(LONGLISTJOBS), 1);
	while (!errflag && jn->stat &&
	       !(jn->stat & STAT_DONE) &&
	       !(interact && (jn->stat & STAT_STOPPED))) {
	    child_suspend(sig);
	    /* Commenting this out makes ^C-ing a job started by a function
	       stop the whole function again.  But I guess it will stop
	       something else from working properly, we have to find out
	       what this might be.  --oberon

	    errflag = 0; */
	    if (jn->stat & STAT_SUPERJOB) {
		Job sj = jobtab + jn->other;
		if (sj->stat & STAT_DONE) {
		    struct process *p;
		    
		    for (p = sj->procs; p; p = p->next)
			if (WIFSIGNALED(p->status)) {
			    killpg(jn->gleader, WTERMSIG(p->status));
			    kill(sj->other, SIGCONT);
			    kill(sj->other, WTERMSIG(p->status));
			    break;
			}
		    if (!p) {
			jn->stat &= ~STAT_SUPERJOB;
			kill(sj->other, SIGCONT);
			deletejob(sj);
		    }
		    curjob = jn - jobtab;
		}
		else if (sj->stat & STAT_STOPPED) {
		    struct process *p;

		    jn->stat |= STAT_STOPPED;
		    for (p = jn->procs; p; p = p->next)
			p->status = sj->procs->status;
		    curjob = jn - jobtab;
		    printjob(jn, !!isset(LONGLISTJOBS), 1);
		    break;
		}
	    }
	    child_block();
	}
    } else
	deletejob(jn);
    child_unblock();
}

/* wait for running job to finish */

/**/
void
waitjobs(void)
{
    waitjob(thisjob, 0);
    thisjob = -1;
}

/* clear job table when entering subshells */

/**/
void
clearjobtab(void)
{
    int i;

    for (i = 1; i < MAXJOB; i++) {
	if (jobtab[i].pwd)
	    zsfree(jobtab[i].pwd);
	if (jobtab[i].ty)
	    zfree(jobtab[i].ty, sizeof(struct ttyinfo));
    }

    memset(jobtab, 0, sizeof(jobtab)); /* zero out table */
}

/* Get a free entry in the job table and initialize it. */

/**/
int
initjob(void)
{
    int i;

    for (i = 1; i < MAXJOB; i++)
	if (!jobtab[i].stat) {
	    jobtab[i].stat = STAT_INUSE;
	    jobtab[i].pwd = ztrdup(pwd);
	    jobtab[i].gleader = 0;
	    return i;
	}

    zerr("job table full or recursion limit exceeded", NULL, 0);
    return -1;
}

/* print pids for & */

/**/
void
spawnjob(void)
{
    Process pn;

    /* if we are not in a subshell */
    if (!subsh) {
	if (curjob == -1 || !(jobtab[curjob].stat & STAT_STOPPED)) {
	    curjob = thisjob;
	    setprevjob();
	} else if (prevjob == -1 || !(jobtab[prevjob].stat & STAT_STOPPED))
	    prevjob = thisjob;
	if (interact && jobbing && jobtab[thisjob].procs) {
	    fprintf(stderr, "[%d]", thisjob);
	    for (pn = jobtab[thisjob].procs; pn; pn = pn->next)
		fprintf(stderr, " %ld", (long) pn->pid);
	    fprintf(stderr, "\n");
	    fflush(stderr);
	}
    }
    if (!jobtab[thisjob].procs)
	deletejob(jobtab + thisjob);
    else
	jobtab[thisjob].stat |= STAT_LOCKED;
    thisjob = -1;
}

static long clktck = 0;

static void
set_clktck(void)
{
#ifdef _SC_CLK_TCK
    if (!clktck)
	/* fetch clock ticks per second from *
	 * sysconf only the first time       */
	clktck = sysconf(_SC_CLK_TCK);
#else
# ifdef __NeXT__
    /* NeXTStep 3.3 defines CLK_TCK wrongly */
    clktck = 60;
# else
#  ifdef CLK_TCK
    clktck = CLK_TCK;
#  else
#   ifdef HZ
     clktck = HZ;
#   else
     clktck = 60;
#   endif
#  endif
# endif
#endif
}

/* Check whether shell should report the amount of time consumed   *
 * by job.  This will be the case if we have preceded the command  *
 * with the keyword time, or if REPORTTIME is non-negative and the *
 * amount of time consumed by the job is greater than REPORTTIME   */

/**/
int
should_report_time(Job j)
{
    Value v;
    char *s = "REPORTTIME";
    int reporttime;

    /* if the time keyword was used */
    if (j->stat & STAT_TIMED)
	return 1;

    if (!(v = getvalue(&s, 0)) || (reporttime = getintvalue(v)) < 0)
	return 0;

    /* can this ever happen? */
    if (!j->procs)
	return 0;

    set_clktck();
    return ((j->procs->ti.ut + j->procs->ti.st) / clktck >= reporttime);
}

/**/
void
printhhmmss(double secs)
{
    int mins = (int) secs / 60;
    int hours = mins / 60;

    secs -= 60 * mins;
    mins -= 60 * hours;
    if (hours)
	fprintf(stderr, "%d:%02d:%05.2f", hours, mins, secs);
    else if (mins)
	fprintf(stderr,      "%d:%05.2f",        mins, secs);
    else
	fprintf(stderr,           "%.3f",              secs);
}

/**/
void
printtime(struct timeval *real, struct timeinfo *ti, char *desc)
{
    char *s;
    double elapsed_time, user_time, system_time;
    int percent;

    if (!desc)
	desc = "";

    set_clktck();
    /* go ahead and compute these, since almost every TIMEFMT will have them */
    elapsed_time = real->tv_sec + real->tv_usec / 1000000.0;
    user_time    = ti->ut / (double) clktck;
    system_time  = ti->st / (double) clktck;
    percent      =  100.0 * (ti->ut + ti->st)
	/ (clktck * real->tv_sec + clktck * real->tv_usec / 1000000.0);

    if (!(s = getsparam("TIMEFMT")))
	s = DEFAULT_TIMEFMT;

    for (; *s; s++)
	if (*s == '%')
	    switch (*++s) {
	    case 'E':
		fprintf(stderr, "%4.2fs", elapsed_time);
		break;
	    case 'U':
		fprintf(stderr, "%4.2fs", user_time);
		break;
	    case 'S':
		fprintf(stderr, "%4.2fs", system_time);
		break;
	    case '*':
		switch (*++s) {
		case 'E':
		    printhhmmss(elapsed_time);
		    break;
		case 'U':
		    printhhmmss(user_time);
		    break;
		case 'S':
		    printhhmmss(system_time);
		    break;
		default:
		    fprintf(stderr, "%%*");
		    s--;
		    break;
		}
		break;
	    case 'P':
		fprintf(stderr, "%d%%", percent);
		break;
	    case 'J':
		fprintf(stderr, "%s", desc);
		break;
	    case '%':
		putc('%', stderr);
		break;
	    case '\0':
		s--;
		break;
	    default:
		fprintf(stderr, "%%%c", *s);
		break;
	} else
	    putc(*s, stderr);
    putc('\n', stderr);
    fflush(stderr);
}

/**/
void
dumptime(Job jn)
{
    Process pn;

    if (!jn->procs)
	return;
    for (pn = jn->procs; pn; pn = pn->next)
	printtime(dtime(&dtimeval, &pn->bgtime, &pn->endtime), &pn->ti, pn->text);
}

/**/
void
shelltime(void)
{
    struct timeinfo ti;
    struct timezone dummy_tz;
    struct tms buf;

    times(&buf);
    ti.ut = buf.tms_utime;
    ti.st = buf.tms_stime;
    gettimeofday(&now, &dummy_tz);
    printtime(dtime(&dtimeval, &shtimer, &now), &ti, "shell");
    ti.ut = buf.tms_cutime;
    ti.st = buf.tms_cstime;
    printtime(dtime(&dtimeval, &shtimer, &now), &ti, "children");
}

/* see if jobs need printing */
 
/**/
void
scanjobs(void)
{
    int i;
 
    for (i = 1; i < MAXJOB; i++)
        if (jobtab[i].stat & STAT_CHANGED)
            printjob(jobtab + i, 0, 1);
}

/* check to see if user has jobs running/stopped */

/**/
void
checkjobs(void)
{
    int i;

    for (i = 1; i < MAXJOB; i++)
	if (i != thisjob && (jobtab[i].stat & STAT_LOCKED) &&
	    !(jobtab[i].stat & STAT_NOPRINT))
	    break;
    if (i < MAXJOB) {
	if (jobtab[i].stat & STAT_STOPPED) {

#ifdef USE_SUSPENDED
	    zerr("you have suspended jobs.", NULL, 0);
#else
	    zerr("you have stopped jobs.", NULL, 0);
#endif

	} else
	    zerr("you have running jobs.", NULL, 0);
	stopmsg = 1;
    }
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.