/*********************************************************************** * * * This software is part of the ast package * * Copyright (c) 1982-2008 AT&T Intellectual Property * * and is licensed under the * * Common Public License, Version 1.0 * * by AT&T Intellectual Property * * * * A copy of the License is available at * * http://www.opensource.org/licenses/cpl1.0.txt * * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * * * * Information and Software Systems Research * * AT&T Research * * Florham Park NJ * * * * David Korn * * * ***********************************************************************/ #pragma prototyped /* * Fault handling routines * * David Korn * AT&T Labs * */ #include "defs.h" #include #include "io.h" #include "history.h" #include "shlex.h" #include "variables.h" #include "jobs.h" #include "path.h" #include "builtins.h" #define abortsig(sig) (sig==SIGABRT || sig==SIGBUS || sig==SIGILL || sig==SIGSEGV) static char indone; #if !_std_malloc # include #endif #if defined(VMFL) && (VMALLOC_VERSION>=20031205L) /* * This exception handler is called after vmalloc() unlocks the region */ static int malloc_done(Vmalloc_t* vm, int type, Void_t* val, Vmdisc_t* dp) { dp->exceptf = 0; sh_exit(SH_EXITSIG); return(0); } #endif /* * Most signals caught or ignored by the shell come here */ void sh_fault(register int sig) { register Shell_t *shp = sh_getinterp(); register int flag=0; register char *trap; register struct checkpt *pp = (struct checkpt*)shp->jmplist; int action=0; /* reset handler */ if(!(sig&SH_TRAP)) signal(sig, sh_fault); sig &= ~SH_TRAP; #ifdef SIGWINCH if(sig==SIGWINCH) { int rows=0, cols=0; int32_t v; astwinsize(2,&rows,&cols); if(v = cols) nv_putval(COLUMNS, (char*)&v, NV_INT32|NV_RDONLY); if(v = rows) nv_putval(LINES, (char*)&v, NV_INT32|NV_RDONLY); shp->winch++; } #endif /* SIGWINCH */ if(shp->savesig) { /* critical region, save and process later */ shp->savesig = sig; return; } trap = shp->st.trapcom[sig]; if(sig==SIGALRM && shp->bltinfun==b_sleep) { if(trap && *trap) { shp->trapnote |= SH_SIGTRAP; shp->sigflag[sig] |= SH_SIGTRAP; } return; } if(shp->subshell && sig!=SIGINT && sig!=SIGQUIT && sig!=SIGWINCH) { shp->exitval = SH_EXITSIG|sig; sh_subfork(); shp->exitval = 0; return; } /* handle ignored signals */ if(trap && *trap==0) return; flag = shp->sigflag[sig]&~SH_SIGOFF; if(!trap) { if(sig==SIGINT && (shp->trapnote&SH_SIGIGNORE)) return; if(flag&SH_SIGIGNORE) return; if(flag&SH_SIGDONE) { void *ptr=0; if((flag&SH_SIGINTERACTIVE) && sh_isstate(SH_INTERACTIVE) && !sh_isstate(SH_FORKED) && ! shp->subshell) { /* check for TERM signal between fork/exec */ if(sig==SIGTERM && job.in_critical) shp->trapnote |= SH_SIGTERM; return; } shp->lastsig = sig; sigrelease(sig); if(pp->mode < SH_JMPFUN) pp->mode = SH_JMPFUN; else pp->mode = SH_JMPEXIT; if(sig==SIGABRT || (abortsig(sig) && (ptr = malloc(1)))) { if(ptr) free(ptr); if(!shp->subshell) sh_done(shp,sig); sh_exit(SH_EXITSIG); } /* mark signal and continue */ shp->trapnote |= SH_SIGSET; if(sig < shp->sigmax) shp->sigflag[sig] |= SH_SIGSET; #if defined(VMFL) && (VMALLOC_VERSION>=20031205L) if(abortsig(sig)) { /* abort inside malloc, process when malloc returns */ /* VMFL defined when using vmalloc() */ Vmdisc_t* dp = vmdisc(Vmregion,0); if(dp) dp->exceptf = malloc_done; } #endif return; } } errno = 0; if(pp->mode==SH_JMPCMD) shp->lastsig = sig; if(trap) { /* * propogate signal to foreground group */ if(sig==SIGHUP && job.curpgid) killpg(job.curpgid,SIGHUP); flag = SH_SIGTRAP; } else { shp->lastsig = sig; flag = SH_SIGSET; #ifdef SIGTSTP if(sig==SIGTSTP) { shp->trapnote |= SH_SIGTSTP; if(pp->mode==SH_JMPCMD && sh_isstate(SH_STOPOK)) { sigrelease(sig); sh_exit(SH_EXITSIG); flag = 0; } } #endif /* SIGTSTP */ } #ifdef ERROR_NOTIFY /* This is obsolete */ if((error_info.flags&ERROR_NOTIFY) && shp->bltinfun) action = (*shp->bltinfun)(-sig,(char**)0,(void*)0); if(action>0) return; #endif if(shp->bltinfun && shp->bltindata.notify) { shp->bltindata.sigset = 1; return; } shp->trapnote |= flag; if(sig < shp->sigmax) shp->sigflag[sig] |= flag; if(pp->mode==SH_JMPCMD && sh_isstate(SH_STOPOK)) { if(action<0) return; sigrelease(sig); sh_exit(SH_EXITSIG); } } /* * initialize signal handling */ void sh_siginit(void *ptr) { Shell_t *shp = (Shell_t*)ptr; register int sig, n=SIGTERM+1; register const struct shtable2 *tp = shtab_signals; sig_begin(); /* find the largest signal number in the table */ #ifdef SIGRTMIN shp->sigruntime[SH_SIGRTMIN] = SIGRTMIN; #endif /* SIGRTMIN */ #ifdef SIGRTMAX shp->sigruntime[SH_SIGRTMAX] = SIGRTMAX; #endif /* SIGRTMAX */ while(*tp->sh_name) { sig = tp->sh_number&((1<sh_number>>SH_SIGBITS) & SH_SIGRUNTIME) sig = shp->sigruntime[sig-1]; if(sig>n && sigsigmax = n++; shp->st.trapcom = (char**)calloc(n,sizeof(char*)); shp->sigflag = (unsigned char*)calloc(n,1); shp->sigmsg = (char**)calloc(n,sizeof(char*)); for(tp=shtab_signals; sig=tp->sh_number; tp++) { n = (sig>>SH_SIGBITS); if((sig &= ((1< shp->sigmax) continue; sig--; if(n&SH_SIGRUNTIME) sig = shp->sigruntime[sig]; if(sig>=0) { shp->sigflag[sig] = n; if(*tp->sh_name) shp->sigmsg[sig] = (char*)tp->sh_value; } } } /* * Turn on trap handler for signal */ void sh_sigtrap(register int sig) { register int flag; void (*fun)(int); sh.st.otrapcom = 0; if(sig==0) sh_sigdone(); else if(!((flag=sh.sigflag[sig])&(SH_SIGFAULT|SH_SIGOFF))) { /* don't set signal if already set or off by parent */ if((fun=signal(sig,sh_fault))==SIG_IGN) { signal(sig,SIG_IGN); flag |= SH_SIGOFF; } else { flag |= SH_SIGFAULT; if(sig==SIGALRM && fun!=SIG_DFL && fun!=sh_fault) signal(sig,fun); } flag &= ~(SH_SIGSET|SH_SIGTRAP); sh.sigflag[sig] = flag; } } /* * set signal handler so sh_done is called for all caught signals */ void sh_sigdone(void) { register int flag, sig = sh.sigmax; sh.sigflag[0] |= SH_SIGFAULT; while(--sig>0) { flag = sh.sigflag[sig]; if((flag&(SH_SIGDONE|SH_SIGIGNORE|SH_SIGINTERACTIVE)) && !(flag&(SH_SIGFAULT|SH_SIGOFF))) sh_sigtrap(sig); } } /* * Restore to default signals * Free the trap strings if mode is non-zero * If mode>1 then ignored traps cause signal to be ignored */ void sh_sigreset(register int mode) { register char *trap; register int flag, sig=sh.st.trapmax; while(sig-- > 0) { if(trap=sh.st.trapcom[sig]) { flag = sh.sigflag[sig]&~(SH_SIGTRAP|SH_SIGSET); if(*trap) { if(mode) free(trap); sh.st.trapcom[sig] = 0; } else if(sig && mode>1) { signal(sig,SIG_IGN); flag &= ~SH_SIGFAULT; flag |= SH_SIGOFF; } sh.sigflag[sig] = flag; } } for(sig=SH_DEBUGTRAP-1;sig>=0;sig--) { if(trap=sh.st.trap[sig]) { if(mode) free(trap); sh.st.trap[sig] = 0; } } sh.st.trapcom[0] = 0; if(mode) sh.st.trapmax = 0; sh.trapnote=0; } /* * free up trap if set and restore signal handler if modified */ void sh_sigclear(register int sig) { register int flag = sh.sigflag[sig]; register char *trap; sh.st.otrapcom=0; if(!(flag&SH_SIGFAULT)) return; flag &= ~(SH_SIGTRAP|SH_SIGSET); if(trap=sh.st.trapcom[sig]) { free(trap); sh.st.trapcom[sig]=0; } sh.sigflag[sig] = flag; } /* * check for traps */ void sh_chktrap(void) { register int sig=sh.st.trapmax; register char *trap; if(!(sh.trapnote&~SH_SIGIGNORE)) sig=0; sh.trapnote &= ~SH_SIGTRAP; /* execute errexit trap first */ if(sh_isstate(SH_ERREXIT) && sh.exitval) { int sav_trapnote = sh.trapnote; sh.trapnote &= ~SH_SIGSET; if(sh.st.trap[SH_ERRTRAP]) { trap = sh.st.trap[SH_ERRTRAP]; sh.st.trap[SH_ERRTRAP] = 0; sh_trap(trap,0); sh.st.trap[SH_ERRTRAP] = trap; } sh.trapnote = sav_trapnote; if(sh_isoption(SH_ERREXIT)) { struct checkpt *pp = (struct checkpt*)sh.jmplist; pp->mode = SH_JMPEXIT; sh_exit(sh.exitval); } } if(sh.sigflag[SIGALRM]&SH_SIGALRM) sh_timetraps(); while(--sig>=0) { if(sh.sigflag[sig]&SH_SIGTRAP) { sh.sigflag[sig] &= ~SH_SIGTRAP; if(trap=sh.st.trapcom[sig]) sh_trap(trap,0); } } } /* * parse and execute the given trap string, stream or tree depending on mode * mode==0 for string, mode==1 for stream, mode==2 for parse tree */ int sh_trap(const char *trap, int mode) { Shell_t *shp = sh_getinterp(); int jmpval, savxit = shp->exitval; int was_history = sh_isstate(SH_HISTORY); int was_verbose = sh_isstate(SH_VERBOSE); int staktop = staktell(); char *savptr = stakfreeze(0); struct checkpt buff; Fcin_t savefc; fcsave(&savefc); sh_offstate(SH_HISTORY); sh_offstate(SH_VERBOSE); shp->intrap++; sh_pushcontext(&buff,SH_JMPTRAP); jmpval = sigsetjmp(buff.buff,0); if(jmpval == 0) { if(mode==2) sh_exec((Shnode_t*)trap,sh_isstate(SH_ERREXIT)); else { Sfio_t *sp; if(mode) sp = (Sfio_t*)trap; else sp = sfopen(NIL(Sfio_t*),trap,"s"); sh_eval(sp,0); } } else if(indone) { if(jmpval==SH_JMPSCRIPT) indone=0; else { if(jmpval==SH_JMPEXIT) savxit = shp->exitval; jmpval=SH_JMPTRAP; } } sh_popcontext(&buff); shp->intrap--; sfsync(shp->outpool); if(!shp->indebug && jmpval!=SH_JMPEXIT && jmpval!=SH_JMPFUN) shp->exitval=savxit; stakset(savptr,staktop); fcrestore(&savefc); if(was_history) sh_onstate(SH_HISTORY); if(was_verbose) sh_onstate(SH_VERBOSE); exitset(); if(jmpval>SH_JMPTRAP) siglongjmp(*shp->jmplist,jmpval); return(shp->exitval); } /* * exit the current scope and jump to an earlier one based on pp->mode */ void sh_exit(register int xno) { Shell_t *shp = &sh; register struct checkpt *pp = (struct checkpt*)shp->jmplist; register int sig=0; register Sfio_t* pool; shp->exitval=xno; if(xno==SH_EXITSIG) shp->exitval |= (sig=shp->lastsig); #ifdef SIGTSTP if(shp->trapnote&SH_SIGTSTP) { /* ^Z detected by the shell */ shp->trapnote = 0; shp->sigflag[SIGTSTP] = 0; if(!shp->subshell && sh_isstate(SH_MONITOR) && !sh_isstate(SH_STOPOK)) return; if(sh_isstate(SH_TIMING)) return; /* Handles ^Z for shell builtins, subshells, and functs */ shp->lastsig = 0; sh_onstate(SH_MONITOR); sh_offstate(SH_STOPOK); shp->trapnote = 0; if(!shp->subshell && (sig=sh_fork(0,NIL(int*)))) { job.curpgid = 0; job.parent = (pid_t)-1; job_wait(sig); job.parent = 0; shp->sigflag[SIGTSTP] = 0; /* wait for child to stop */ shp->exitval = (SH_EXITSIG|SIGTSTP); /* return to prompt mode */ pp->mode = SH_JMPERREXIT; } else { if(shp->subshell) sh_subfork(); /* child process, put to sleep */ sh_offstate(SH_STOPOK); sh_offstate(SH_MONITOR); shp->sigflag[SIGTSTP] = 0; /* stop child job */ killpg(job.curpgid,SIGTSTP); /* child resumes */ job_clear(); shp->forked = 1; shp->exitval = (xno&SH_EXITMASK); return; } } #endif /* SIGTSTP */ /* unlock output pool */ sh_offstate(SH_NOTRACK); if(!(pool=sfpool(NIL(Sfio_t*),shp->outpool,SF_WRITE))) pool = shp->outpool; /* can't happen? */ sfclrlock(pool); #ifdef SIGPIPE if(shp->lastsig==SIGPIPE) sfpurge(pool); #endif /* SIGPIPE */ sfclrlock(sfstdin); if(!pp) sh_done(shp,sig); shp->prefix = 0; #if SHOPT_TYPEDEF shp->mktype = 0; #endif /* SHOPT_TYPEDEF*/ if(pp->mode == SH_JMPSCRIPT && !pp->prev) sh_done(shp,sig); if(pp->mode) siglongjmp(pp->buff,pp->mode); } static void array_notify(Namval_t *np, void *data) { Namarr_t *ap = nv_arrayptr(np); NOT_USED(data); if(ap && ap->fun) (*ap->fun)(np, 0, NV_AFREE); } /* * This is the exit routine for the shell */ void sh_done(void *ptr, register int sig) { Shell_t *shp = (Shell_t*)ptr; register char *t; register int savxit = shp->exitval; shp->trapnote = 0; indone=1; if(sig==0) sig = shp->lastsig; if(shp->userinit) (*shp->userinit)(shp, -1); if(t=shp->st.trapcom[0]) { shp->st.trapcom[0]=0; /*should free but not long */ shp->oldexit = savxit; sh_trap(t,0); savxit = shp->exitval; } else { /* avoid recursive call for set -e */ sh_offstate(SH_ERREXIT); sh_chktrap(); } nv_scan(shp->var_tree,array_notify,(void*)0,NV_ARRAY,NV_ARRAY); sh_freeup(shp); #if SHOPT_ACCT sh_accend(); #endif /* SHOPT_ACCT */ #if SHOPT_VSH || SHOPT_ESH if(sh_isoption(SH_EMACS)||sh_isoption(SH_VI)||sh_isoption(SH_GMACS)) tty_cooked(-1); #endif #ifdef JOBS if((sh_isoption(SH_INTERACTIVE) && shp->login_sh) || (!sh_isoption(SH_INTERACTIVE) && (sig==SIGHUP))) job_walk(sfstderr,job_terminate,SIGHUP,NIL(char**)); #endif /* JOBS */ job_close(shp); if(nv_search("VMTRACE", shp->var_tree,0)) strmatch((char*)0,(char*)0); sfsync((Sfio_t*)sfstdin); sfsync((Sfio_t*)shp->outpool); sfsync((Sfio_t*)sfstdout); if(sig) { /* generate fault termination code */ signal(sig,SIG_DFL); sigrelease(sig); kill(getpid(),sig); pause(); } #if SHOPT_KIA if(sh_isoption(SH_NOEXEC)) kiaclose((Lex_t*)shp->lex_context); #endif /* SHOPT_KIA */ exit(savxit&SH_EXITMASK); }