/*
 * $Id: signals.c,v 1.29 1995/05/04 06:01:15 coleman Exp coleman $
 *
 * signals.c - signals handling code
 *
 * 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"
 
/* signals that are trapped = 1, signals ignored = 2 */
int sigtrapped[VSIGCOUNT];
 
/* trap functions for each signal */
List sigfuncs[VSIGCOUNT];
 
/* 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

static int from_sig = 0;

#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.                    */

/**/
void
install_handler(int sig)
{
#ifdef POSIX_SIGNALS
    struct sigaction act;
 
    act.sa_handler = (SIGNAL_HANDTYPE) handler;
    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) handler;
    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, handler);
#  else  /* NO_SIGNAL_BLOCKING (bummer) */
    signal(sig, handler);

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

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

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

/* release ^C interrupts */
 
/**/
void
noholdintr(void)
{
    if (interact)
        signal_unblock(signal_mask(SIGINT));
}
 
/* create a signal mask containing *
 * only the given signal           */
 
/**/
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.       */
 
/**/
sigset_t
signal_block(sigset_t set)
{
    sigset_t oset;
 
#ifdef POSIX_SIGNALS
    sigprocmask(SIG_BLOCK, &set, &oset);
#else
# ifdef BSD_SIGNALS
    oset = sigblock(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  /* 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  /* BSD_SIGNALS   */
#endif   /* POSIX_SIGNALS */
 
    return oset;
}

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

/**/
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       */

/**/
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(int sig, int sig2)
{
    int ret;
 
#ifdef POSIX_SIGNALS
    sigset_t set;

    sigfillset(&set);
    sigdelset(&set, sig);
    sigdelset(&set, SIGHUP);  /* still don't know why we add this? */
    if (sig2)
        sigdelset(&set, sig2);
    ret = sigsuspend(&set);
#else
# ifdef BSD_SIGNALS
    sigset_t set;

    sigfillset(&set);
    sigdelset(&set, sig);
    if (sig2)
      sigdelset(&set, sig2);
    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 unblock sig2 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;
}


/* Use a circular queue to save signals caught during    *
 * critical sections of code.  You call queue_signals to *
 * start queueing, and unqueue_signals to process the    *
 * queue and stop queueing.  Since the kernel doesn't    *
 * queue signals, it is probably overkill for zsh to do  *
 * this, but it shouldn't hurt anything to do it anyway. */

/* Right now I'm queueing all signals, but maybe we only *
 * need to queue SIGCHLD.  Anybody know?                 */


#define MAX_QUEUE_SIZE 16

static int queueing_enabled;
static sigset_t signal_mask_queue[MAX_QUEUE_SIZE];
static int signal_queue[MAX_QUEUE_SIZE];
static int queue_front = 0;
static int queue_rear = 0;

/* Start queueing up any received signals rather *
 * than handling them.                           */

/**/
void
queue_signals(void)
{
    queueing_enabled = 1;        /* signals are now being queued */
}

/* Process all queued signals */

/**/
void
unqueue_signals(void)
{
    queueing_enabled = 0;                               /* turn off queueing      */

    while (queue_front != queue_rear) {                 /* while signals in queue */
        queue_front = ++queue_front % MAX_QUEUE_SIZE;
        signal_setmask(signal_mask_queue[queue_front]); /* reset signal mask      */
        handler(signal_queue[queue_front]);             /* handle queued signal   */
    }
}
 
/* What flavor of waitpid/wait3/wait shall we use */
 
#ifdef HAVE_WAITPID
# define  WAIT(pid, statusp, options) waitpid(pid, statusp, options)
#else
# ifdef HAVE_WAIT3
#  define WAIT(pid, statusp, options) wait3(statusp, options, NULL)
# else
#  define WAIT(pid, statusp, options) wait(statusp)
# endif
#endif

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

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

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

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

        if (temp_rear != queue_front) { /* Make sure it's not full (extremely unlikely) */
            queue_rear = temp_rear;                  /* ok, not full, so add to queue   */
            signal_queue[queue_rear] = sig;          /* save signal caught              */
            signal_mask_queue[queue_rear] = oldmask; /* save current signal mask        */
        }
        signal_reset(sig);
        return;
    }
 
    signal_setmask(oldmask);          /* Reset signal mask, signal traps ok now */
 
    switch (sig) {
    case SIGCHLD:

        /* keep WAITING until no more child processes to reap */
        while (1) {
            int old_errno = errno;    /* save the errno, since WAIT may change it */
            pid_t pid;
            int status;
            Job jn;
            struct process *pn;
            long childs, childu;

            pid = WAIT(-1, &status, WNOHANG|WUNTRACED);  /* reap the child process */

            childs = shtms.tms_cstime;
            childu = shtms.tms_cutime;
            times(&shtms);
            if (pid == -1) {
                if (errno != ECHILD)
                    zerr("wait failed: %e", NULL, errno);
                errno = old_errno;
                break;
            }
            errno = old_errno;        /* restore the original errno                   */
            if (!pid)                 /* no more children to reap                     */
                break;
            if (pid == cmdoutpid) {   /* child returned was from process substitution */
                cmdoutdone(status);   /* so do some cleaning up                       */
                continue;
            }
            findproc(pid, &jn, &pn);  /* find the process and job containing this pid */
            if (jn) {
		struct timezone dummy_tz;
 
                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        */
                updatestatus(jn);                       /* update job table entry            */
            }
        }
        break;
 
    case SIGHUP:
        if (sigtrapped[SIGHUP])
            dotrap(SIGHUP);
        else {
            stopmsg = from_sig = 1;
            zexit(SIGHUP);
        }
        break;
 
    case SIGINT:
        if (sigtrapped[SIGINT])
            dotrap(SIGINT);
        else {
            extern int list_pipe, simple_pline;
 
            if (list_pipe || chline || simple_pline) {
                breaks = loops;
                errflag = 1;
            }
        }
        break;

    case SIGWINCH:
        adjustwinsize();  /* check window size and adjust */
        break;

    case SIGALRM:
        if (sigtrapped[SIGALRM]) {
            dotrap(SIGALRM);
            if (tmout)
                alarm(tmout);           /* reset the alarm */
        } else {
            zerr("timeout", NULL, 0);
            stopmsg = from_sig = 1;
            zexit(SIGALRM);
        }
        break;
 
    default:
        dotrap(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(void)
{
    int t0, killed = 0;
 
    if (isset(NOHUP))
        return;
    for (t0 = 1; t0 != MAXJOB; t0++)
        if ((from_sig || t0 != thisjob) && (jobtab[t0].stat & STAT_LOCKED) &&
            !(jobtab[t0].stat & STAT_NOPRINT) &&
            !(jobtab[t0].stat & STAT_STOPPED)) {
            if (killpg(jobtab[t0].gleader, SIGHUP) != -1)
                killed++;
        }
    if (killed)
        zerr("warning: %d jobs SIGHUPed", NULL, killed);
}


/* send a signal to a job (simply involves kill if monitoring is on) */
 
/**/
int
killjb(Job jn, int sig)
{
    struct 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)
                    kill(pn->pid, sig);
 
                for (pn = jn->procs; pn->next; pn = pn->next)
                    err = kill(pn->pid, sig);
 
                return err;
            }
 
            killpg(jobtab[jn->other].gleader, sig);
            return killpg(jn->gleader, sig);
        }
        else
            return (killpg(jn->gleader, sig));
    }
    for (pn = jn->procs; pn; pn = pn->next)
        if ((err = kill(pn->pid, sig)) == -1 && errno != ESRCH)
            return -1;
    return err;
}

/**/
void
settrap(int t0, List l)
{
    Cmd c;
 
    if (l) {
        c = l->left->left->left;
        if (c->type == SIMPLE && empty(c->args) && empty(c->redir)
            && empty(c->vars) && !c->flags)
            l = NULL;
    }
    if (t0 == -1)
        return;
    if (jobbing && (t0 == SIGTTOU || t0 == SIGTSTP || t0 == SIGTTIN
                    || t0 == SIGPIPE)) {
        zerr("can't trap SIG%s in interactive shells", sigs[t0], 0);
        return;
    }
    if (sigfuncs[t0])
        freestruct(sigfuncs[t0]);
    if (!l) {
        sigtrapped[t0] = 2;
        sigfuncs[t0] = NULL;
        if (t0 && t0 <= SIGCOUNT &&
#ifdef SIGWINCH
            t0 != SIGWINCH &&
#endif
            t0 != SIGCHLD)
            signal_ignore(t0);
    } else {
        if (t0 && t0 <= SIGCOUNT &&
#ifdef SIGWINCH
            t0 != SIGWINCH &&
#endif
            t0 != SIGCHLD)
            install_handler(t0);
        sigtrapped[t0] = 1;
        permalloc();
        sigfuncs[t0] = (List) dupstruct(l);
        heapalloc();
    }
}

/**/
void
unsettrap(int t0)
{
    if (t0 == -1)
        return;
    if (jobbing && (t0 == SIGTTOU || t0 == SIGTSTP || t0 == SIGTTIN
                    || t0 == SIGPIPE)) {
        return;
    }
    sigtrapped[t0] = 0;
    if (t0 == SIGINT)
        intr();
    else if (t0 == SIGHUP)
        install_handler(t0);
    else if (t0 && t0 <= SIGCOUNT &&
#ifdef SIGWINCH
             t0 != SIGWINCH &&
#endif
             t0 != SIGCHLD)
        signal_default(t0);
    if (sigfuncs[t0]) {
        freestruct(sigfuncs[t0]);
        sigfuncs[t0] = NULL;
    }
}

/**/
void
dotrap(int sig)
{
    int sav, savval;
 
    sav = sigtrapped[sig];
    savval = lastval;
    if (sav == 2)          /* if signal is being ignored, return */
        return;
    sigtrapped[sig] = 2;
    if (sigfuncs[sig]) {
        Lklist args;
        char *name, num[4];
 
        lexsave();
        permalloc();
        args = newlist();
        name = (char *)zalloc(5 + strlen(sigs[sig]));
        sprintf(name, "TRAP%s", sigs[sig]);
        addnode(args, name);
        sprintf(num, "%d", sig);
        addnode(args, num);
        trapreturn = -1;
        doshfunc(sigfuncs[sig], args, 0, 1);
        lexrestore();
        freetable(args, (FFunc) NULL);
        zsfree(name);
        if (trapreturn > 0) {
            breaks = loops;
            errflag = 1;
        } else
            trapreturn = 0;
    }
    if (sigtrapped[sig])
        sigtrapped[sig] = sav;
    lastval = savval;
}
 

