diff options
| author | Craig Jennings <c@cjennings.net> | 2025-05-08 18:49:34 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2025-05-08 18:51:59 -0500 |
| commit | 000e00871830cd15de032c80e2b62946cf19445c (patch) | |
| tree | 794a7922750472bbe0e024042d6ba84f411fc3e0 /dotfiles/system/.zsh/modules/Src/signals.c | |
| parent | fe302606931e4bad91c4ed6df81a4403523ba780 (diff) | |
adding missing dotfiles and folders
- profile.d/
- bashrc
- authinfo.gpg
- .zsh/
Diffstat (limited to 'dotfiles/system/.zsh/modules/Src/signals.c')
| -rw-r--r-- | dotfiles/system/.zsh/modules/Src/signals.c | 1479 |
1 files changed, 1479 insertions, 0 deletions
diff --git a/dotfiles/system/.zsh/modules/Src/signals.c b/dotfiles/system/.zsh/modules/Src/signals.c new file mode 100644 index 0000000..20c6fdf --- /dev/null +++ b/dotfiles/system/.zsh/modules/Src/signals.c @@ -0,0 +1,1479 @@ +/* + * 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 + if (WIFEXITED(status) && + pn->pid == jn->gleader && + killpg(pn->pid, 0) == -1) { + jn->gleader = 0; + if (!(jn->stat & STAT_NOSTTY)) { + /* + * This PID was in control of the terminal; + * reclaim terminal now it has exited. + * It's still possible some future forked + * process of this job will become group + * leader, however. + */ + attachtty(mypgrp); + } + } + } + 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 void +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) { + /* + * Do not kill this job's process if it's already dead as its + * pid could have been reused by the system. + * As the PID doesn't exist don't return an error. + */ + if (pn->status == SP_RUNNING || WIFSTOPPED(pn->status)) { + /* + * kill -0 on a job is pointless. We still call kill() for each process + * in case the user cares about it but we ignore its outcome. + */ + 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); +} |
