/*
 * $Id: jobs.c,v 1.28 1995/05/12 01:44:55 coleman Exp coleman $
 *
 * jobs.c - job control
 *
 * This file is part of zsh, the Z shell.
 *
 * Copyright (c) 1992-1995 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 its documentation 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"

extern int sigtrapped[VSIGCOUNT];

/* 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;
	dt->tv_sec -= 1;
    }
    return dt;
}

/* clean up after a $() or `` substitution */

/**/
void
cmdoutdone(int status)
{
    cmdoutpid = 0;
    if (WIFSIGNALED(status)) {
	cmdoutval = (0200 | WTERMSIG(status));
	if (WTERMSIG(status) == SIGINT)
	    (void)kill(getpid(), SIGINT);
	else if (sigtrapped[WTERMSIG(status)])
	    dotrap(WTERMSIG(status));
    } else
	cmdoutval = WEXITSTATUS(status);
}

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

/**/
void
makerunning(Job jn)
{
    struct 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);
}

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

/**/
void
updatestatus(Job jn)
{
    struct process *pn;
    int val = 0, status = 0, job;
    int somestopped = 0, inforeground = 0;
    pid_t pgrp;

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

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

    if (somestopped) {
	if (jn->stty_in_env && !jn->ty) {
	    jn->ty = (struct ttyinfo *) zalloc(sizeof(struct ttyinfo));
	    gettyinfo(jn->ty);
	}
	if (jn->stat & STAT_STOPPED)
	    return;
    } else {                   /* job is done, so remember return value */
	lastval2 = val;
	/* If last process was run in the currrent shell, keep old status */
	if (job == thisjob && !(jn->stat & STAT_CURSH))
	    lastval = val;
    }

    pgrp = gettygrp();           /* get process group of tty      */

    /* is this job in the foreground */
    if (jn->gleader == pgrp || (pgrp > 1 && kill(-pgrp, 0) == -1))
	inforeground = 1;

    if (inforeground && !ttyfrozen && !val && !jn->stty_in_env)
	gettyinfo(&shttyinfo);
    adjustwinsize();             /* check window size and adjust if necessary */
    if (somestopped && jn->stat & STAT_SUPERJOB)
	return;
    jn->stat |= (somestopped) ? STAT_CHANGED | STAT_STOPPED :
	STAT_CHANGED | STAT_DONE;
    if ((jn->stat & (STAT_DONE | STAT_STOPPED)) == STAT_STOPPED) {
	prevjob = curjob;
	curjob = job;
    }
    if ((isset(NOTIFY) || job == thisjob) && (jn->stat & STAT_LOCKED)) {
	printjob(jn, !!isset(LONGLISTJOBS));
	if (zleactive)
	    refresh();
    }
    if (sigtrapped[SIGCHLD] && job != thisjob)
	dotrap(SIGCHLD);

    /* WHY DO WE USE THE RETURN STATUS OF PROCESS GROUP LEADER HERE? */
    /* If the foreground job got a signal, pretend we got it, too.   */
    if (inforeground && WIFSIGNALED(status)) {
	if (sigtrapped[WTERMSIG(status)]) {
	    dotrap(WTERMSIG(status));
	} else if (WTERMSIG(status) == SIGINT ||
		   WTERMSIG(status) == SIGQUIT) {
	    breaks = loops;
	    errflag = 1;
	}
    }
}

/* find process and job associated with pid */

/**/
void
findproc(pid_t pid, Job *jptr, struct process **pptr)
{
    struct process *pn;
    int jn;

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


/* lng = 0 means jobs    *
 * lng = 1 means jobs -l *
 * lng = 2 means jobs -p */

/**/
void
printjob(Job jn, int lng)
{
    int job = jn - jobtab, len = 9, sig, sflag = 0, llen;
    int conted = 0, lineleng = columns, skip = 0, doputnl = 0;
    struct process *pn;

    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;
		else if (sig == SIGINT)
		    errflag = 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;
	struct process *qn;

	trashzle();
	if (doputnl)
	    putc('\n', stderr);
	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(stderr, "[%ld]  %c ",
			    (long)(jn - jobtab),
			    (job == curjob) ? '+'
			    : (job == prevjob) ? '-' : ' ');
		else
		    fprintf(stderr, (job > 9) ? "        " : "       ");
	    else
		fprintf(stderr, "zsh: ");
	    if (lng)
		if (lng == 1)
		    fprintf(stderr, "%ld ", (long) pn->pid);
		else {
		    pid_t x = jn->gleader;

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

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

    if (interact && job == thisjob && strcmp(jn->pwd, pwd)) {
	printf("(pwd now: ");
	printdir(pwd);
	printf(")\n");
	fflush(stdout);
    }
/* 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
deletejob(Job jn)
{
    struct process *pn, *nx;
    char *s;

    for (pn = jn->procs; pn; pn = nx) {
	nx = pn->next;
	zfree(pn, sizeof(struct process));
    }
    zsfree(jn->pwd);
    if (jn->filelist) {
	while ((s = (char *)getnode(jn->filelist))) {
	    unlink(s);
	    zsfree(s);
	}
	zfree(jn->filelist, sizeof(struct lklist));
    }
    if (jn->ty)
	zfree(jn->ty, sizeof(struct ttyinfo));

    *jn = zero;
}

/* set the previous job to something reasonable */

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

    for (t0 = MAXJOB - 1; t0; t0--)
	if ((jobtab[t0].stat & STAT_INUSE) && (jobtab[t0].stat & STAT_STOPPED) &&
	    t0 != curjob && t0 != thisjob)
	    break;
    if (!t0)
	for (t0 = MAXJOB - 1; t0; t0--)
	    if ((jobtab[t0].stat & STAT_INUSE) && t0 != curjob && t0 != thisjob)
		break;
    prevjob = (t0) ? t0 : -1;
}

/* initialize a job table entry */

/**/
void
initjob(void)
{
    jobtab[thisjob].pwd = ztrdup(pwd);
    jobtab[thisjob].stat = STAT_INUSE;
    jobtab[thisjob].gleader = 0;
}

/* add a process to the current job */

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

    if (!jobtab[thisjob].gleader)
	jobtab[thisjob].gleader = pid;
    process = (struct process *)zcalloc(sizeof *process);
    process->pid = pid;
    if (text)
	strcpy(process->text, text);
    else
	*process->text = '\0';
    process->next = NULL;
    process->status = SP_RUNNING;
    gettimeofday(&process->bgtime, &dummy_tz);
    if (jobtab[thisjob].procs) {  /* attach process to end of list */
	struct process *n;

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


/* determine if it's all right to exec a command without forking * 
 * in last component of subshells; it's not ok if we have files  * 
 * to delete.                                                    */

/**/
int
execok(void)
{
    Job jn;

    for (jn = jobtab + 1; jn != jobtab + MAXJOB; jn++)
	if (jn->stat && jn->filelist)
	    return 0;
    return 1;

}

/* 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));
	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));
		    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 t0;

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

	jobtab[t0] = zero;
    }
}

/* get a free entry in the job table to use */

/**/
int
getfreejob(void)
{
    int t0;

    for (t0 = 1; t0 != MAXJOB; t0++)
	if (!jobtab[t0].stat) {
	    jobtab[t0].stat |= STAT_INUSE;
	    return t0;
	}
    zerr("job table full or recursion limit exceeded", NULL, 0);
    return -1;
}

/* print pids for & */

/**/
void
spawnjob(void)
{
    struct process *pn;

    if (!subsh) {   /* if we are not in a subshell */
	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;
}


/* 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)
{
    static long clktck = 0;

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

    if (reporttime < 0)
	return 0;

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

    /* fetch clock ticks per second only the first time used */
    if (!clktck)
#ifdef _SC_CLK_TCK
       clktck = sysconf(_SC_CLK_TCK);
#else
# ifdef CLK_TCK
       clktck = CLK_TCK;
# else
#  ifdef HZ
       clktck = HZ;
#  else
       clktck = 60;
#  endif
# endif
#endif

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

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

    if (!clktck)       /* fetch clock ticks per second only the first time */
#ifdef _SC_CLK_TCK
       clktck = sysconf(_SC_CLK_TCK);
#else
# ifdef CLK_TCK
       clktck = CLK_TCK;
# else
#  ifdef HZ
       clktck = HZ;
#  else
       clktck = 60;
#  endif
# endif
#endif

    if (!desc)
	desc = "";

    /* go ahead and compute these, since almost every TIMEFMT will have them */
    elapsed_time = real->tv_sec + real->tv_usec / 1000000.0;
    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 (percent > 100)
        percent = 100;   /* just to make it look right */

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

/**/
void
dumptime(Job jn)
{
    struct 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 t0;
 
    for (t0 = 1; t0 != MAXJOB; t0++)
        if (jobtab[t0].stat & STAT_CHANGED)
            printjob(jobtab + t0, 0);
}

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

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

    for (t0 = 1; t0 != MAXJOB; t0++)
	if (t0 != thisjob && (jobtab[t0].stat & STAT_LOCKED) &&
	    !(jobtab[t0].stat & STAT_NOPRINT))
	    break;
    if (t0 != MAXJOB) {
	if (jobtab[t0].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;
    }
}

