#ident "@(#)xec.c 1.12 95/04/10 SMI" /* From AT&T Toolchest */ /* * UNIX shell * * S. R. Bourne * Rewritten by David Korn * AT&T Bell Laboratories * */ #include #include "defs.h" #include "jobs.h" #include "sym.h" #include "test.h" #include "builtins.h" #ifdef _sys_timeb_ # ifndef TIC_SEC # define TIC_SEC 60 # endif /* TIC_SEC */ # include #endif /* _sys_timeb_ */ /* These routines are referenced by this module */ #ifdef DEVFD extern void close_pipes(); #endif /* DEVFD */ #ifdef VFORK extern int vfork_check(); extern int vfork_save(); extern void vfork_restore(); #endif /* VFORK */ static int trim_eq(); static char *word_trim(); static char locbuf[TMPSIZ]; /* store last argument here if it fits */ static char pipejob; /* ======== command execution ========*/ sh_exec(argt, flags) union anynode *argt; int flags; { register union anynode *t; register int type = flags; char *sav=stakptr(0); int errorflg = (type&ERRFLG); int execflg = (type&1); int lastpipe = (type&LASTPIPE); int mainloop = (type&PROMPT); unsigned save_states = (st.states&(ERRFLG|MONITOR|LASTPIPE)); #ifdef VFORK int v_fork; #endif /* VFORK */ if(sh.trapnote&SIGSET) sh_exit(SIGFAIL); flags &= ~(LASTPIPE|PROMPT); st.states &= ~(ERRFLG|MONITOR); st.states |= ((save_states&flags)|lastpipe); if((t=argt) && st.execbrk==0 && !is_option(NOEXEC)) { char **com; int argn; char *com0 = NIL; pid_t forkpid; jmp_buf retbuf; jmp_buf *savreturn; int save; int topfd; struct fileblk *savio; int skipexitset = 0; int jmpval; if(mainloop) pipejob = job.pipeflag = 0; if(lastpipe) { /* save state for last element of a pipe */ topfd = sh.topfd; forkpid = job.parent; if(io_ftable[0]) save = io_ftable[0]->flag; savreturn = sh.freturn; sh.freturn = (jmp_buf*)retbuf; save_states |= (st.states&PROMPT); st.states &= ~PROMPT; savio = sh.savio; sh.savio = st.standin; jmpval =SETJMP(retbuf); if(jmpval) goto endpipe; if(!execflg) io_save(0,topfd); io_renumber(sh.inpipe[0],0); } type = t->tre.tretyp; if(!sh.intrap) sh.oldexit=sh.exitval; sh.exitval=0; sh.lastpath = 0; switch(type&COMMSK) { case TCOM: { register struct argnod *argp; register struct namnod* np; struct ionod *io; st.cmdline = t->com.comline; com = arg_build(&argn,&(t->com)); if(t->tre.tretyp&COMSCAN) { argp = t->com.comarg; if(argp && *com && !(argp->argflag&A_RAW)) if(sh.trapnote&SIGSET) sh_exit(SIGFAIL); } if(!(np = t->com.comnamp)&& com[0]) np = nam_search(com[0],sh.fun_tree,0); sh.cmdname = com0 = com[0]; io = t->tre.treio; if(argp = t->com.comset) { if(argn==0 || (!st.subflag && np && is_asbuiltin(np))) { env_setlist(argp, 0); argp = NULL; } } if((io||argn)) { if(argn==0) { /* fake true built-in */ argn=1; np = SYSTRUE; com = &np->namid; } /* set +x doesn't echo */ else if((np!=SYSSET) && is_option(EXECPR)) sh_trace(com,1); if(io) p_flush(); /* check for builtins */ if(np && is_abuiltin(np)) { /* failures on built-ins not fatal */ jmp_buf retbuf; jmp_buf *savreturn = sh.freturn; struct fileblk *saveio=sh.savio; int indx = sh.topfd; int scope=0; int jmpval; int ismon = (st.states&MONITOR); #ifdef notdef if(st.subflag || is_asbuiltin(np))) #endif sh.freturn = (jmp_buf*)retbuf; sh.savio = st.standin; jmpval = SETJMP(retbuf); if(jmpval == 0) { int flag = execflg; /* * XPG4: * Due to the fact BLT_SPC is in BLT_ENV * when "set" becomes special builtin * it would fail for "set -- a b c". */ if(nam_istype(np,(BLT_ENV)) == BLT_ENV) { if(np==SYSLOGIN) flag=1; else if(np==SYSEXEC) flag=1+(com[1]==0); } else if(!is_asbuiltin(np)) st.states |= BUILTIN; if(io) indx = io_redirect(io,flag); if(argp) { nam_scope(argp); scope++; } sh.exitval = (*funptr(np))(argn,com); } sh.savio = saveio; st.states &= ~BUILTIN; st.states |= ismon; sh.freturn = savreturn; if(scope) nam_unscope(); if(io) io_restore(indx); if(jmpval>1) LONGJMP(*sh.freturn,jmpval); else if (jmpval > 0 && is_asbuiltin(np)) LONGJMP(*sh.freturn,5); goto setexit; } /* check for functions */ dofunction: if(np) { int indx; struct slnod *slp; if(np->value.namval.ip==0) { if(!nam_istype(np,N_FUNCTION)) goto dosearch; path_search(com0,0); if(np->value.namval.ip==0) cmd_shcfail(e_found, ENOTFOUND); } /* increase refcnt for unset */ slp = (struct slnod*)np->value.namenv; sh_funstaks(slp->slchild,1); staklink(slp->slptr); indx = io_redirect(io,execflg); sh_funct((union anynode*)(funtree(np)) ,com ,flags|(int)(nam_istype(np,T_FLAG)?EXECPR:0) ,t->com.comset); io_restore(indx); sh_funstaks(slp->slchild,-1); stakdelete(slp->slptr); goto setexit; } if(strchr(com0,'/')==0) { dosearch: if(path_search(com0,1)) { np=nam_search(com0,sh.fun_tree,0); goto dofunction; } } } else if(io==0) { setexit: sh.exitval &= EXITMASK; exitset(); if(sh.trapnote || (sh.exitval && (st.states&ERRFLG))) sh_chktrap(); break; } type = TCOM; } case TFORK: { register pid_t parent; int no_fork; int pipes[2]; io_sync(); no_fork = (execflg && !(type&(FAMP|FPOU)) && !st.trapcom[0] && !st.trapcom[ERRTRAP]); if(no_fork) job.parent=parent=0; else /* FORKLIM is the max period between forks - power of 2 usually. Currently shell tries after 2,4,8,16, and 32 seconds and then quits */ { register unsigned forkcnt=1; if(type&FTMP) { io_linkdoc(st.iotemp); st.linked = 1; } if(type&FCOOP) /* set up pipe for cooperating process */ { if(sh.cpid) sh_fail(e_pexists,NIL); if(sh.cpipe[0]<=0 || sh.cpipe[1] <= 0) { /* first co-process */ io_popen(pipes); sh.cpipe[0] = io_renumber(pipes[0],CINPIPE); sh.cpipe[1] = io_renumber(pipes[1],CINPIPE2); } sh.outpipe = sh.cpipe; io_popen(sh.inpipe=pipes); sh.inpipe[1] = io_renumber(sh.inpipe[1],COTPIPE); if(!(st.states&PROFILE)) st.states |= is_option(MONITOR); } /* async commands allow job control */ if((type&FAMP) && !(type&(FCOMSUB|FPOU)) &&!(st.states&PROFILE)) st.states |= is_option(MONITOR); nam_strval(RANDNOD); #ifdef VFORK if(v_fork=vfork_check(t)) vfork_save(); while((job.parent=parent=(v_fork?vfork():fork())) == -1) #else while((job.parent=parent=fork()) == -1) #endif /* VFORK */ { if((forkcnt *= 2) > FORKLIM) { register const char *msg; switch(errno) { case ENOMEM: msg = e_swap; break; default: case EAGAIN: msg = e_fork; break; } sh_fail(msg,NIL); } if(sh.trapnote&SIGSET) sh_exit(SIGFAIL); alarm(forkcnt); job_wait((pid_t)-1); if(alarm(0)) forkcnt /= 2; } } #ifdef SIGTSTP if(!job.jobcontrol) #endif /* SIGTSTP */ { /* disable foreground job control */ if(!(type&FAMP)) job_nonstop(); # ifdef DEVFD else if(!(type&FINT)) job_nonstop(); # endif /* DEVFD */ } if(parent) /* This is the parent branch of fork * It may or may not wait for the child */ { register int pno; #ifdef VFORK if(v_fork) vfork_restore(); #endif /* VFORK */ forkpid = parent; #ifdef JOBS /* first process defines process gid */ if(!job.pipeflag) { if (st.states&MONITOR) setpgid(parent, 0); job.curpgid = parent; } if(st.states&MONITOR) { if(lastpipe) job.waitall = pipejob; /* * errno==EPERM means that an * earlier processes completed. * Make parent the job group id */ if(setpgid(parent,job.curpgid)<0 && errno==EPERM) setpgid(parent,parent); } #endif /* JOBS */ if(lastpipe && (st.states&MONITOR)) { io_restore(topfd); forkpid = 0; execflg++; } if(type&FPCL) close(sh.inpipe[0]); if(type&FCOOP) sh.cpid = parent; else if(type&FCOMSUB) sh.subpid = parent; pno = job_post(parent); if(lastpipe) pipejob = 0; if(pipejob) job.pipeflag++; if((type&(FAMP|FPOU))==0) { #ifdef DEVFD close_pipes(); #endif /* DEVFD */ job_wait(parent); job.curpgid = 0; /* invalidate tty state */ tty_set(-1); } if(type&FAMP) { sh.bckpid = parent; job.curpgid = 0; if(st.states&(PROFILE|PROMPT)) { /* print job number */ p_setout(ERRIO); #ifdef JOBS p_sub(pno,'\t'); #endif /* JOBS */ p_num(parent,NL); } } if(lastpipe) job.waitall = 0; sh_chktrap(); break; } else /* * this is the FORKED branch (child) of execute */ { register pid_t pid = getpid(); if(st.standout != 1) st.standout = io_renumber(st.standout,1); st.states |= (FORKED|NOLOG); st.states &= ~(BUILTIN|LASTPIPE); st.fn_depth = 0; /* * dot_depth needs to be reset so sh_exit() * would exit subshell properly. */ st.dot_depth = 0; if(no_fork==0) { sh.login_sh = 0; st.states &= ~(RM_TMP|IS_TMP); io_settemp(pid); if(st.linked == 1) { io_swapdoc(st.iotemp); st.linked = 2; } else st.iotemp=0; job.waitsafe = 0; } else if(st.states&RM_TMP) rm_files(io_tmpname); #ifdef ACCT suspacct(); #endif /* ACCT */ /* child should not unlink the tmpfile */ /* Turn off INTR and QUIT if `FINT' */ /* Reset remaining signals to parent */ /* except for those `lost' by trap */ #ifdef VFORK sig_reset(1); #else sig_reset(0); #endif /* VFORK */ #ifdef JOBS if(st.states&MONITOR) { int pgrp; if(job.pipeflag==0) pgrp = pid; else pgrp = job.curpgid; while(setpgid(0,pgrp)<0 && pgrp!=pid) job.curpgid=pgrp=pid; if(!job.jobcontrol&&(type&FINT)) goto closein; # ifdef SIGTSTP if(!(type&(FAMP|FPOU))) tcsetpgrp(JOBTTY,pgrp); signal(SIGTTIN,SIG_DFL); signal(SIGTTOU,SIG_DFL); signal(SIGTSTP,SIG_DFL); # endif /* SIGTSTP */ } else if(type&FINT) #else if(type&FINT) #endif /* JOBS */ { /* default std input for & */ signal(SIGINT,SIG_IGN); signal(SIGQUIT,SIG_IGN); closein: if(!st.ioset) { if(close(0)>=0) io_fopen(e_devnull); } } /* pipe in or out */ if((type&FAMP) && is_option(BGNICE)) nice(4); if(type&FPIN) { io_renumber(sh.inpipe[0],0); if((type&FPOU)==0||(type&FCOOP)) close(sh.inpipe[1]); } if(type&FPOU) { io_renumber(sh.outpipe[1],1); io_pclose(sh.outpipe); } st.states &= ~MONITOR; #ifdef JOBS job.jobcontrol = 0; #endif /* JOBS */ if(type!=TCOM) st.cmdline = t->fork.forkline; io_redirect(t->tre.treio,1); if(type!=TCOM) { /* don't clear job table for out pipes so that jobs can be piped */ if(no_fork==0 && (type&FPOU)==0) { job.waitall=pipejob = 0; job_clear(); } sh_exec(t->fork.forktre,flags|1); } else if(com0!=ENDARGS) { off_option(ERRFLG); sh_freeup(); path_exec(com,t->com.comset); } sh_done(0); } } case TSETIO: { /* * don't create a new process, just * save and restore io-streams */ int indx; st.cmdline = t->fork.forkline; indx = io_redirect(t->fork.forkio,execflg); sh_exec(t->fork.forktre,flags); io_restore(indx); break; } case TPAR: sh_exec(t->par.partre,flags); sh_done(0); case TFIL: { /* * This code sets up a pipe. * All elements of the pipe are started by the parent. * The last element executes in current environment */ register union anynode *tf; int pvo[2]; /* old pipe for multi-pipeline */ int pvn[2]; /* set up pipe */ type = 1; sh.inpipe = pvo; sh.outpipe = pvn; pipejob = 1; do { /* create the pipe */ io_popen(pvn); tf = t->lst.lstlef; /* type==0 on multi-stage pipe */ if(type==0) tf->fork.forktyp |= FPCL|FPIN; /* execute out part of pipe no wait */ type = sh_exec(tf, MONITOR|errorflg); tf = t->lst.lstrit; t = tf->fork.forktre; /* save the pipe stream-ids */ pvo[0] = pvn[0]; /* close out-part of pipe */ close(pvn[1]); /* pipeline all in one process group */ } /* repeat until end of pipeline */ while(!type && !tf->fork.forkio && t->tre.tretyp==TFIL); sh.inpipe = pvn; sh.outpipe = 0; if(type == 0) /* * execute last element of pipeline * in the current process */ sh_exec(tf->fork.forktre,flags|(LASTPIPE)); else /* execution failure, close pipe */ io_pclose(pvn); pipejob = 0; break; } case TLST: { /* a list of commands are executed here */ do { sh_exec(t->lst.lstlef,errorflg); t = t->lst.lstrit; } while(t->tre.tretyp == TLST); sh_exec(t,flags); break; } case TAND: if(type&TTEST) skipexitset++; if(sh_exec(t->lst.lstlef,0)==0) sh_exec(t->lst.lstrit,flags); break; case TORF: if(type&TTEST) skipexitset++; if(sh_exec(t->lst.lstlef,0)!=0) sh_exec(t->lst.lstrit,flags); break; case TFOR: /* for and select */ { register char **args; register int nargs; struct namnod *n; char **arglist; struct dolnod *argsav=NULL; struct comnod *tp; char *nullptr = NULL; int refresh = 1; char *cp; if((tp=t->for_.forlst)==NULL) { args=st.dolv+1; nargs = st.dolc; argsav=arg_use(); } else { args=arg_build(&argn,tp); nargs = argn; } n = env_namset(t->for_.fornam, sh.var_tree,P_FLAG|V_FLAG); st.loopcnt++; while(*args !=ENDARGS && st.execbrk == 0) { if(t->tre.tretyp==TSELECT) { char *val; /* reuse register */ #define c type if(refresh) { p_setout(ERRIO); p_list(nargs,(arglist=args)); refresh = 0; } sh_prompt(1); env_readline(&nullptr,0,R_FLAG); if(fiseof(io_ftable[0])) { sh.exitval = 1; clearerr(io_ftable[0]); break; } if((val=nam_fstrval(REPLYNOD))==NULL) continue; else { if(*(cp=val) == 0) { refresh++; goto checkbrk; } while(c = *cp++) if(c < '0' && c > '9') break; if(c!=0) c = nargs; else c = atoi(val)-1; if(c<0 || c >= nargs) { cp = (char*)e_nullstr; args = &cp; } else args += c; } } #undef c if(nam_istype(n,N_ARRAY)) array_dotset(n,0); nam_putval(n, *args); sh_exec(t->for_.fortre,errorflg); if(t->tre.tretyp == TSELECT) { if((cp=nam_fstrval(REPLYNOD)) && *cp==0) refresh++; args = arglist; } else args++; checkbrk: if(st.breakcnt<0) st.execbrk = (++st.breakcnt !=0); } if(st.breakcnt>0) st.execbrk = (--st.breakcnt !=0); st.loopcnt--; arg_free(argsav,0); break; } case TWH: /* while and until */ { register int i=0; st.loopcnt++; while(st.execbrk==0 && (sh_exec(t->wh.whtre,0)==0)==(type==TWH)) { i = sh_exec(t->wh.dotre,errorflg); if(st.breakcnt<0) st.execbrk = (++st.breakcnt !=0); } if(st.breakcnt>0) st.execbrk = (--st.breakcnt !=0); st.loopcnt--; sh.exitval= i; break; } case TARITH: /* (( expression )) */ { static char *arg[4]= {"((", 0, "))"}; if(!(t->ar.arexpr->argflag&A_RAW)) arg[1] = word_trim(t->ar.arexpr,0); else arg[1] = t->ar.arexpr->argval; st.cmdline = t->ar.arline; if(is_option(EXECPR)) sh_trace(arg,1); sh.exitval = !sh_arith(arg[1]); break; } case TIF: if(sh_exec(t->if_.iftre,0)==0) sh_exec(t->if_.thtre,flags); else if(t->if_.eltre) sh_exec(t->if_.eltre, flags); else sh.exitval=0; /* force zero exit for if-then-fi */ break; case TSW: { char *r = word_trim(t->sw.swarg,0); t= (union anynode*)(t->sw.swlst); while(t) { register struct argnod *rex=(struct argnod*)t->reg.regptr; while(rex) { register char *s; if(rex->argflag&A_MAC) { s = mac_expand(rex->argval); while(*s=='\\' && s[1]==0) s+=2; } else s = rex->argval; type = (rex->argflag&A_RAW); if((type && eq(r,s)) || (!type && (strmatch(r,s) || trim_eq(r,s)))) { do sh_exec(t->reg.regcom,flags); while(t->reg.regflag && (t=(union anynode*)t->reg.regnxt)); t=0; break; } else rex=rex->argnxt.ap; } if(t) t=(union anynode*)t->reg.regnxt; } break; } #ifdef POSIX case TTIME: /* XPG4: Turn on POSIX ifdef to handle '!' */ if(type!=TTIME) { sh_exec(t->par.partre,0); sh.exitval = !sh.exitval; break; } #endif /* POSIX */ { /* time the command */ struct tms before,after; #ifdef _sys_timeb_ time_t at,bt; struct timeb tb,ta; ftime(&tb); #else clock_t at,bt; #endif /* _sys_timeb_ */ bt = times(&before); job.waitall = 1; sh_exec(t->par.partre,0); job.waitall = 0; at = times(&after); #ifdef _sys_timeb_ ftime(&ta); at = TIC_SEC*(ta.time-tb.time); at += ((TIC_SEC*((time_t)(ta.millitm-tb.millitm)))/1000); #else at -= bt; #endif /* _sys_timeb_ */ p_setout(ERRIO); p_str(e_real,'\t'); p_time(at,NL); p_str(e_user,'\t'); at = after.tms_utime - before.tms_utime; at += after.tms_cutime - before.tms_cutime; p_time(at,NL); p_str(e_sys,'\t'); at = after.tms_stime - before.tms_stime; at += after.tms_cstime - before.tms_cstime; p_time(at,NL); break; } case TPROC: { register struct namnod *np; register char *fname = ((struct procnod*)t)->procnam; register struct slnod *slp; if(!isalpha(*fname)) sh_fail(fname,e_ident); np = env_namset(fname,sh.fun_tree,P_FLAG|V_FLAG); /* a function name cannot be a built-in yet */ if(is_abuiltin(np)) { p_setout(ERRIO); p_prp(e_restricted); p_str(":",' '); p_str(fname,0); p_str(is_builtin,NL); sh.exitval++; break; } if(np->value.namval.rp) { slp = (struct slnod*)np->value.namenv; sh_funstaks(slp->slchild,-1); stakdelete(slp->slptr); } else np->value.namval.rp = new_of(struct Ufunction,0); if(t->proc.procstak) { slp = t->proc.procstak; sh_funstaks(slp->slchild,1); staklink(slp->slptr); np->value.namenv = (char*)slp; funtree(np) = (int*)(t->proc.proctre); np->value.namval.rp->hoffset = t->proc.procloc; } else nam_free(np); nam_ontype(np,N_FUNCTION); break; } #ifdef NEWTEST /* new test compound command */ case TTST: { register int n; int nargs; char *left; /* we need the following for execution trace */ static char arg0[]= "[[ !"; static char *arg[6]= {arg0}; static char unop[3]= "-?"; if(type&TTEST) skipexitset++; if (type&TPAREN) { sh_exec(t->lst.lstlef,0); n = !sh.exitval; } else { n = type>>TSHIFT; left = word_trim(&(t->lst.lstlef->arg),0); if(type&TUNARY) { nargs = 3; unop[1] = n; arg[1] = unop; arg[2] = left; n = unop_test(n,left); } else if(type&TBINARY) { nargs = 4; arg[1] = left; arg[2] = (char*)(test_optable+(n&037)-1)->sysnam; arg[3] = word_trim(&(t->lst.lstrit->arg),0); n = test_binop(n,left, word_trim(&(t->lst.lstrit->arg),(n==TEST_PEQ||n==TEST_PNE))); } arg[nargs] = "]]"; arg[nargs+1] = 0; if(type&TNEGATE) arg[0][2] = ' '; else arg[0][2] = 0; sh_trace(arg,1); } sh.exitval = (!n^((type&TNEGATE)!=0)); break; } #endif /* NEWTEST */ } /* set $. */ if(mainloop && com0) { if(sh.lastarg!= locbuf && sh.lastarg) free(sh.lastarg); if(strlen(com[argn-1]) < TMPSIZ) { nam_ontype(L_ARGNOD,N_FREE); sh.lastarg = strcpy(locbuf,com[argn-1]); } else { nam_offtype(L_ARGNOD,~N_FREE); sh.lastarg = sh_heap(com[argn-1]); } } if(lastpipe) { endpipe: if(!execflg) { io_restore(topfd); type = sh.exitval; job_wait(forkpid); sh.exitval = type; } if(io_ftable[0]) io_ftable[0]->flag = save; sh.savio = savio; st.states &= ~LASTPIPE; sh.freturn = savreturn; st.ioset = 0; job.waitall = pipejob = 0; sh_chktrap(); if(jmpval>1) LONGJMP(*sh.freturn,jmpval); } if(!skipexitset) exitset(); } if(sav != stakptr(0)) stakset(sav,0); else if(staktell()) stakseek(0); st.states |= save_states; st.linked = 0; if(sh.trapnote&SIGSET) sh_exit(SIGFAIL|sh.exitval); return(sh.exitval); } /* * test for equality with second argument trimmed * returns 1 if r == trim(s) otherwise 0 */ static trim_eq(r,s) register char *r,*s; { register char c; while(c = *s++) { if(c==ESCAPE) c = *s++; if(c && c != *r++) return(0); } return(*r==0); } /* * print out the command line if set -x is on */ int sh_trace(com,nl) char **com; { if(is_option(EXECPR)) { p_setout(ERRIO); p_str(mac_try(nam_fstrval(PS4NOD)),0); if(com) echo_list(1,com); if(nl) newline(); return(1); } return(0); } static char *word_trim(arg,flag) register struct argnod *arg; { register char *r = arg->argval; if((arg->argflag&A_RAW)) return(r); if(flag==0) return(mac_trim(r,0)); r = mac_expand(r); return(r); }