/* * 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; /* 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]; /* 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; } /* * 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); 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) addbgstatus(pid, (int)lastval2); } } /* 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 < 0) zexit(SIGINT, 1); if (list_pipe || chline || simple_pline) { breaks = loops; errflag |= ERRFLAG_INT; inerrflush(); check_cursh_sig(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; for (pn = jn->procs; pn->next; pn = pn->next) if (kill(pn->pid, sig) == -1 && errno != ESRCH) err = -1; 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; 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; 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); 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); } /* * 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. */ sigtrapped[sig] |= (locallevel << ZSIG_SHIFT) | flags; 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. */ 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); /* * 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] && !isset(POSIXTRAPS)) { 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 && !isset(POSIXTRAPS) && (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); 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]) unsettrap(sig); zfree(st, sizeof(*st)); } } if (exittr) { if (!isset(POSIXTRAPS)) 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; } } 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); 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"); } 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--; } /* Standard call to execute a trap for a given signal. */ /**/ void dotrap(int sig) { void *funcprog; 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; dotrapargs(sig, sigtrapped+sig, funcprog); }