#ident "@(#)cmd.c 1.8 95/03/21 SMI" /* From AT&T Toolchest */ /* * UNIX shell * * S. R. Bourne * Rewritten by David Korn * AT&T Bell Laboratories * */ #include "defs.h" #include "sym.h" #include "history.h" #include "builtins.h" /* These routines are referenced by this module */ static union anynode *makelist(); static struct argnod *qscan(); static struct ionod *inout(); static void chkword(); static void chkflags(); static void chksym(); static union anynode *sh_cmd(); static union anynode *term(); static union anynode *list(); #ifdef notdef /* #ifdef POSIX */ static union anynode *andlist(); #endif /* POSIX */ static struct regnod *syncase(); static union anynode *item(); static int skipnl(); static void prsym(); int getlineno(); #ifdef NEWTEST # include "test.h" static union anynode *test_expr(); static union anynode *test_and(); static union anynode *test_or(); static union anynode *test_primary(); #endif /* NEWTEST */ static int heredoc; #define getnode(type) ((union anynode*)stakalloc(sizeof(struct type))) /* * ======== command line decoding ======== * * This is the parser for a shell command line */ /* * Make a node which will cause the shell to fork */ union anynode *sh_mkfork(flgs, i) int flgs; union anynode *i; { register union anynode *t; t = getnode(forknod); t->fork.forktyp = flgs|TFORK; t->fork.forktre = i; t->fork.forkio = 0; return(t); } /* * Make a node corresponding to a command list */ static union anynode *makelist(type,i,r) int type; union anynode *i, *r; { register union anynode *t; if(i==0 || r==0) sh_syntax(); else { t = getnode(lstnod); t->lst.lsttyp = type; t->lst.lstlef = i; t->lst.lstrit = r; } return(t); } /* * shell parser */ union anynode *sh_parse(sym,flg) int sym; int flg; { register union anynode *t; st.staklist = 0; st.iopend = st.iotemp = 0; t = sh_cmd(sym,flg); stakseek(0); return(t); } /* * remove temporary files and stacks */ void sh_freeup() { register struct ionod *iop = st.iotemp; while(iop) { unlink(iop->ioname); if(iop->iolink) free(iop->iolink); iop=iop->iolst; } st.iotemp = 0; if(st.staklist) sh_funstaks(st.staklist,-1); st.staklist = 0; } /* * increase reference count for each stack in function list when flag>0 * decrease reference count for each stack in function list when flag<=0 * stack is freed when reference count is zero */ void sh_funstaks(slp,flag) register struct slnod *slp; register int flag; { register struct slnod *slpold; while(slpold=slp) { if(slp->slchild) sh_funstaks(slp->slchild,flag); slp = slp->slnext; if(flag<=0) stakdelete(slpold->slptr); else staklink(slpold->slptr); } } /* * cmd * empty * list * list & [ cmd ] * list [ ; cmd ] */ static union anynode *sh_cmd(sym,flg) register int sym; int flg; { register int flag = FINT|FAMP; register union anynode *i, *e; struct ionod *saviotemp = st.iotemp; /* parser output goes on standard error */ sh.wdset = 0; if(sym==NL) sh.owdval = 0; p_setout(ERRIO); i = list(flg); if(sh.wdval==NL) { if(flg&NLFLG) { sh.wdval=';'; sh_prompt(0); } } else if(i==0 && (flg&MTFLG)==0) sh_syntax(); switch(sh.wdval) { case COOPSYM: /* set up a cooperating process */ flag |= (FPIN|FPOU|FPCL|FCOOP); case '&': if(i) { if(saviotemp!=st.iotemp || heredoc) flag |= FTMP; i = sh_mkfork(flag, i); } else sh_syntax(); case ';': if(e=sh_cmd(sym,flg|MTFLG)) i=makelist(TLST, i, e); else if(i==0) { sh.wdval = ';'; sh_syntax(); } break; case EOFSYM: if(sym==NL) break; default: if(sym) chksym(sym); } return(i); } /* * Section 3.9.3 in POSIX.2 states that the operators * && and || shall have equal precedence and shall be * evaluated from beginning to end * VSC test suite sh_06.sh, test case tp458(). */ #ifdef notdef /* #ifdef POSIX */ /* precedence of && higher than || */ /* * list * andlist * list || andlist */ static union anynode *list(flg) { register union anynode *r = andlist(flg); while(r && sh.wdval==ORFSYM) r = makelist(TORF , r, andlist(NLFLG)); return(r); } /* * andlist * term * and && term */ static union anynode *andlist(flg) { register union anynode *r = term(flg); while(r && sh.wdval==ANDFSYM) r = makelist(TAND, r, term(NLFLG)); return(r); } #else /* * list * term * list && term * list || term */ static union anynode *list(flg) { register union anynode *r; register int b; r = term(flg); while(r && ((b=(sh.wdval==ANDFSYM)) || sh.wdval==ORFSYM)) { r = makelist((b ? TAND : TORF), r, term(NLFLG)); } return(r); } #endif /* POSIX */ /* * term * item * item | term */ static union anynode *term(flg) register int flg; { register union anynode *t; struct ionod *saviotemp = st.iotemp; heredoc = 0; sh.reserv++; if(flg&NLFLG) skipnl(); else sh_lex(); /* check to see if pipeline is to be timed */ #ifdef POSIX if(sh.wdval==TIMSYM || sh.wdval==NOTSYM) #else if(sh.wdval==TIMSYM) #endif /* POSIX */ { t = getnode(parnod); t->par.partyp=TTIME; #ifdef POSIX if(sh.wdval==NOTSYM) t->par.partyp |= COMSCAN; #endif /* POSIX */ t->par.partre = term(0); } else if((t=item(NLFLG|MTFLG)) && sh.wdval=='|') { register union anynode *tt; if(saviotemp!=st.iotemp || heredoc) flg = (FTMP|FPOU); else flg = FPOU; if(tt=term(NLFLG)) t=makelist(TFIL,sh_mkfork(flg,t),sh_mkfork(FPIN|FPCL,tt)); else chksym(0); } return(t); } /* * case statement */ static struct regnod* syncase(esym) register int esym; { sh.wdset |= IN_CASE; /* set to avoid aliasing expressions */ skipnl(); if(sh.wdval==esym) { sh.wdset &= ~IN_CASE; return(0); } else { register struct regnod *r=(struct regnod*) stakalloc(sizeof(struct regnod)); r->regptr=0; r->regflag=0; if(sh.wdval == '(') skipnl(); while(1) { chkflags(A_EXP); sh.wdarg->argnxt.ap=r->regptr; r->regptr=sh.wdarg; if(sh.wdval==')' || sh.wdval=='|' || ( sh_lex()!=')' && sh.wdval!='|' )) sh_syntax(); if(sh.wdval=='|') sh_lex(); else break; } sh.wdset &= ~IN_CASE; r->regcom=sh_cmd(0,NLFLG|MTFLG); if(sh.wdval==ECSYM) r->regnxt=syncase(esym); else if(sh.wdval==ECASYM) { r->regflag++; r->regnxt=syncase(esym); } else { chksym(esym); r->regnxt=0; } return(r); } } /* * item * * ( cmd ) [ < in ] [ > out ] * word word* [ < in ] [ > out ] * if ... then ... else ... fi * for ... while ... do ... done * case ... in ... esac * begin ... end */ static union anynode *item(flag) int flag; { register union anynode *t; register struct ionod *io; int savwdval = sh.owdval; int savline = sh.olineno; if(flag) io=inout((struct ionod*)0,1); else io=0; if(sh.wdval && sh.wdval!=EOFSYM && sh.wdval!=PROCSYM) { sh.olineno = getlineno(0); sh.owdval = sh.wdval; } switch(sh.wdval) { #ifdef NEWTEST /* [[ ... ]] test expression */ case BTSTSYM: sh.wdset |= (IN_TEST|TEST_OP1); t = test_expr(ETSTSYM); t->tre.tretyp &= ~TTEST; sh.wdset &= ~(IN_TEST|TEST_OP1); break; #endif /* NEWTEST */ /* ((...)) arithmetic expression */ case EXPRSYM: t = getnode(arithnod); t->ar.artyp=TARITH; t->ar.arline = getlineno(st.firstline); chkflags(0); t->ar.arexpr = sh.wdarg; sh_lex(); goto done; /* case statement */ case CASYM: { t = getnode(swnod); chkword(); chkflags(0); t->sw.swarg=sh.wdarg; skipnl(); chksym(INSYM|BRSYM); t->sw.swlst=syncase(sh.wdval==INSYM?ESSYM:KTSYM); t->sw.swtyp=TSW; break; } /* if statement */ case IFSYM: { register union anynode *tt; register int w; t = getnode(ifnod); t->if_.iftyp=TIF; t->if_.iftre=sh_cmd(THSYM,NLFLG); t->if_.thtre=sh_cmd(ELSYM|FISYM|EFSYM,NLFLG); w = sh.wdval; t->if_.eltre=(w==ELSYM?sh_cmd(FISYM,NLFLG): (w==EFSYM?(sh.wdval=IFSYM, tt=item(0)):0)); if(w==EFSYM) { if(tt->tre.tretyp!=TSETIO) goto done; t->if_.eltre = tt->fork.forktre; tt->fork.forktre = t; t = tt; goto done; } break; } /* for and select statement */ case FORSYM: case SELSYM: { t = getnode(fornod); t->for_.fortyp=(sh.wdval==FORSYM?TFOR:TSELECT); t->for_.forlst=0; chkword(); t->for_.fornam=(char*) sh.wdarg->argval; while((sh.reserv++, sh_lex()==NL)) sh_prompt(0); if(sh.wdval==INSYM) { chkword(); t->for_.forlst=(struct comnod*) item(0); if(sh.wdval!=NL && sh.wdval!=';') sh_syntax(); if(sh.wdval==NL) sh_prompt(0); skipnl(); } /* 'for i;do cmd' is valid syntax */ else if(sh.wdval==';') { sh.reserv = 1; sh_lex(); } chksym(DOSYM|BRSYM); t->for_.fortre=sh_cmd(sh.wdval==DOSYM?ODSYM:KTSYM,NLFLG); break; } /* This is the code for parsing function definitions */ case PROCSYM: funct_5_2: { Stak_t *savstak; struct slnod *slp; int savstates = st.states; int saveline = st.firstline; register struct fileblk *fd = NULL; struct ionod *saviotemp = st.iotemp; t = getnode(procnod); t->proc.proctyp=TPROC; t->proc.procloc = -1; st.firstline = st.standin->flin; t->proc.procline = st.firstline; flag = (sh.wdval==PROCSYM); if(flag) chkword(); t->proc.procnam= (char*)sh.wdarg->argval; skipnl(); if(is_option(INTFLG) && !is_option(NOLOG) && st.standin->ftype==F_ISFILE) { /* just in case history file not open yet */ if(hist_open()) { fd = io_ftable[hist_ptr->fixfd]; st.states |= FIXFLG; t->proc.procloc = hist_position(hist_ptr->fixind) + fd->ptr - fd->base; } } if(flag) chksym(BRSYM); /* create a new stak frame to compile the command */ savstak = stakcreate(STAK_SMALL); savstak = stakinstall(savstak, 0); slp = (struct slnod*)stakalloc(sizeof(struct slnod)); slp->slchild = 0; slp->slnext = st.staklist; st.staklist = 0; t->proc.procstak = slp; t->proc.proctre = item(0); /* restore the old stack */ slp->slptr = stakinstall(savstak,0); slp->slchild = st.staklist; st.staklist = slp; if(st.iotemp != saviotemp) { st.iotemp = saviotemp; st.states |= RM_TMP; } if(fd && !(savstates&FIXFLG)) { hist_flush(); hist_cancel(); st.states &= ~FIXFLG; } st.firstline = saveline; return(t); } /* while and until */ case WHSYM: case UNSYM: { t = getnode(whnod); t->wh.whtyp=(sh.wdval==WHSYM ? TWH : TUN); t->wh.whtre = sh_cmd(DOSYM,NLFLG); t->wh.dotre = sh_cmd(ODSYM,NLFLG); break; } /* command group with {...} */ case BRSYM: t = sh_cmd(KTSYM,NLFLG); break; case '(': { register union anynode * p; p = getnode(parnod); p->par.partre=sh_cmd(')',NLFLG); p->par.partyp=TPAR; t=sh_mkfork(0,p); break; } default: if(io==0) return(0); /* simple command */ case 0: { register struct argnod *argp; struct argnod **argtail; struct argnod **settail; int keywd=KEYFLG; int argno = 0; int bltin = 0; int key_on = (flag?is_option(KEYFLG):0); t = getnode(comnod); t->com.comio=io; /*initial io chain*/ /* set command line number for error messages */ t->com.comline = getlineno(st.firstline); argtail = &(t->com.comarg); t->com.comset = 0; t->com.comnamp = 0; settail = &(t->com.comset); while(sh.wdval==0) { argp = sh.wdarg; argp->argchn = 0; /* test for keyword argument */ if(sh.wdset&keywd) { chkflags(0); if(bltin&SYSDECLARE) goto cmdarg; *settail = argp; settail = &(argp->argnxt.ap); /* alias substitutions allowed */ sh.wdset |= (KEYFLG|CAN_ALIAS); } else { sh.wdset = 0; /* don't hunt for aliases*/ chkflags(A_SPLIT|A_EXP); cmdarg: if((argp->argflag&A_RAW) == 0) argno = -1; if(argno>=0 && argno++==0) { /* check for builtin command */ if((t->com.comnamp=nam_search(argp->argval,sh.fun_tree,0)) && nam_istype(t->com.comnamp,BLT_DCL)) { bltin = SYSDECLARE; key_on = KEYFLG; } } *argtail = argp; argtail = &(argp->argnxt.ap); sh.wdset = keywd= key_on; } #ifdef DEVFD retry: sh_lex(); if((sh.wdval==IPROC ||sh.wdval==OPROC)) { union anynode *t; int flag = (sh.wdval==OPROC); t = sh_cmd(')',NLFLG); argp = (struct argnod*)stakalloc(sizeof(struct argnod)); *argp->argval = 0; argno = -1; *argtail = argp; argtail = &(argp->argnxt.ap); argp->argchn = (struct argnod*)sh_mkfork(flag?FPIN|FAMP|FPCL:FPOU,t); argp->argflag = (A_EXP|flag); goto retry; } #else sh_lex(); #endif /* DEVFD */ if(argno==1 && !t->com.comset && sh.wdval== '(') { /* SVR2 style function */ sh_lex(); if(sh.wdval == ')') { sh.wdarg = argp; goto funct_5_2; } sh.wdval = '('; } if(flag) { if(io) { while(io->ionxt) io = io->ionxt; io->ionxt = inout((struct ionod*)0,0); } else t->com.comio = io = inout((struct ionod*)0,0); } } *argtail = 0; t->com.comtyp = TCOM; /* expand argument list if possible */ if(argno>0) t->com.comarg = qscan(&t->com,argno); else if(t->com.comarg) t->com.comtyp |= COMSCAN; sh.wdset &= ~CAN_ALIAS; return(t); } } sh.reserv++; sh_lex(); if(io=inout(io,0)) { int type = t->tre.tretyp&COMMSK; t=sh_mkfork(0,t); t->tre.treio=io; t->fork.forkline = getlineno(st.firstline)-1; if(type != TFORK) t->tre.tretyp = TSETIO; } done: sh.owdval = savwdval; sh.olineno = savline; return(t); } /* * skip past newlines but issue prompt if interactive */ static int skipnl() { while((sh.reserv++, sh_lex()==NL)) sh_prompt(0); if(sh.wdval==';') sh_syntax(); return(sh.wdval); } /* * check for and process and i/o redirections * if flag is set then an alias can be in the next word */ static struct ionod *inout(lastio,flag) struct ionod *lastio; { register int iof; register struct ionod *iop; register int c; iof=sh.wdnum; switch(sh.wdval) { case DOCSYM: /* << */ iof |= (IODOC|IORAW); heredoc = FTMP; break; case APPSYM: /* >> */ case '>': if(sh.wdnum==0) iof |= 1; iof |= IOPUT; if(sh.wdval==APPSYM) { iof |= IOAPP; break; } case '<': if((c=io_nextc())=='&') iof |= IOMOV; else if(c=='|' && sh.wdval=='>') iof |= IOCLOB; else if(c=='>') iof |= IORDW; else io_unreadc(c); break; default: return(lastio); } chkword(); iop=(struct ionod*) stakalloc(sizeof(struct ionod)); iop->ioname=sh.wdarg->argval; iop->iolink = 0; iop->iodelim = 0; if(iof&IODOC) { iop->iolst=st.iopend; st.iopend=iop; } else { iop->iolst = 0; chkflags(A_EXP); if(sh.wdarg->argflag&A_RAW) iof |= IORAW; } iop->iofile=iof; if(flag) /* allow alias substitutions and parameter assignments */ sh.wdset |= (KEYFLG|CAN_ALIAS); sh_lex(); iop->ionxt=inout(lastio,flag); return(iop); } /* * get next token and make sure that it is not a keyword or meta-character */ static void chkword() { if(sh_lex()) sh_syntax(); } /* * see if this token is syntactically correct */ static void chksym(sym) register int sym; { register int x = sym&sh.wdval; if(((x&SYMFLG) ? x : sym) != sh.wdval) sh_syntax(); } /* * print the name of a syntactic token */ static void prsym(sym) register int sym; { if(sym&SYMFLG) { register const struct sysnod *sp=tab_reserved; while(sp->sysval && sp->sysval!=sym) sp++; p_str(sp->sysnam,0); } else if(sym==EOFSYM) p_str(e_endoffile,0); else { if(sym&SYMREP) p_char(sym); if(sym==NL) p_str("newline or ;",0); else p_char(sym); } p_char('\''); } /* * print a bad syntax message */ void sh_syntax() { register const char *cp = e_unexpected; register int w = sh.wdval; p_setout(ERRIO); p_prp(e_synbad); if((w==EOFSYM) && sh.owdval) { w = sh.owdval; cp = e_unmatched; } else sh.olineno = st.standin->flin; if(!(st.states&(PROMPT|PROFILE))) { p_str(e_atline,0); p_num(sh.olineno,SP); } p_str(e_colon,'`'); if(w) prsym(w); else { sh_trim(sh.wdarg->argval); p_str(sh.wdarg->argval,'\''); } p_str(cp,NL); exitset(); #ifdef WEXP_E if (is_option(WEXP_E)) sh_exit(WEXP_SYNTAX); else #endif /* WEXP_E */ sh_exit(SYNBAD); } /* * check argument for possible optimizations * in many cases we can skip mac_expand and file name expansion * fexp is set to the union of A_SPLIT and A_EXP when splitting and/or * file expansion are possible. */ #define EXP_MACRO 2 /* macro expansion needed */ #define EXP_TRIM 4 /* quoted characters in string */ #define EXP_FILE 8 /* file expansion characters*/ #define EXP_QUOTE 16 /* string contains " character */ static void chkflags(fexp) { register struct argnod* argp = sh.wdarg; register int c; argp->argflag = 0; { register int flag = 0; char nquote = 0; char *sp=argp->argval; while(c= *sp++) { if(c==ESCAPE) { flag |= EXP_TRIM; sp++; } else if(isexp(c)) { if(c == '$' || c == '`') { flag |= EXP_MACRO; if(c=='`' || *sp==LPAREN) sh.nested_sub++; if(!nquote) { flag |= EXP_FILE; break; } } else if(!nquote) { /* special case of '[' */ if(*sp || c!='[') flag |= EXP_FILE; } } else if(c == '"') { /* toggle the quote count */ nquote = !nquote; flag |= EXP_QUOTE; } } if(!(flag&EXP_FILE)) fexp &= ~A_EXP; /* return if no macro expansion, file expansion or trimming required */ if(flag==0) { argp->argflag |= A_RAW; return; } /* return if macro or command substitution needed */ if(flag&EXP_MACRO) { argp->argflag |= (A_MAC|fexp); if(is_option(NOEXEC)) { int wdset = sh.wdset; int offset = staktell(); char *savst = stakfreeze(0); st.cmdline = getlineno(st.firstline); mac_expand(argp->argval); stakset(savst,offset); sh.wdarg = argp; sh.wdset = wdset; } return; } /* check to see if file expansion is required */ if(fexp&A_EXP) { argp->argflag|= A_EXP; /* return if no quotes otherwise don't optimize */ if(flag&(EXP_QUOTE|EXP_TRIM)) { argp->argflag= A_MAC|A_EXP; return; } return; } argp->argflag |= A_RAW; } /* just get rid of quoting stuff and consider argument as expanded */ { register char *dp,*sp; char nquote = 0; /* set within quoted string */ dp = sp = argp->argval; while(c= *sp++) { if(c != '"') { if(c==ESCAPE) { /* strip escchar's in double quotes */ c = *sp++; if(nquote && !escchar(c) && c!='"') *dp++ = ESCAPE; } *dp++ = c; } else /* toggle quote marker */ nquote = !nquote; } *dp = 0; } } /* * convert argument chain to argument list when no special arguments */ #ifdef VPIX # define EXTRA 2 #else # define EXTRA 1 #endif /* VPIX */ static struct argnod *qscan(ac,argn) struct comnod *ac; int argn; { register char **cp; register struct argnod *ap; register struct dolnod* dp; /* leave space for an extra argument at the front */ dp = (struct dolnod*)stakalloc((unsigned)sizeof(struct dolnod) + EXTRA*sizeof(char*) + argn*sizeof(char*)); cp = dp->dolarg+EXTRA; dp->doluse = argn; ap = ac->comarg; while(ap) { *cp++ = ap->argval; ap = ap->argnxt.ap; } *cp = NULL; return((struct argnod*)dp); } int getlineno(offset) register int offset; { if(st.exec_flag) return(st.cmdline); return(st.standin->flin-offset-((st.peekn&STRIP)==NL)); } #ifdef NEWTEST static union anynode *test_expr(sym) { register union anynode *t = test_or(); if(sh.wdval!=sym) sh_syntax(); return(t); } static union anynode *test_or() { register union anynode *t = test_and(); while(sh.wdval==ORFSYM) t = makelist(TORF|TTEST,t,test_and()); return(t); } static union anynode *test_and() { register union anynode *t = test_primary(); while(sh.wdval==ANDFSYM) t = makelist(TAND|TTEST,t,test_primary()); return(t); } static union anynode *test_primary() { register int num; register struct argnod *arg; register union anynode *t; sh.wdset |= TEST_OP1; skipnl(); num = sh.wdnum; switch(sh.wdval) { case '(': t = test_expr(')'); t = makelist(TTST|TTEST|TPAREN ,t, t); sh_lex(); break; case '!': t = test_primary(); t->tre.tretyp |= TNEGATE; break; case TESTUNOP: chkword(); chkflags(0); t = makelist(TTST|TTEST|TUNARY|(num<') num = TEST_SGT; else sh_syntax(); chkword(); if(num&TEST_PATTERN) { chkflags(A_EXP); if(sh.wdarg->argflag&A_EXP) num &= ~TEST_PATTERN; } else chkflags(0); t = makelist(TTST|TTEST|TBINARY|(num<