/*
 * signals.c - signals handling code
 *
 * 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 "signals.pro"
 
/* Array describing the state of each signal: an element contains *
 * 0 for the default action or some ZSIG_* flags ored together.   */

/**/
mod_export int sigtrapped[VSIGCOUNT];

/*
 * Trap programme lists for each signal.
 *
 * If (sigtrapped[sig] & ZSIG_FUNC) is set, this isn't used.
 * The corresponding shell function is used instead.
 *
 * Otherwise, if sigtrapped[sig] is not zero, this is NULL when a signal
 * is to be ignored, and if not NULL contains the programme list to be
 * eval'd.
 */

/**/
mod_export Eprog siglists[VSIGCOUNT];

/* Total count of trapped signals */

/**/
mod_export int nsigtrapped;

/* Running an exit trap? */

/**/
int in_exit_trap;

/*
 * Flag that exit trap has been set in POSIX mode.
 * The setter's expectation is therefore that it is run
 * on programme exit, not function exit.
 */

/**/
static int exit_trap_posix;

/* Variables used by signal queueing */

/**/
mod_export int queueing_enabled, queue_front, queue_rear;
/**/
mod_export int signal_queue[MAX_QUEUE_SIZE];
/**/
mod_export sigset_t signal_mask_queue[MAX_QUEUE_SIZE];
#ifdef DEBUG
/**/
mod_export int queue_in;
#endif

/* Variables used by trap queueing */

/**/
mod_export int trap_queueing_enabled, trap_queue_front, trap_queue_rear;
/**/
mod_export int trap_queue[MAX_QUEUE_SIZE];

/* This is only used on machines that don't understand signal sets.  *
 * On SYSV machines this will represent the signals that are blocked *
 * (held) using sighold.  On machines which can't block signals at   *
 * all, we will simulate this by ignoring them and remembering them  *
 * in this variable.                                                 */
#if !defined(POSIX_SIGNALS) && !defined(BSD_SIGNALS)
static sigset_t blocked_set;
#endif

#ifdef POSIX_SIGNALS
# define signal_jmp_buf       sigjmp_buf
# define signal_setjmp(b)     sigsetjmp((b),1)
# define signal_longjmp(b,n)  siglongjmp((b),(n))
#else
# define signal_jmp_buf       jmp_buf
# define signal_setjmp(b)     setjmp(b)
# define signal_longjmp(b,n)  longjmp((b),(n))
#endif
 
#ifdef NO_SIGNAL_BLOCKING
# define signal_process(sig)  signal_ignore(sig)
# define signal_reset(sig)    install_handler(sig)
#else
# define signal_process(sig)  ;
# define signal_reset(sig)    ;
#endif

/* Install signal handler for given signal.           *
 * If possible, we want to make sure that interrupted *
 * system calls are not restarted.                    */

/**/
mod_export void
install_handler(int sig)
{
#ifdef POSIX_SIGNALS
    struct sigaction act;
 
    act.sa_handler = (SIGNAL_HANDTYPE) zhandler;
    sigemptyset(&act.sa_mask);        /* only block sig while in handler */
    act.sa_flags = 0;
# ifdef SA_INTERRUPT                  /* SunOS 4.x */
    if (interact)
        act.sa_flags |= SA_INTERRUPT; /* make sure system calls are not restarted */
# endif
    sigaction(sig, &act, (struct sigaction *)NULL);
#else
# ifdef BSD_SIGNALS
    struct sigvec vec;
 
    vec.sv_handler = (SIGNAL_HANDTYPE) zhandler;
    vec.sv_mask = sigmask(sig);    /* mask out this signal while in handler    */
#  ifdef SV_INTERRUPT
    vec.sv_flags = SV_INTERRUPT;   /* make sure system calls are not restarted */
#  endif
    sigvec(sig, &vec, (struct sigvec *)NULL);
# else
#  ifdef SYSV_SIGNALS
    /* we want sigset rather than signal because it will   *
     * block sig while in handler.  signal usually doesn't */
    sigset(sig, zhandler);
#  else  /* NO_SIGNAL_BLOCKING (bummer) */
    signal(sig, zhandler);

#  endif /* SYSV_SIGNALS  */
# endif  /* BSD_SIGNALS   */
#endif   /* POSIX_SIGNALS */
}

/* enable ^C interrupts */
 
/**/
mod_export void
intr(void)
{
    if (interact)
        install_handler(SIGINT);
}

/* disable ^C interrupts */
 
#if 0 /**/
void
nointr(void)
{
    if (interact)
        signal_ignore(SIGINT);
}
#endif
 
/* temporarily block ^C interrupts */
 
/**/
mod_export void
holdintr(void)
{
    if (interact)
        signal_block(signal_mask(SIGINT));
}

/* release ^C interrupts */
 
/**/
mod_export void
noholdintr(void)
{
    if (interact)
        signal_unblock(signal_mask(SIGINT));
}
 
/* create a signal mask containing *
 * only the given signal           */
 
/**/
mod_export sigset_t
signal_mask(int sig)
{
    sigset_t set;
 
    sigemptyset(&set);
    if (sig)
        sigaddset(&set, sig);
    return set;
}

/* Block the signals in the given signal *
 * set. Return the old signal set.       */

/**/
#ifndef BSD_SIGNALS

/**/
mod_export sigset_t
signal_block(sigset_t set)
{
    sigset_t oset;
 
#ifdef POSIX_SIGNALS
    sigprocmask(SIG_BLOCK, &set, &oset);

#else
# ifdef SYSV_SIGNALS
    int i;
 
    oset = blocked_set;
    for (i = 1; i <= NSIG; ++i) {
        if (sigismember(&set, i) && !sigismember(&blocked_set, i)) {
            sigaddset(&blocked_set, i);
            sighold(i);
        }
    }
# else  /* NO_SIGNAL_BLOCKING */
/* We will just ignore signals if the system doesn't have *
 * the ability to block them.                             */
    int i;

    oset = blocked_set;
    for (i = 1; i <= NSIG; ++i) {
        if (sigismember(&set, i) && !sigismember(&blocked_set, i)) {
            sigaddset(&blocked_set, i);
            signal_ignore(i);
        }
   }
# endif /* SYSV_SIGNALS */
#endif /* POSIX_SIGNALS */
 
    return oset;
}

/**/
#endif /* BSD_SIGNALS */

/* Unblock the signals in the given signal *
 * set. Return the old signal set.         */

/**/
mod_export sigset_t
signal_unblock(sigset_t set)
{
    sigset_t oset;

#ifdef POSIX_SIGNALS
    sigprocmask(SIG_UNBLOCK, &set, &oset);
#else
# ifdef BSD_SIGNALS
    sigfillset(&oset);
    oset = sigsetmask(oset);
    sigsetmask(oset & ~set);
# else
#  ifdef SYSV_SIGNALS
    int i;
 
    oset = blocked_set;
    for (i = 1; i <= NSIG; ++i) {
        if (sigismember(&set, i) && sigismember(&blocked_set, i)) {
            sigdelset(&blocked_set, i);
            sigrelse(i);
        }
    }
#  else  /* NO_SIGNAL_BLOCKING */
/* On systems that can't block signals, we are just ignoring them.  So *
 * to unblock signals, we just reenable the signal handler for them.   */
    int i;

    oset = blocked_set;
    for (i = 1; i <= NSIG; ++i) {
        if (sigismember(&set, i) && sigismember(&blocked_set, i)) {
            sigdelset(&blocked_set, i);
            install_handler(i);
        }
   }
#  endif /* SYSV_SIGNALS  */
# endif  /* BSD_SIGNALS   */
#endif   /* POSIX_SIGNALS */
 
    return oset;
}

/* set the process signal mask to *
 * be the given signal mask       */

/**/
mod_export sigset_t
signal_setmask(sigset_t set)
{
    sigset_t oset;
 
#ifdef POSIX_SIGNALS
    sigprocmask(SIG_SETMASK, &set, &oset);
#else
# ifdef BSD_SIGNALS
    oset = sigsetmask(set);
# else
#  ifdef SYSV_SIGNALS
    int i;
 
    oset = blocked_set;
    for (i = 1; i <= NSIG; ++i) {
        if (sigismember(&set, i) && !sigismember(&blocked_set, i)) {
            sigaddset(&blocked_set, i);
            sighold(i);
        } else if (!sigismember(&set, i) && sigismember(&blocked_set, i)) {
            sigdelset(&blocked_set, i);
            sigrelse(i);
        }
    }
#  else  /* NO_SIGNAL_BLOCKING */
    int i;

    oset = blocked_set;
    for (i = 1; i < NSIG; ++i) {
        if (sigismember(&set, i) && !sigismember(&blocked_set, i)) {
            sigaddset(&blocked_set, i);
            signal_ignore(i);
        } else if (!sigismember(&set, i) && sigismember(&blocked_set, i)) {
            sigdelset(&blocked_set, i);
            install_handler(i);
        }
    }
#  endif /* SYSV_SIGNALS  */
# endif  /* BSD_SIGNALS   */
#endif   /* POSIX_SIGNALS */
 
    return oset;
}

#if defined(NO_SIGNAL_BLOCKING)
static int suspend_longjmp = 0;
static signal_jmp_buf suspend_jmp_buf;
#endif

/**/
int
signal_suspend(UNUSED(int sig), int wait_cmd)
{
    int ret;

#if defined(POSIX_SIGNALS) || defined(BSD_SIGNALS)
    sigset_t set;
# if defined(POSIX_SIGNALS) && defined(BROKEN_POSIX_SIGSUSPEND)
    sigset_t oset;
# endif

    sigemptyset(&set);

    /* SIGINT from the terminal driver needs to interrupt "wait"
     * and to cause traps to fire, but otherwise should not be
     * handled by the shell until after any foreground job has
     * a chance to decide whether to exit on that signal.
     */
    if (!(wait_cmd || isset(TRAPSASYNC) ||
	  (sigtrapped[SIGINT] & ~ZSIG_IGNORED)))
	sigaddset(&set, SIGINT);
#endif /* POSIX_SIGNALS || BSD_SIGNALS */

#ifdef POSIX_SIGNALS
# ifdef BROKEN_POSIX_SIGSUSPEND
    sigprocmask(SIG_SETMASK, &set, &oset);
    ret = pause();
    sigprocmask(SIG_SETMASK, &oset, NULL);
# else /* not BROKEN_POSIX_SIGSUSPEND */
    ret = sigsuspend(&set);
# endif /* BROKEN_POSIX_SIGSUSPEND */
#else /* not POSIX_SIGNALS */
# ifdef BSD_SIGNALS
    ret = sigpause(set);
# else
#  ifdef SYSV_SIGNALS
    ret = sigpause(sig);

#  else  /* NO_SIGNAL_BLOCKING */
    /* need to use signal_longjmp to make this race-free *
     * between the child_unblock() and pause()           */
    if (signal_setjmp(suspend_jmp_buf) == 0) {
        suspend_longjmp = 1;   /* we want to signal_longjmp after catching signal */
        child_unblock();       /* do we need to do wait_cmd stuff as well?             */
        ret = pause();
    }
    suspend_longjmp = 0;       /* turn off using signal_longjmp since we are past *
                                * the pause() function.                           */
#  endif /* SYSV_SIGNALS  */
# endif  /* BSD_SIGNALS   */
#endif   /* POSIX_SIGNALS */

    return ret;
}

/* last signal we handled: race prone, or what? */
/**/
int last_signal;

/*
 * Wait for any processes that have changed state.
 *
 * The main use for this is in the SIGCHLD handler.  However,
 * we also use it to pick up status changes of jobs when
 * updating jobs.
 */
/**/
void
wait_for_processes(void)
{
    /* keep WAITING until no more child processes to reap */
    for (;;) {
	/* save the errno, since WAIT may change it */
	int old_errno = errno;
	int status;
	Job jn;
	Process pn;
	pid_t pid;
	pid_t *procsubpid = &cmdoutpid;
	int *procsubval = &cmdoutval;
	int cont = 0;
	struct execstack *es = exstack;

	/*
	 * Reap the child process.
	 * If we want usage information, we need to use wait3.
	 */
#if defined(HAVE_WAIT3) || defined(HAVE_WAITPID)
# ifdef WCONTINUED
# define WAITFLAGS (WNOHANG|WUNTRACED|WCONTINUED)
# else
# define WAITFLAGS (WNOHANG|WUNTRACED)
# endif
#endif
#ifdef HAVE_WAIT3
# ifdef HAVE_GETRUSAGE
	struct rusage ru;

	pid = wait3((void *)&status, WAITFLAGS, &ru);
# else
	pid = wait3((void *)&status, WAITFLAGS, NULL);
# endif
#else
# ifdef HAVE_WAITPID
	pid = waitpid(-1, &status, WAITFLAGS);
# else
	pid = wait(&status);
# endif
#endif

	if (!pid)  /* no more children to reap */
	    break;

	/* check if child returned was from process substitution */
	for (;;) {
	    if (pid == *procsubpid) {
		*procsubpid = 0;
		if (WIFSIGNALED(status))
		    *procsubval = (0200 | WTERMSIG(status));
		else
		    *procsubval = WEXITSTATUS(status);
		use_cmdoutval = 1;
		get_usage();
		cont = 1;
		break;
	    }
	    if (!es)
		break;
	    procsubpid = &es->cmdoutpid;
	    procsubval = &es->cmdoutval;
	    es = es->next;
	}
	if (cont)
	    continue;

	/* check for WAIT error */
	if (pid == -1) {
	    if (errno != ECHILD)
		zerr("wait failed: %e", errno);
	    /* WAIT changed errno, so restore the original */
	    errno = old_errno;
	    break;
	}

	/* This is necessary to be sure queueing_enabled > 0 when
	 * we enter printjob() from update_job(), so that we don't
	 * decrement to zero in should_report_time() and improperly
	 * run other handlers in the middle of processing this one */
	queue_signals();

	/*
	 * Find the process and job containing this pid and
	 * update it.
	 */
	if (findproc(pid, &jn, &pn, 0)) {
	    if (((jn->stat & STAT_BUILTIN) ||
		 (list_pipe &&
		  (thisjob == -1 ||
		   (jobtab[thisjob].stat & STAT_BUILTIN)))) &&
		WIFSTOPPED(status) && WSTOPSIG(status) == SIGTSTP) {
		killjb(jn, SIGCONT);
		zwarn("job can't be suspended");
	    } else {
#if defined(HAVE_WAIT3) && defined(HAVE_GETRUSAGE)
		struct timezone dummy_tz;
		gettimeofday(&pn->endtime, &dummy_tz);
#ifdef WIFCONTINUED
		if (WIFCONTINUED(status))
		    pn->status = SP_RUNNING;
		else
#endif
		pn->status = status;
		pn->ti = ru;
#else
		update_process(pn, status);
#endif
	    }
	    update_job(jn);
	} else if (findproc(pid, &jn, &pn, 1)) {
	    pn->status = status;
	    update_job(jn);
	} else {
	    /* If not found, update the shell record of time spent by
	     * children in sub processes anyway:  otherwise, this
	     * will get added on to the next found process that
	     * terminates.
	     */
	    get_usage();
	}
	/*
	 * Accumulate a list of older jobs.  We only do this for
	 * background jobs, which is something in the job table
	 * that's not marked as in the current shell or as shell builtin
	 * and is not equal to the current foreground job.
	 */
	if (jn && !(jn->stat & (STAT_CURSH|STAT_BUILTIN)) &&
	    jn - jobtab != thisjob) {
	    int val = (WIFSIGNALED(status) ?
		   0200 | WTERMSIG(status) :
		   (WIFSTOPPED(status) ?
		    0200 | WEXITSTATUS(status) :
		    WEXITSTATUS(status)));
	    addbgstatus(pid, val);
	}

	unqueue_signals();
    }
}

/* the signal handler */
 
/**/
mod_export RETSIGTYPE
zhandler(int sig)
{
    sigset_t newmask, oldmask;

#if defined(NO_SIGNAL_BLOCKING)
    int do_jump;
    signal_jmp_buf jump_to;
#endif
 
    last_signal = sig;
    signal_process(sig);
 
    sigfillset(&newmask);
    /* Block all signals temporarily           */
    oldmask = signal_block(newmask);
 
#if defined(NO_SIGNAL_BLOCKING)
    /* do we need to longjmp to signal_suspend */
    do_jump = suspend_longjmp;
    /* In case a SIGCHLD somehow arrives       */
    suspend_longjmp = 0;

    /* Traps can cause nested signal_suspend()  */
    if (sig == SIGCHLD) {
        if (do_jump) {
	    /* Copy suspend_jmp_buf                    */
            jump_to = suspend_jmp_buf;
	}
    }
#endif

    /* Are we queueing signals now?      */
    if (queueing_enabled) {
        int temp_rear = ++queue_rear % MAX_QUEUE_SIZE;

	DPUTS(temp_rear == queue_front, "BUG: signal queue full");
	/* Make sure it's not full (extremely unlikely) */
        if (temp_rear != queue_front) {
	    /* ok, not full, so add to queue   */
            queue_rear = temp_rear;
	    /* save signal caught              */
            signal_queue[queue_rear] = sig;
	    /* save current signal mask        */
            signal_mask_queue[queue_rear] = oldmask;
        }
        signal_reset(sig);
        return;
    }
 
    /* Reset signal mask, signal traps ok now */
    signal_setmask(oldmask);
 
    switch (sig) {
    case SIGCHLD:
	wait_for_processes();
        break;
 
    case SIGPIPE:
	if (!handletrap(SIGPIPE)) {
	    if (!interact)
		_exit(SIGPIPE);
	    else if (!isatty(SHTTY)) {
		stopmsg = 1;
		zexit(SIGPIPE, 1);
	    }
	}
	break;

    case SIGHUP:
        if (!handletrap(SIGHUP)) {
            stopmsg = 1;
            zexit(SIGHUP, 1);
        }
        break;
 
    case SIGINT:
        if (!handletrap(SIGINT)) {
	    if ((isset(PRIVILEGED) || isset(RESTRICTED)) &&
		isset(INTERACTIVE) && (noerrexit & NOERREXIT_SIGNAL))
		zexit(SIGINT, 1);
            if (list_pipe || chline || simple_pline) {
                breaks = loops;
                errflag |= ERRFLAG_INT;
		inerrflush();
		check_cursh_sig(SIGINT);
            }
	    lastval = 128 + SIGINT;
        }
        break;

#ifdef SIGWINCH
    case SIGWINCH:
        adjustwinsize(1);  /* check window size and adjust */
	(void) handletrap(SIGWINCH);
        break;
#endif

    case SIGALRM:
        if (!handletrap(SIGALRM)) {
	    int idle = ttyidlegetfn(NULL);
	    int tmout = getiparam("TMOUT");
	    if (idle >= 0 && idle < tmout)
		alarm(tmout - idle);
	    else {
		/*
		 * We want to exit now.
		 * Cancel all errors, including a user interrupt
		 * which is now redundant.
		 */
		errflag = noerrs = 0;
		zwarn("timeout");
		stopmsg = 1;
		zexit(SIGALRM, 1);
	    }
        }
        break;
 
    default:
        (void) handletrap(sig);
        break;
    }   /* end of switch(sig) */
 
    signal_reset(sig);

/* This is used to make signal_suspend() race-free */
#if defined(NO_SIGNAL_BLOCKING)
    if (do_jump)
        signal_longjmp(jump_to, 1);
#endif

} /* handler */

 
/* SIGHUP any jobs left running */
 
/**/
void
killrunjobs(int from_signal)
{
    int i, killed = 0;
 
    if (unset(HUP))
        return;
    for (i = 1; i <= maxjob; i++)
        if ((from_signal || i != thisjob) && (jobtab[i].stat & STAT_LOCKED) &&
            !(jobtab[i].stat & STAT_NOPRINT) &&
            !(jobtab[i].stat & STAT_STOPPED)) {
            if (jobtab[i].gleader != getpid() &&
		killpg(jobtab[i].gleader, SIGHUP) != -1)
                killed++;
        }
    if (killed)
        zwarn("warning: %d jobs SIGHUPed", killed);
}


/* send a signal to a job (simply involves kill if monitoring is on) */
 
/**/
int
killjb(Job jn, int sig)
{
    Process pn;
    int err = 0;

    if (jobbing) {
        if (jn->stat & STAT_SUPERJOB) {
            if (sig == SIGCONT) {
                for (pn = jobtab[jn->other].procs; pn; pn = pn->next)
                    if (killpg(pn->pid, sig) == -1)
			if (kill(pn->pid, sig) == -1 && errno != ESRCH)
			    err = -1;

		/*
		 * Note this does not kill the last process,
		 * which is assumed to be the one controlling the
		 * subjob, i.e. the forked zsh that was originally
		 * list_pipe_pid...
		 */
                for (pn = jn->procs; pn->next; pn = pn->next)
                    if (kill(pn->pid, sig) == -1 && errno != ESRCH)
			err = -1;

		/*
		 * ...we only continue that once the external processes
		 * currently associated with the subjob are finished.
		 */
		if (!jobtab[jn->other].procs && pn)
		    if (kill(pn->pid, sig) == -1 && errno != ESRCH)
			err = -1;

                return err;
            }
            if (killpg(jobtab[jn->other].gleader, sig) == -1 && errno != ESRCH)
		err = -1;

	    if (killpg(jn->gleader, sig) == -1 && errno != ESRCH)
		err = -1;

	    return err;
        }
        else
	    return killpg(jn->gleader, sig);
    }
    for (pn = jn->procs; pn; pn = pn->next)
        if ((err = kill(pn->pid, sig)) == -1 && errno != ESRCH && sig != 0)
            return -1;
    return err;
}

/*
 * List for saving traps.  We don't usually have that many traps
 * at once, so just use a linked list.
 */
struct savetrap {
    int sig, flags, local, posix;
    void *list;
};

static LinkList savetraps;
static int dontsavetrap;

/*
 * Save the current trap by copying it.  This does nothing to
 * the existing value of sigtrapped or siglists.
 */

static void
dosavetrap(int sig, int level)
{
    struct savetrap *st;
    st = (struct savetrap *)zalloc(sizeof(*st));
    st->sig = sig;
    st->local = level;
    st->posix = (sig == SIGEXIT) ? exit_trap_posix : 0;
    if ((st->flags = sigtrapped[sig]) & ZSIG_FUNC) {
	/*
	 * Get the old function: this assumes we haven't added
	 * the new one yet.
	 */
	Shfunc shf, newshf = NULL;
	if ((shf = (Shfunc)gettrapnode(sig, 1))) {
	    /* Copy the node for saving */
	    newshf = (Shfunc) zshcalloc(sizeof(*newshf));
	    newshf->node.nam = ztrdup(shf->node.nam);
	    newshf->node.flags = shf->node.flags;
	    newshf->funcdef = dupeprog(shf->funcdef, 0);
	    if (shf->node.flags & PM_LOADDIR) {
		dircache_set(&newshf->filename, shf->filename);
	    } else {
		newshf->filename = ztrdup(shf->filename);
	    }
	    if (shf->sticky) {
		newshf->sticky = sticky_emulation_dup(shf->sticky, 0);
	    } else
		newshf->sticky = 0;
	    if (shf->node.flags & PM_UNDEFINED)
		newshf->funcdef->shf = newshf;
	}
#ifdef DEBUG
	else dputs("BUG: no function present with function trap flag set.");
#endif
	DPUTS(siglists[sig], "BUG: function signal has eval list, too.");
	st->list = newshf;
    } else if (sigtrapped[sig]) {
	st->list = siglists[sig] ? dupeprog(siglists[sig], 0) : NULL;
    } else {
	DPUTS(siglists[sig], "BUG: siglists not null for untrapped signal");
	st->list = NULL;
    }
    if (!savetraps)
	savetraps = znewlinklist();
    /*
     * Put this at the front of the list
     */
    zinsertlinknode(savetraps, (LinkNode)savetraps, st);
}


/*
 * Set a trap:  note this does not handle manipulation of
 * the function table for TRAPNAL functions.
 *
 * sig is the signal number.
 *
 * l is the list to be eval'd for a trap defined with the "trap"
 * builtin and should be NULL for a function trap.
 *
 * flags includes any additional flags to be or'd into sigtrapped[sig],
 * in particular ZSIG_FUNC; the basic flags will be assigned within
 * settrap.
 */

/**/
mod_export int
settrap(int sig, Eprog l, int flags)
{
    if (sig == -1)
        return 1;
    if (jobbing && (sig == SIGTTOU || sig == SIGTSTP || sig == SIGTTIN)) {
        zerr("can't trap SIG%s in interactive shells", sigs[sig]);
        return 1;
    }

    /*
     * Call unsettrap() unconditionally, to make sure trap is saved
     * if necessary.
     */
    queue_signals();
    unsettrap(sig);

    DPUTS((flags & ZSIG_FUNC) && l,
	  "BUG: trap function has passed eval list, too");
    siglists[sig] = l;
    if (!(flags & ZSIG_FUNC) && empty_eprog(l)) {
	sigtrapped[sig] = ZSIG_IGNORED;
        if (sig && sig <= SIGCOUNT &&
#ifdef SIGWINCH
            sig != SIGWINCH &&
#endif
            sig != SIGCHLD)
            signal_ignore(sig);
    } else {
	nsigtrapped++;
        sigtrapped[sig] = ZSIG_TRAPPED;
        if (sig && sig <= SIGCOUNT &&
#ifdef SIGWINCH
            sig != SIGWINCH &&
#endif
            sig != SIGCHLD)
            install_handler(sig);
    }
    sigtrapped[sig] |= flags;
    /*
     * Note that introducing the locallevel does not affect whether
     * sigtrapped[sig] is zero or not, i.e. a test without a mask
     * works just the same.
     */
    if (sig == SIGEXIT) {
	/* Make POSIX behaviour of EXIT trap sticky */
	exit_trap_posix = isset(POSIXTRAPS);
	/* POSIX exit traps are not local. */
	if (!exit_trap_posix)
	    sigtrapped[sig] |= (locallevel << ZSIG_SHIFT);
    }
    else
	sigtrapped[sig] |= (locallevel << ZSIG_SHIFT);
    unqueue_signals();
    return 0;
}

/**/
void
unsettrap(int sig)
{
    HashNode hn;

    queue_signals();
    hn = removetrap(sig);
    if (hn)
	shfunctab->freenode(hn);
    unqueue_signals();
}

/**/
HashNode
removetrap(int sig)
{
    int trapped;

    if (sig == -1 ||
	(jobbing && (sig == SIGTTOU || sig == SIGTSTP || sig == SIGTTIN)))
	return NULL;

    queue_signals();
    trapped = sigtrapped[sig];
    /*
     * Note that we save the trap here even if there isn't an existing
     * one, to aid in removing this one.  However, if there's
     * already one at the current locallevel we just overwrite it.
     *
     * Note we save EXIT traps based on the *current* setting of
     * POSIXTRAPS --- so if there is POSIX EXIT trap set but
     * we are in native mode it can be saved, replaced by a function
     * trap, and then restored.
     */
    if (!dontsavetrap &&
	(sig == SIGEXIT ? !isset(POSIXTRAPS) : isset(LOCALTRAPS)) &&
	locallevel &&
	(!trapped || locallevel > (sigtrapped[sig] >> ZSIG_SHIFT)))
	dosavetrap(sig, locallevel);

    if (!trapped) {
	unqueue_signals();
        return NULL;
    }
    if (sigtrapped[sig] & ZSIG_TRAPPED)
	nsigtrapped--;
    sigtrapped[sig] = 0;
    if (sig == SIGINT && interact) {
	/* PWS 1995/05/16:  added test for interactive, also noholdintr() *
	 * as subshells ignoring SIGINT have it blocked from delivery     */
        intr();
	noholdintr();
    } else if (sig == SIGHUP)
        install_handler(sig);
    else if (sig == SIGPIPE && interact && !forklevel)
        install_handler(sig);
    else if (sig && sig <= SIGCOUNT &&
#ifdef SIGWINCH
             sig != SIGWINCH &&
#endif
             sig != SIGCHLD)
        signal_default(sig);
    if (sig == SIGEXIT)
	exit_trap_posix = 0;

    /*
     * At this point we free the appropriate structs.  If we don't
     * want that to happen then either the function should already have been
     * removed from shfunctab, or the entry in siglists should have been set
     * to NULL.  This is no longer necessary for saving traps as that
     * copies the structures, so here we are remove the originals.
     * That causes a little inefficiency, but a good deal more reliability.
     */
    if (trapped & ZSIG_FUNC) {
	HashNode node = gettrapnode(sig, 1);

	/*
	 * As in dosavetrap(), don't call removeshfuncnode() because
	 * that calls back into unsettrap();
	 */
	if (node)
	    removehashnode(shfunctab, node->nam);
	unqueue_signals();

	return node;
    } else if (siglists[sig]) {
	freeeprog(siglists[sig]);
	siglists[sig] = NULL;
    }
    unqueue_signals();

    return NULL;
}

/**/
void
starttrapscope(void)
{
    /* No special SIGEXIT behaviour inside another trap. */
    if (intrap)
	return;

    /*
     * SIGEXIT needs to be restored at the current locallevel,
     * so give it the next higher one. dosavetrap() is called
     * automatically where necessary.
     */
    if (sigtrapped[SIGEXIT] && !exit_trap_posix) {
	locallevel++;
	unsettrap(SIGEXIT);
	locallevel--;
    }
}

/*
 * Reset traps after the end of a function: must be called after
 * endparamscope() so that the locallevel has been decremented.
 */

/**/
void
endtrapscope(void)
{
    LinkNode ln;
    struct savetrap *st;
    int exittr = 0;
    void *exitfn = NULL;

    /*
     * Remember the exit trap, but don't run it until
     * after all the other traps have been put back.
     * Don't do this inside another trap.
     */
    if (!intrap &&
	!exit_trap_posix && (exittr = sigtrapped[SIGEXIT])) {
	if (exittr & ZSIG_FUNC) {
	    exitfn = removehashnode(shfunctab, "TRAPEXIT");
	} else {
	    exitfn = siglists[SIGEXIT];
	    siglists[SIGEXIT] = NULL;
	}
	if (sigtrapped[SIGEXIT] & ZSIG_TRAPPED)
	    nsigtrapped--;
	sigtrapped[SIGEXIT] = 0;
    }

    if (savetraps) {
	while ((ln = firstnode(savetraps)) &&
	       (st = (struct savetrap *) ln->dat) &&
	       st->local > locallevel) {
	    int sig = st->sig;

	    remnode(savetraps, ln);

	    if (st->flags && (st->list != NULL)) {
		/* prevent settrap from saving this */
		dontsavetrap++;
		if (st->flags & ZSIG_FUNC)
		    settrap(sig, NULL, ZSIG_FUNC);
		else
			settrap(sig, (Eprog) st->list, 0);
		if (sig == SIGEXIT)
		    exit_trap_posix = st->posix;
		dontsavetrap--;
		/*
		 * counting of nsigtrapped should presumably be handled
		 * in settrap...
		 */
		DPUTS((sigtrapped[sig] ^ st->flags) & ZSIG_TRAPPED,
		      "BUG: settrap didn't restore correct ZSIG_TRAPPED");
		if ((sigtrapped[sig] = st->flags) & ZSIG_FUNC)
		    shfunctab->addnode(shfunctab, ((Shfunc)st->list)->node.nam,
				       (Shfunc) st->list);
	    } else if (sigtrapped[sig]) {
		/*
		 * Don't restore the old state if someone has set a
		 * POSIX-style exit trap --- allow this to propagate.
		 */
		if (sig != SIGEXIT || !exit_trap_posix)
		    unsettrap(sig);
	    }

	    zfree(st, sizeof(*st));
	}
    }

    if (exittr) {
	/*
	 * We already made sure this wasn't set as a POSIX exit trap.
	 * We respect the user's intention when the trap in question
	 * was set.
	 */
	dotrapargs(SIGEXIT, &exittr, exitfn);
	if (exittr & ZSIG_FUNC)
	    shfunctab->freenode((HashNode)exitfn);
	else
	    freeeprog(exitfn);
    }
    DPUTS(!locallevel && savetraps && firstnode(savetraps),
	  "BUG: still saved traps outside all function scope");
}


/*
 * Decide whether a trap needs handling.
 * If so, see if the trap should be run now or queued.
 * Return 1 if the trap has been or will be handled.
 * This only needs to be called in place of dotrap() in the
 * signal handler, since it's only while waiting for children
 * to exit that we queue traps.
 */
/**/
static int
handletrap(int sig)
{
    if (!sigtrapped[sig])
	return 0;

    if (trap_queueing_enabled)
    {
	/* Code borrowed from signal queueing */
	int temp_rear = ++trap_queue_rear % MAX_QUEUE_SIZE;

	DPUTS(temp_rear == trap_queue_front, "BUG: trap queue full");
	/* If queue is not full... */
	if (temp_rear != trap_queue_front) {
	    trap_queue_rear = temp_rear;
	    trap_queue[trap_queue_rear] = sig;
	}
	return 1;
    }

    dotrap(sig);

    if (sig == SIGALRM)
    {
	int tmout;
	/*
	 * Reset the alarm.
	 * It seems slightly more natural to do this when the
	 * trap is run, rather than when it's queued, since
	 * the user doesn't see the latter.
	 */
	if ((tmout = getiparam("TMOUT")))
	    alarm(tmout);
    }

    return 1;
}


/*
 * Queue traps if they shouldn't be run asynchronously, i.e.
 * we're not in the wait builtin and TRAPSASYNC isn't set, when
 * waiting for children to exit.
 *
 * Note that unlike signal queuing this should only be called
 * in single matching pairs and can't be nested.  It is
 * only needed when waiting for a job or process to finish.
 *
 * There is presumably a race setting this up: we shouldn't be running
 * traps between forking a foreground process and this point, either.
 */
/**/
void
queue_traps(int wait_cmd)
{
    if (!isset(TRAPSASYNC) && !wait_cmd) {
	/*
	 * Traps need to be handled synchronously, so
	 * enable queueing.
	 */
	trap_queueing_enabled = 1;
    }
}


/*
 * Disable trap queuing and run the traps.
 */
/**/
void
unqueue_traps(void)
{
    trap_queueing_enabled = 0;
    while (trap_queue_front != trap_queue_rear) {
	trap_queue_front = (trap_queue_front + 1) % MAX_QUEUE_SIZE;
	(void) handletrap(trap_queue[trap_queue_front]);
    }
}


/* Execute a trap function for a given signal, possibly
 * with non-standard sigtrapped & siglists values
 */

/* Are we already executing a trap? */
/**/
int intrap;

/* Is the current trap a function? */

/**/
int trapisfunc;

/*
 * If the current trap is not a function, at what function depth
 * did the trap get called?
 */
/**/
int traplocallevel;

/*
 * sig is the signal number.
 * *sigtr is the value to be taken as the field in sigtrapped (since
 *   that may have changed by this point if we are exiting).
 * sigfn is an Eprog with a non-function eval list, or a Shfunc
 *   with a function trap.  It may be NULL with an ignored signal.
 */

/**/
static void
dotrapargs(int sig, int *sigtr, void *sigfn)
{
    LinkList args;
    char *name, num[4];
    int obreaks = breaks;
    int oretflag = retflag;
    int olastval = lastval;
    int isfunc;
    int traperr, new_trap_state, new_trap_return;

    /* if signal is being ignored or the trap function		      *
     * is NULL, then return					      *
     *								      *
     * Also return if errflag is set.  In fact, the code in the       *
     * function will test for this, but this way we keep status flags *
     * intact without working too hard.  Special cases (e.g. calling  *
     * a trap for SIGINT after the error flag was set) are handled    *
     * by the calling code.  (PWS 1995/06/08).			      *
     *                                                                *
     * This test is now replicated in dotrap().                       */
    if ((*sigtr & ZSIG_IGNORED) || !sigfn || errflag)
        return;

    /*
     * Never execute special (synchronous) traps inside other traps.
     * This can cause unexpected code execution when more than one
     * of these is set.
     *
     * The down side is that it's harder to debug traps.  I don't think
     * that's a big issue.
     */
    if (intrap) {
	switch (sig) {
	case SIGEXIT:
	case SIGDEBUG:
	case SIGZERR:
	    return;
	}
    }

    queue_signals();	/* Any time we manage memory or global state */

    intrap++;
    *sigtr |= ZSIG_IGNORED;

    zcontext_save();
    /* execsave will save the old trap_return and trap_state */
    execsave();
    breaks = retflag = 0;
    traplocallevel = locallevel;
    runhookdef(BEFORETRAPHOOK, NULL);
    if (*sigtr & ZSIG_FUNC) {
	int osc = sfcontext, old_incompfunc = incompfunc;
	HashNode hn = gettrapnode(sig, 0);

	args = znewlinklist();
	/*
	 * In case of multiple names, try to get
	 * a hint of the name in use from the function table.
	 * In special cases, e.g. EXIT traps, the function
	 * has already been removed.  Then it's OK to
	 * use the standard name.
	 */
	if (hn) {
	    name = ztrdup(hn->nam);
	} else {
	    name = (char *) zalloc(5 + strlen(sigs[sig]));
	    sprintf(name, "TRAP%s", sigs[sig]);
	}
	zaddlinknode(args, name);
	sprintf(num, "%d", sig);
	zaddlinknode(args, num);

	trap_return = -1;	/* incremented by doshfunc */
	trap_state = TRAP_STATE_PRIMED;
	trapisfunc = isfunc = 1;

	sfcontext = SFC_SIGNAL;
	incompfunc = 0;
	doshfunc((Shfunc)sigfn, args, 1);	/* manages signal queueing */
	sfcontext = osc;
	incompfunc= old_incompfunc;
	freelinklist(args, (FreeFunc) NULL);
	zsfree(name);
    } else {
	trap_return = -2;	/* not incremented, used at current level */
	trap_state = TRAP_STATE_PRIMED;
	trapisfunc = isfunc = 0;

	execode((Eprog)sigfn, 1, 0, "trap");	/* manages signal queueing */
    }
    runhookdef(AFTERTRAPHOOK, NULL);

    traperr = errflag;

    /* Grab values before they are restored */
    new_trap_state = trap_state;
    new_trap_return = trap_return;

    execrestore();
    zcontext_restore();

    if (new_trap_state == TRAP_STATE_FORCE_RETURN &&
	/* zero return from function isn't special */
	!(isfunc && new_trap_return == 0)) {
	if (isfunc) {
	    breaks = loops;
	    /*
	     * For SIGINT we behave the same as the default behaviour
	     * i.e. we set the error bit indicating an interrupt.
	     * We do this with SIGQUIT, too, even though we don't
	     * handle SIGQUIT by default.  That's to try to make
	     * it behave a bit more like its normal behaviour when
	     * the trap handler has told us that's what it wants.
	     */
	    if (sig == SIGINT || sig == SIGQUIT)
		errflag |= ERRFLAG_INT;
	    else
		errflag |= ERRFLAG_ERROR;
	}
	lastval = new_trap_return;
	/* return triggered */
	retflag = 1;
    } else {
	if (traperr && !EMULATION(EMULATE_SH))
	    lastval = 1;
	else {
	    /*
	     * With no explicit forced return, we keep the
	     * lastval from before the trap ran.
	     */
	    lastval = olastval;
	}
	if (try_tryflag) {
	    if (traperr)
		errflag |= ERRFLAG_ERROR;
	    else
		errflag &= ~ERRFLAG_ERROR;
	}
	breaks += obreaks;
	/* return not triggered: restore old flag */
	retflag = oretflag;
	if (breaks > loops)
	    breaks = loops;
    }

    /*
     * If zle was running while the trap was executed, see if we
     * need to restore the display.
     */
    if (zleactive && resetneeded)
	zleentry(ZLE_CMD_REFRESH);

    if (*sigtr != ZSIG_IGNORED)
	*sigtr &= ~ZSIG_IGNORED;
    intrap--;

    unqueue_signals();
}

/* Standard call to execute a trap for a given signal. */

/**/
void
dotrap(int sig)
{
    void *funcprog;
    int q = queue_signal_level();

    if (sigtrapped[sig] & ZSIG_FUNC) {
	HashNode hn = gettrapnode(sig, 0);
	if (hn)
	    funcprog = hn;
	else {
#ifdef DEBUG
	    dputs("BUG: running function trap which has escaped.");
#endif
	    funcprog = NULL;
	}
    } else
	funcprog = siglists[sig];

    /*
     * Copied from dotrapargs().
     * (In fact, the gain from duplicating this appears to be virtually
     * zero.  Not sure why it's here.)
     */
    if ((sigtrapped[sig] & ZSIG_IGNORED) || !funcprog || errflag)
	return;

    dont_queue_signals();

    if (sig == SIGEXIT)
	++in_exit_trap;

    dotrapargs(sig, sigtrapped+sig, funcprog);

    if (sig == SIGEXIT)
	--in_exit_trap;

    restore_queue_signals(q);
}
