fault.c revision 7c2fbfb345896881c631598ee3852ce9ce33fb07
/***********************************************************************
* *
* 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 <dgk@research.att.com> *
* *
***********************************************************************/
#pragma prototyped
/*
* Fault handling routines
*
* David Korn
* AT&T Labs
*
*/
#include "defs.h"
#include <fcin.h>
#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 <vmalloc.h>
#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_SIGBITS)-1);
if ((tp->sh_number>>SH_SIGBITS) & SH_SIGRUNTIME)
sig = shp->sigruntime[sig-1];
if(sig>n && sig<SH_TRAP)
n = sig;
tp++;
}
shp->sigmax = 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<<SH_SIGBITS)-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 <sig>
*/
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);
}