/* * PWB/UNIX shell * copyright 1973 Bell Telephone Laboratries inc. * 2.44 of 5/26/77 */ #include "errnos.h" #include "stat.h" #define NOFILE 15 /* max open files per process */ static char SCCSID[] "@(#)sh.c 2.44"; #define INTR 2 #define QUIT 3 #define SIGPIPE 13 #define LINSIZ 1000 #define ARGSIZ 50 #define TRESIZ 100 #define QUOTE 0200 #define FAND 1 #define FCAT 2 #define FPIN 4 #define FPOU 8 #define FPAR 16 #define FINT 32 #define FPRS 64 #define TCOM 1 #define TPAR 2 #define TFIL 3 #define TLST 4 #define TOR 5 #define TAND 6 #define DTYP 0 #define DFLG 1 #define DLEF 2 #define DRIT 3 #define DSPR 4 #define DCOM 5 #define N 'n'-'a' #define P 'p'-'a' #define R 'r'-'a' #define S 's'-'a' #define T 't'-'a' #define W 'w'-'a' #define Z 'z'-'a' #define DOLREPL 1 #define DOLREPQ 2 extern errno; /* BSIZ = buffer for tty, BSIZFIL = size for file, 1 = size for pipe */ #define BSIZ 64 #define BSIZFIL 512 struct { long start; /* absolute file addr of char in buf[0] */ long linloc; /* loc of begin of line for while processing */ int gotten; /* number of bytes read in last actual read */ int nleft; char *nextp; } b; char bstate; /* primary state var for control of buffering */ /* 0 ==> BNOW right; real I/O ptr set at BEND (internal I/O) */ /* 1 ==> real ptr has been set from BNOW (pre-exec) */ /* 2 ==> post-exec - internals must be reset */ /* pre-exec: 0->1; post-exec: 1->2; int cmd: 2->0 */ /* pump begin: 1->0 */ char b1char; /* input buffer: 1 at a time */ char *bbuf &b1char; /* addr of actual buffer */ #define BNOW (b.start + (b.gotten - b.nleft)) #define BEND (b.start + b.gotten) int bnread BSIZ; /* must change to 1 if found to be in pipe */ #define ACNAME "/etc/sha" char *dolp; int idolp; int oldfil0; /* fildes for original file 0 */ char pidp[6], argstr[6]; int wide 5; /* glitch for 5char pid # */ char devtty[10]; int ldivr; char **dolv; int dolc; char *promp, *opromp, *optpromp; char *linep; char *elinep; char **argp; char **eargp; int *treep; int *treeend; char peekc; char gflg; char error; char acctf; /* <= 0 ==> no acctg at all, >0 ==> acctg */ char acctfi; /* = 0 ==> no acctg for internal cmds */ char uid; char setintr; char *arginp; char onelflg; char rflg; int exitcode; char exitstr[6] ; char *seta[26]; char *endcore, *endptr; int oldintr; /* save INTR state existing at start */ int wasintr; char gointr[32]; int catchintr(); char proflag; /* 1 ==> inside .profile */ /* 2 ==> interrupt in .profile */ char redirf; /* 1 ==> I/O redirection of input; controls pump rebuffering */ char optfv; /* 0 ==> +v, 1 ==> -v; -v ==> print commands */ struct statb sb; /* commands performed internally */ char *comint[] { "chdir", /* 0 */ "shift", /* 1 */ "login", /* 2 */ "newgrp", /* 3 */ "wait", /* 4 */ ":", /* 5 */ "onintr", /* 6 */ "=", /* 7 */ "next", /* 8 */ "if", /* 9 */ "else", /* 10 */ "endif", /* 11 */ "goto", /* 12 */ "test", /* 13 */ "switch", /* 14 */ "break", /* 15 */ "endsw", /* 16 */ "exit", /* 17 */ "while", /* 18 */ "end", /* 19 */ "continue", /* 20 */ "breaksw", /* 21 */ "opt", /* 22 */ "cd", /* 23, another name for chdir */ 0 }; /* defines for internal commands */ #define ZCHDIR 0 #define ZSHIFT 1 #define ZLOGIN 2 #define ZNEWGRP 3 #define ZWAIT 4 #define ZLABEL 5 #define ZONINTR 6 #define ZEQUALS 7 #define ZNEXT 8 #define ZIF 9 #define ZELSE 10 #define ZENDIF 11 #define ZGOTO 12 #define ZTEST 13 #define ZSWITCH 14 #define ZBREAK 15 #define ZENDSW 16 #define ZEXIT 17 #define ZWHILE 18 #define ZEND 19 #define ZCONTINUE 20 #define ZBREAKSW 21 #define ZOPT 22 #define ZCD 23 #define ONINTR comint[ZONINTR] #define IF comint[ZIF] #define ENDIF comint[ZENDIF] #define ENDSW comint[ZENDSW] #define END comint[ZEND] /* other keywords */ char THEN[] "then"; char *ARG0, *ARG1; /* for diagnostics & internal commands */ int COMTYPE; /* code for command, -1, or one of Z* */ char *mesg[] { 0, "Hangup", 0, "Quit", "Illegal instruction", "Trace/BPT trap", "IOT trap", "EMT trap", "Floating exception", "Killed", "Bus error", "Memory fault", "Bad system call", "Broken pipe", "Alarm clock", "Terminated", 0, 0, 0, 0, }; /* messages */ char *SYNTAX "syntax error: "; char *MISS "missing "; char *MISSL "missing label: "; char *NONUM "non-numeric arg: "; char *ARGCNT "arg count"; char *ARGLNG "arg list too long"; char *BADARG "bad arg: "; char *CANTEX "cannot execute"; char *CANTOP "cannot open: "; char *NOTPER "not permitted"; char *NOTWHI "used outside loop"; char *EQERR "`=' error"; struct stime { long procu, procs, childu, childs, curtim; } timeb; struct { char cname[8]; char lname[6]; char shtty; char tuid; long datet; long realt; long bcput; long bsyst; } tbuf; long tell(); char *logtty(), *nxtarg(), *sname(); /* following items implement while -- end stack of WDEEP levels */ #define WDEEP 3 #define INWHILE (wtop < WDEEP) struct { long sloc; /* starting loc = addr of while */ long eloc; /* ending loc = addr of line AFTER end */ } wstk[WDEEP]; int wtop WDEEP; /* top of stack */ main(c, av) int c; char **av; { register f; register char *p, **v; copy(itoa(getpid()), pidp); copy("/dev/tty",devtty); setxcod(0); if (c >= 2 && (eq(av[1], "-v") || eq(av[1], "-x"))) { optfv = 1; av[1] = av[0]; av++; --c; } f = c-2; f = f<0 ? 0 : f; copy(itoa(f), argstr); seta[N] = argstr; seta[T] = logtty(); copyn(logname(), tbuf.lname, sizeof tbuf.lname); tbuf.shtty = devtty[8] = *seta[T]; v = av; p = sname(*v); if (eq(p,"-rsh") || eq(p,"rsh")) rflg++; if (*p == '-') proflag++; for (f = 3; f < NOFILE; f++) close(f); oldfil0 = dup(0); /* save for pipe into, .profile */ promp = "% "; if(c > 1) { promp = 0; tbuf.shtty = 'x'; signal(INTR, oldintr = signal(INTR, 1)); if (*v[1]=='-') { *p = '-'; if (v[1][1]=='c' && c>2) arginp = v[2]; else if (v[1][1]=='t') onelflg = 2; } else { close(0); f = open(v[1], 0); if(f < 0) { prs(CANTOP); err(v[1]); exit(1); } bnread = BSIZFIL; } } if (onelflg || arginp || (seek(0, 0, 1) == -1 && errno == ESPIPE)) bnread = 1; /* no lookahead, no buffering */ else bbuf = sbrk(bnread); endptr = endcore = sbrk(0); seta[S] = logdir(); setwhere(); if(((uid = getuid())&0377) == 0) { if (promp) promp = "# "; } pexinit(); initacct(); if (proflag) { if (uid != 0 && (f = open(".profile",0)) >= 0) { close(0); dup(f); close(f); promp = 0; signal(INTR, catchintr); } else proflag = 0; } if(*p == '-' && !proflag) { setintr++; signal(QUIT, 1); signal(INTR, 1); } dolv = v+1; dolc = c-1; loop: if (promp) prs(optpromp ? optpromp : promp); main1(); if(wasintr) { if(eq(gointr, "-")) goto loop; wasintr = 0; if (proflag > 1) { b.nleft = 0; goto loop; /* will cause faked `next' */ } wtop = WDEEP; /* clear out while stack */ ARG0 = ONINTR; ARG1 = gointr; search(COMTYPE = ZGOTO, 0); setxcod(1); signal(INTR, 0); } goto loop; } char line[LINSIZ]; main1() { char *args[ARGSIZ]; int trebuf[TRESIZ]; register char c, *cp; register *t; argp = args; eargp = args+ARGSIZ-5; linep = line; elinep = line+LINSIZ-5; error = 0; gflg = 0; b.linloc = BNOW; /* find where we are, in case while */ COMTYPE = -1; /* avoid bad diagnostic from readc */ do { cp = linep; word(); if (optfv) { putc(' '); prs(cp); } } while(*cp != '\n'); treep = trebuf; treeend = &trebuf[TRESIZ]; if(gflg == 0) { if(error == 0) { setexit(); if (error) return; t = syntax(args, argp); } if(error != 0) { err("syntax error"); } else { redirf = 0; /* no redirect of fildes 0 yet */ execute(t); bsynch(0); /* adjust internal to real */ } } } word() { register char c, c1; register dolflag; *argp++ = linep; loop: switch(c = getc(DOLREPL)) { case ' ': case '\t': goto loop; case '\'': /* '...' : what you see is what you get */ case '"': /* "..." : \", \$, $ substitution */ c1 = c; dolflag = (c == '"' && !dolp) ? DOLREPQ : !DOLREPL; while((c=getc(dolflag)) != c1) { if(c == '\n') { error++; peekc = c; return; } if (c1 == '"' && c == '\\' && ((peekc = getc(!DOLREPL)) == '$' || peekc == '"')) { c = peekc; peekc = 0; } *linep++ = c|QUOTE; } goto pack; case '&': case '|': *linep++ = c; if((peekc=getc(DOLREPL)) == c) peekc = 0; else linep--; case ';': case '<': case '>': case '(': case ')': case '^': case '\n': *linep++ = c; *linep++ = '\0'; return; case '\\': if ((c=getc(!DOLREPL))=='\n') goto loop; else { c =| QUOTE; break; } } peekc = c; pack: for(;;) { if ((c = getc(DOLREPL))=='\\') { if ((c=getc(!DOLREPL))=='\n') c = ' '; else c =| QUOTE; } if(any(c, " '\"\t;&<>()|^\n")) { peekc = c; if(any(c, "\"'")) goto loop; *linep++ = '\0'; return; } *linep++ = c; } } tree(n) int n; { register *t; t = treep; treep =+ n; if (treep>treeend) { prs("Command line overflow\n"); error++; reset(); } return(t); } char subchar '$'; /* variable marker, may be changed by pump */ /* flag: !DOLREPL ==> no substitution, DOLREPL ==> substitute, DOLREPQ ==> quoted substitution: "$1" = value of $1 for sure */ getc(flag) register flag; { register char c; if(peekc) { c = peekc; peekc = 0; return(c); } if(argp > eargp) { argp =- 10; while((c=getc(!DOLREPL)) != '\n'); argp =+ 10; err("Too many args"); gflg++; return(c); } if(linep > elinep) { linep =- 10; while((c=getc(!DOLREPL)) != '\n'); linep =+ 10; err("Too many characters"); gflg++; return(c); } getd: if(dolp) { if (c = *dolp++) { if (flag == DOLREPQ) c =| QUOTE; return c; } if (idolp && ++idolp < dolc) { dolp = dolv[idolp]; return(' '); } dolp = 0; } c = readc(); if(c == subchar && flag) { c = readc(); if(c>='0' && c<='9') { if(c-'0' < dolc) dolp = dolv[c-'0']; goto getd; } else if(c>='a' && c<='z') { dolp = seta[c-'a']; goto getd; } else if(c == '$') { dolp = pidp; goto getd; } /* $* = $1 $2 .... */ else if (c == '*') { if (dolc > 1) { idolp = 1; dolp = dolv[1]; } goto getd; } else if(c != '\n') c = readc(); } return(c&0177); } /* * syntax * empty * syn1 */ syntax(p1, p2) register char **p1, **p2; { while(p1 != p2) { if(any(**p1, ";&\n")) p1++; else return(syn1(p1, p2)); } return(0); } /* * syn1 * syn1a * syn1a & syntax * syn1a ; syntax */ syn1(p1, p2) char **p1, **p2; { register char **p; register *t, *t1; int l; l = 0; for(p=p1; p!=p2; p++) switch(**p) { case '(': l++; continue; case ')': l--; if(l < 0) error++; continue; case '&': if((*p)[1] == '&') continue; /* and, not asynch */ case ';': case '\n': if(l == 0) { l = **p; t = tree(4); t[DTYP] = TLST; t[DLEF] = syn1a(p1, p); t[DFLG] = 0; if(l == '&') { t1 = t[DLEF]; t1[DFLG] =| FAND|FPRS|FINT; } t[DRIT] = syntax(p+1, p2); return(t); } } if(l == 0) return(syn1a(p1, p2)); error++; } /* * syn1a * syn1b * syn1b || syn1a */ syn1a(p1,p2) char **p1, **p2; { register char **p; register int l, *t; l = 0; for(p=p1; p!=p2; p++) switch(**p) { case '(': l++; continue; case ')': l--; continue; case '|': if((*p)[1] == '\0') continue; /* a pipe not an or */ if(l == 0) { t = tree(4); t[DTYP] = TOR; t[DLEF] = syn1b(p1, p); t[DRIT] = syn1a(p+1, p2); t[DFLG] = 0; return(t); } } return(syn1b(p1, p2)); } /* * syn1b * syn2 * syn2 && syn1b */ syn1b(p1,p2) char **p1, **p2; { register char **p; register int l, *t; l = 0; for(p=p1; p!=p2; p++) switch(**p) { case '(': l++; continue; case ')': l--; continue; case '&': if(l == 0) { t = tree(4); t[DTYP] = TAND; t[DLEF] = syn2(p1, p); t[DRIT] = syn1b(p+1, p2); t[DFLG] = 0; return(t); } } return(syn2(p1, p2)); } /* * syn2 * syn3 * syn3 | syn2 */ syn2(p1, p2) char **p1, **p2; { register char **p; register int l, *t; l = 0; for(p=p1; p!=p2; p++) switch(**p) { case '(': l++; continue; case ')': l--; continue; case '|': case '^': if(l == 0) { t = tree(4); t[DTYP] = TFIL; t[DLEF] = syn3(p1, p); t[DRIT] = syn2(p+1, p2); t[DFLG] = 0; return(t); } } return(syn3(p1, p2)); } /* * syn3 * ( syn1 ) [ < in ] [ > out ] * word word* [ < in ] [ > out ] */ syn3(p1, p2) char **p1, **p2; { register char **p; char **lp, **rp; register *t; int n, l, i, o, c, flg; flg = 0; if(**p2 == ')') flg =| FPAR; lp = 0; rp = 0; i = 0; o = 0; n = 0; l = 0; for(p=p1; p!=p2; p++) switch(c = **p) { case '(': if(l == 0) { if(lp != 0) error++; lp = p+1; } l++; continue; case ')': l--; if(l == 0) rp = p; continue; case '>': p++; if (p1 != p2 && **p == '>') { if (l == 0) flg =| FCAT; } else p--; case '<': if(l == 0) { p++; if(p == p2) { error++; p--; } if(any(**p, "<>(")) error++; if(c == '<') { if(i != 0) error++; i = *p; continue; } if(o != 0) error++; o = *p; } continue; default: if(l == 0) p1[n++] = *p; } if(lp != 0) { if(n != 0) error++; t = tree(5); t[DTYP] = TPAR; t[DSPR] = syn1(lp, rp); } else { if(n == 0) error++; p1[n++] = 0; t = tree(n+5); t[DTYP] = TCOM; for(l=0; l<n; l++) t[l+DCOM] = p1[l]; } t[DFLG] = flg; t[DLEF] = i; t[DRIT] = o; return(t); } scan(at, f) int *at; int (*f)(); { register char *p; register *t; t = at+DCOM; while(p = *t++) (*f)(p); } tglob(s) char *s; { register char *p, c; for (p=s; c = *p++;) if(any(c, "[?*")) gflg = 1; } trim(s) char *s; { register char *p; for (p=s; *p++ =& 0177; ); return(s); } int ap, ac; /* arg pointer & count for if & related cmds */ char **av; /* av[0] = t[DCOM] */ int *savdlef; /* for cmd piped into, has &t for cmd on other end */ execute(t, pf1, pf2) int *t, *pf1, *pf2; { int i, f, pv[2], wt; register *t1; register char *cp1, *cp2; tryagain: /* if expr command may come back here to do command */ if(t != 0) switch(t[DTYP]) { case TCOM: ARG0 = cp1 = t[DCOM]; ARG1 = cp2 = t[DCOM+1]; if((COMTYPE = lookup(cp1)) < 0) goto notinternal; if (acctfi) { times(&timeb); time(&timeb.curtim); } setxcod(0); /* assume will be good */ av = &t[DCOM]; ap = 1; for(ac = 1; av[ac]; ac++); if (cp2) trim(cp2); if (COMTYPE != ZEQUALS) /* = takes care of self,can't do now */ bsynch(0); /* make sure internal ptr right */ switch (COMTYPE) { case ZCHDIR: case ZCD: if (rflg) { die(NOTPER, 0); break; } if(cp2 == 0) die(ARGCNT, 0); else if(chdir(cp2) < 0) die("bad directory", 0); break; case ZSHIFT: if(dolc < 1) { die(ARGCNT, 0); break; } if(cp2==0) { dolv[1] = dolv[0]; dolv++; dolc--; break; } if((i = atoi(cp2)) < 0 || i > dolc) { die(BADARG, cp2); break; } for(; i < dolc; i++) dolv[i] = dolv[i+1]; dolc--; break; case ZLOGIN: if(promp != 0) { fclean(); execv("/bin/login", t+DCOM); } die(CANTEX, 0); break; case ZNEWGRP: if(promp != 0) { fclean(); execv("/bin/newgrp", t+DCOM); } die(CANTEX, 0); break; case ZWAIT: pwait(-1, 0); break; case ZLABEL: case ZENDIF: case ZENDSW: break; case ZONINTR: /* suppress onintr if interactive shell, or if * noninteractive fired up immune to INTR. */ if (promp || oldintr) break; if(!cp2) { signal(INTR, 0); break; } copy(cp2, gointr); wasintr = 0; signal(INTR, catchintr); break; case ZEQUALS: if(t[DFLG]&FPIN) close(pf1[1]); i = *cp2 - 'a'; if(t[DCOM+3] != 0 && eq(t[DCOM+2], "")) { t[DCOM+2] = t[DCOM+3]; setxcod(1); } /* 3rd exists & null 2nd ==> use 3rd instead */ if(i>25 || i<0 || (i==P && rflg)) err(EQERR); else { f = t[DFLG]&FPIN; if ((seta[i] = rdval((f ? pf1[0] : 0), t[DLEF], t[DCOM+2]))==0) err(EQERR); if (f) { /* piped, must assure synch */ pwait(savdlef[DSPR], savdlef); bsynch(2); } } break; case ZNEXT: opromp = promp; if(!cp2) { f = dup(oldfil0); promp = uid? "% ": "# "; bnread = BSIZ; proflag = 0; } else { if (rflg) { die(NOTPER, 0); break; } promp = 0; f = open(cp2, 0); } if (f<0) { promp = opromp; die(CANTOP, cp2); } else { close(0); dup(f); close(f); signal(INTR, promp?1:0); signal(QUIT, promp?1:0); setintr = promp?1:0; } b.nleft = b.gotten = 0; b.start = 0; for (wtop = 0; INWHILE; wtop++) { wstk[wtop].sloc = 0; wstk[wtop].eloc = 0; } break; case ZEXIT: bflush(); setxcod(cp2 ? atoi(cp2) : 0); break; case ZGOTO: /* following takes care of (unlikely) case in which * goto exits from loop(s) whose end(s) are as * yet unlocated. although not efficient, it is * rigged to act in the intuitively proper way. */ i = wtop; while (INWHILE) { if (wstk[wtop].eloc == 0) { search(ZBREAK, 0); /* find end */ wstk[wtop].eloc = BNOW; } else bseek(wstk[wtop].eloc); wtop++; } wtop = i; search(ZGOTO, 0); b.linloc = BNOW; /* effect of goto on while */ while (INWHILE && (b.linloc < wstk[wtop].sloc || b.linloc > wstk[wtop].eloc)) wtop++; /* pop */ break; case ZELSE: search(ZELSE, eq(cp2, IF) && eq(av[ac-1], THEN)); break; case ZBREAK: if (INWHILE) toend(); else die(NOTWHI, 0); break; case ZBREAKSW: ARG1 = 0; /* ignore possible arg */ case ZSWITCH: search(COMTYPE, 0); /* ZBREAKSW or ZSWITCH */ break; case ZTEST: i = exp(); if(nxtarg() != 0) die(SYNTAX, av[ap-1]); else setxcod(!i); break; case ZIF: if (exp()) { if (eq(nxtarg(), THEN)) break; --ap; /* ap -> new cmd */ /* shift args to eliminate if */ for(i = DSPR; i >= 0; i--) av[--ap] = t[i]; t = &av[ap]; enacct(cp1, 1); /* acct for if part */ goto tryagain; } else if (eq(nxtarg(), THEN)) search(ZIF, 0); break; case ZWHILE: if (ac > 2) { /* expr like that of if */ i = exp(); if (cp1 = nxtarg()) { die(SYNTAX, cp1); break; } } else /* 0 args or 1 null arg = false, else true*/ i = cp2 != 0 && *cp2 != '\0'; if (wtop >= WDEEP || wstk[wtop].sloc != b.linloc) { /* not already in loop */ if (--wtop >= 0) { if (wstk[wtop].sloc != b.linloc) { wstk[wtop].sloc = b.linloc; wstk[wtop].eloc = 0; } } else { die(">3 levels", 0); break; } } if (i) break; toend(); /* condition nomatch */ break; case ZCONTINUE: if (INWHILE) bseek(wstk[wtop].sloc); else die(NOTWHI, 0); break; case ZEND: if (INWHILE) { /* while active */ wstk[wtop].eloc = BNOW; bseek(wstk[wtop].sloc); } else die(NOTWHI, 0); break; case ZOPT: for(i = DCOM+1; cp2 = t[i]; i++) { if (eq(cp2, "-v") || eq(cp2, "-x")) optfv = 1; else if (eq(cp2, "+v") || eq(cp2, "+x")) optfv = 0; else if (eq(cp2, "-p")) { if (cp2 = t[++i]) optpromp = rdval(0, 0, cp2); else optpromp = ""; } } break; } enacct(cp1, 1); /* internal commands */ return; notinternal: case TPAR: bsynch(1); /* set real ptr = BNOW */ f = t[DFLG]; i = 0; if((f&FPAR) == 0) if ((i = dofork()) == -1) return; /* couldn't fork */ if(i != 0) { /* parent */ if((f&FPIN) != 0) { close(pf1[0]); close(pf1[1]); t[DLEF] = savdlef; /* link back for pwait*/ } else t[DLEF] = 0; /* 1st or only in pipeline */ t[DSPR] = i; /* save proc-num for pwait */ if ((f & FPRS) && promp) { prs(itoa(i)); prs("\n"); } if ((f & (FAND | FPOU)) == 0) pwait(i, t); bsynch(2); /* mark possible read by cmd */ return; } /* * ignore interrupts for asynchronous cmds [code needed * for sh proc only; ignore also for onintr - */ if (f & FPRS) { signal(INTR, 1); signal(QUIT, 1); } if (eq(gointr, "-")) signal(INTR, 1); if(t[DLEF] != 0) { close(0); /* following for pipe into sh */ trim(t[DLEF]); if (eq(t[DLEF], "--")) dup(oldfil0); else { i = open(t[DLEF], 0); if(i < 0) xdie(CANTOP, t[DLEF]); } redirf++; } if(t[DRIT] != 0) { if (rflg) xdie(">: ",NOTPER); if((f&FCAT) != 0) { i = open(trim(t[DRIT]), 1); if(i >= 0) { seek(i, 0, 2); goto f1; } } i = creat(trim(t[DRIT]), 0666); if(i < 0) xdie("cannot create: ",t[DRIT]); f1: close(1); dup(i); close(i); } if((f&FPIN) != 0) { close(0); dup(pf1[0]); close(pf1[0]); close(pf1[1]); redirf++; } if((f&FPOU) != 0) { close(1); dup(pf2[1]); close(pf2[0]); close(pf2[1]); } if((f&FINT)!=0 && t[DLEF]==0 && (f&FPIN)==0) { close(0); open("/dev/null", 0); } if((f&FINT) == 0 && setintr) { signal(INTR, 0); signal(QUIT, 0); } if(t[DTYP] == TPAR) { if(t1 = t[DSPR]) t1[DFLG] =| f&FINT; execute(t1); exit(exitcode); } fclean(); if (rflg && any('/', trim(t[DCOM]))) xdie(NOTPER, 0); /* no / in rsh commands */ gflg = 0; scan(t, &tglob); if (gflg && t[DCOM+1]) { /* old glob stuff t[DSPR] = seta[P]; execv("/etc/glob", t+DSPR); xdie("glob: ",CANTEX); */ etcglob(t + DCOM); } scan(t, &trim); if (eq(cp1, "pump")) dopump(&t[DCOM+1], redirf); texec(t[DCOM], t); case TFIL: f = t[DFLG]; pipe(pv); t1 = t[DLEF]; t1[DFLG] =| FPOU | (f&(FPIN|FINT|FPRS)); execute(t1, pf1, pv); t1 = t[DRIT]; t1[DFLG] =| FPIN | (f&(FPOU|FINT|FAND|FPRS)); savdlef = t[DLEF]; /* save so can link pipe together */ execute(t1, pv, pf2); return; case TLST: f = t[DFLG]&FINT; if(t1 = t[DLEF]) t1[DFLG] =| f; execute(t1); if(t1 = t[DRIT]) t1[DFLG] =| f; execute(t1); return; case TOR: case TAND: f = t[DFLG]&FINT; t1 = t[DLEF]; t1[DFLG] =| f; execute(t1); i = atoi(seta[R]); if ((i == 0) == (t[DTYP] == TAND)) { t1 = t[DRIT]; t1[DFLG] =| f; execute(t1); } return; } } toend() { if (wstk[wtop].eloc == 0) { /* need to find end */ ARG1 = 0; search(ZBREAK, 0); } else bseek(wstk[wtop].eloc); wtop++; /* pop 1 level */ return; } lookup(p) register char *p; { register char *q; register i; for (i = 0; q = comint[i]; i++) if (eq(p, q)) return(i); return -1; } dofork() { register wt, i; for(wt = 10;; wt =+ 10) { if ((i = fork()) != -1) break; if (promp == 0 && wt < 60) sleep(wt); else { err("cannot fork;try again"); break; } } return i; } fclean() { if (acctf) close(acctf); if (oldfil0) close(oldfil0); return; } texec(f, t) register *t; { extern errno; register char *cp; char tline[48]; char txe2big, txeacces; int txtbsy; /* kludge cntr for ETXTBSY fix */ txeacces = txe2big = 0; txtbsy = 0; cp = seta[P]; /* normal case -- search */ if (any('/', f)) { if (rflg) xdie(NOTPER, 0); /* no / by rsh */ cp = ""; /* sh: exec only cmd name as given */ } do { cp = pcat(cp, f, tline, sizeof tline); retry: execv(tline, t+DCOM); switch (errno) { case ENOEXEC: t[DCOM] = tline; t[DSPR] = seta[Z]; execv(t[DSPR], t+DSPR); xdie(seta[Z], " No shell!"); case EACCES: txeacces++; /* file there, missing x (probably) */ break; case ENOMEM: xdie("too large", 0); case E2BIG: txe2big++; break; case ETXTBSY: if ((txtbsy =+ 10) > 60) xdie("text busy", 0); sleep(txtbsy); goto retry; } } while(cp); if (txe2big) xdie(ARGLNG,0); if (txeacces) xdie("file not executable", 0); xdie("not found", 0); } char pipebomb; /* 1 ==> SIGPIPE caught */ catchpipe() { if (promp != 0) exit(1); pipebomb++; return; } /* dopump: pump command: * pump [+] [-[subchar]] [eofstr] * default is to substitute using $ * - suppresses substitution * -subchar uses subchar instead of $ for substitution * + causes leading tabs in input to be thrown away * eofstr defaults to ! * eofstr may not begin with `+', which is reserved for future flags. * acts like filter, although actually copy of sh * in input, \subchar = subchar, no other escaping performed * if interactive, ignores interrupts & quits, but dies if other end * of pipe dies; if non-interactive, reads to eofstr or real eof * even if other end does die. * NOTE: line is used as workarea, both for eofstr and input line * eofstr is safe because initial arg can't be at beginning */ dopump(t) register char **t; { #define eofstr line #define pumpwk (line+96) register char c, *p; char tabeat; tabeat = 0; if (redirf) { b.nleft = b.gotten = 0; /* clear buffer values */ bnread = 1; /* be careful with pipes & files */ } else { if (promp == 0) bsynch(0); /* use existing b., if any */ else { signal(INTR, 1); /* interactive - ignore */ signal(QUIT, 1); /* for sake of pump -|ed */ } } if (proflag) { signal(INTR, 0); proflag = 0; } signal(SIGPIPE, catchpipe); setxcod(0); /* in case real eof */ copy("!", eofstr); for(; *t; t++) switch (**t) { case '-': subchar = *(*t + 1); /* \0 turns subst off */ break; case '+': tabeat++; break; default: copy(*t, eofstr); } while(1) { /* real eof will cause exit in readc */ p = pumpwk; if (tabeat) { while((c = getc(DOLREPL)) == '\t'); peekc = c; } do { c = getc(DOLREPL); if (c == '\\' && (peekc = getc(!DOLREPL)) == subchar) { c = peekc; peekc = 0; } *p++ = c; } while (c != '\n'); *--p = 0; /* put 0 for \n */ if (eq(pumpwk, eofstr)) break; *p = '\n'; if (!pipebomb) write(1, pumpwk, p - pumpwk + 1); } bsynch(1); /* in case file */ exit(pipebomb); } atoi(s) char *s; { register char *sp; register i, neg; sp = s; i = neg = 0; if(*sp == '-') { ++neg; ++sp; } while(*sp) { if(*sp < '0' || *sp > '9') { die(NONUM, s); return 0; } i = i * 10 + (*sp - '0'); ++sp; } return neg? -i: i; } xdie(str1, str2) char *str1, *str2; { die(str1, str2); exit(1); } die(str1, str2) char *str1, *str2; { prs(ARG0); prs(": "); prs(str1); err(str2); return; } /* * err: emit error message, flush input by seeking to EOF (but only * if reading from 0 in unrestricted way), exit. */ err(s) char *s; { prs(s); prs("\n"); if (onelflg == 0 && arginp == 0) bflush(); setxcod(1); } prs(s) register char *s; { if (s == 0) return; while(*s) putc(*s++); } putc(c) { write(2, &c, 1); } any(c, s) register char c, *s; { while(*s) if(*s++ == c) return(1); return(0); } eq(s1, s2) register char *s1, *s2; { if (s1 == 0 || s2 == 0) return 0; while(*s1++ == *s2) if(*s2++ == '\0') return(1); return(0); } /* initacct: initialize acctg according to state of ACNAME file: if cannot open ACNAME, no acctg done (acctf == 0) group permissions of ACNAME are used otherwise: 0 cmd level, externals only (acctf > 0, acctfi == 0) 4 cmd level, add internal cmds (acctf > 0, acctfi > 0) 2 shell proc, externals only (acctf > 0, acctfi == 0) 1 shell proc, add internal cmds (acctf > 0, acctfi > 0) NOTE: THIS IS KLUDGE INTENDED FOR OBLIVION AFTER EITHER NEW MH ACCTG GETS INSTALLED AND/OR INTERRUPT-HANDLING CHANGE PERMITS BETTER INTERNAL CMD HANDLING. */ initacct() { register f; if ((acctf = open(ACNAME, 1)) < 0) acctf = 0; /* no acctg at all */ else { fstat(acctf, &sb); f = sb.i_mode; if (tbuf.shtty == 'x') { /* shell proc */ if (f & 020) { if (f & 010) acctfi++; /* ext + int */ } else { close(acctf); /* no acct at all */ acctf = 0; } } else if (f & 040) acctfi++; /* cmd level, ext + int */ } return; } pwait(i, t) int i, *t; { register p, e, *t1; int nprocs; int s; nprocs = (t1 = t) ? 0 : 1; while(t1) { /* count number of procs in current pipeline */ if (t1[DSPR]) nprocs++; t1 = t1[DLEF]; } if(i != 0) do { if (acctf) { times(&timeb); time(&timeb.curtim); } p = wait(&s); if(wasintr) p = wait(&s); if(p == -1) break; e = s&0177; exitcode = (s>>8)&0377; if(e==INTR && wasintr) /* process returned intr */ e = 0; if(mesg[e] != 0) { if(p != i) { prs(itoa(p)); prs(": "); } prs(mesg[e]); if(s&0200) prs(" -- Core dumped"); } if (p == i) setxcod(exitcode); if(e != 0) err(""); for(t1 = t; t1; t1 = t1[DLEF]) { /* hunt proc; elims most **gok*/ if (t1[DSPR] == p) { nprocs--; break; } } acct(t1); } while (nprocs); } acct(t) int *t; { if(t == 0) enacct("**gok", 0); else if(*t == TPAR) enacct("()", 0); else enacct(t[DCOM], 0); } enacct(as, acctype) char *as; int acctype; /* 0 ==> child process, 1 ==> internal cmd */ { struct stime timbuf; register i; register char *np; if (uid == 0) return; if (acctf <= 0) /* if no accounting active, elim sys calls */ return; if (acctype && acctfi <= 0) return; /* internal cmd, but no internal acctg */ times(&timbuf); time(&timbuf.curtim); tbuf.realt = timbuf.curtim - timeb.curtim; if (acctype) { tbuf.bcput = timbuf.procu - timeb.procu; tbuf.bsyst = timbuf.procs - timeb.procs; } else { tbuf.bcput = timbuf.childu - timeb.childu; tbuf.bsyst = timbuf.childs - timeb.childs; } np = sname(as); for (i=0; i<8; i++) { tbuf.cname[i] = *np; if (*np) np++; } tbuf.datet = timbuf.curtim; tbuf.tuid = uid; seek(acctf, 0, 2); write(acctf, &tbuf, sizeof(tbuf)); } rdval(pipef, lef, na) char *na; { register char *st, *np; char c; st = endptr; np = na; if(!pipef && lef) { pipef = eq(lef, "--") ? dup(oldfil0) : open(lef, 0); if(pipef<0) return 0; } for(;;) { if(endptr >= endcore-10) if((endcore=sbrk(64))<0) return 0; if(!na) { if(read(pipef, &c, 1) <= 0) { setxcod(1); /* EOF indicator */ break; } } else c = *np++ & 0177; *endptr++ = c; if(c=='\n' || c=='\0') break; } if(c=='\n') --endptr; *endptr++ = '\0'; if(pipef || lef) close(pipef); return st; } catchintr() { if (proflag) proflag++; /* in .profile, make sure come out ok */ wasintr++; signal(INTR, 1); } pcat(so1, so2, si, sz) register char *so1, *so2; char *si; int sz; { register char *s; s = si; while(*so1 != ':' && *so1 != '\0' && --sz) *s++ = *so1++; if(si != s && --sz > 0) *s++ = '/'; while(*so2 && --sz > 0) *s++ = *so2++; if (--sz < 0) { *si = '\0'; die("cmd line overflow", 0); } else *s = '\0'; return *so1 ? ++so1 : 0; } setxcod(code) int code; { copy(itoa(code), exitstr); seta[R] = exitstr; return; } copy(source, sink) register char *source, *sink; { while(*sink++ = *source++ & 0177); } /* * copyn: copy at most n bytes from source to sink. */ copyn(source, sink, n) register char *source, *sink; int n; { register i; for (i = 0; i < n; i++) if (!(*sink++ = *source++)) break; } itoa(n) { register i, j; register char *cp; static char *str[12]; j = n; for(cp = &str[4]; ;--cp) { if(wide) --wide; j = ldiv(0, j, 10); *cp = ldivr+'0'; if((j | wide) == 0) return cp; } } char *nxtarg() { register iap; if ((iap = ap++) > ac || av[iap] == 0) return(0); return trim(av[iap]); } exp() { int p1; p1 = e1(); if (eq(nxtarg(), "-o")) return(p1 | exp()); ap--; return(p1); } e1() { int p1; p1 = e2(); if (eq(nxtarg(), "-a")) return (p1 & e1()); ap--; return(p1); } e2() { if (eq(nxtarg(), "!")) return(!e3()); ap--; return(e3()); } e3() { int ccode; int nap; int int1, int2; register char *a, *p1, *p2; ccode = 0; a = nxtarg(); if(eq(a, "(")) { ccode = exp(); if(!eq(nxtarg(), ")")) goto erre3; return(ccode); } if(eq(a, "{")) { /* execute a command for exit code */ nap = ap; /* save 1st arg ptr */ while (ap < ac && !eq(av[ap], "}")) trim(av[ap++]); if (ap >= ac || nap == ap) { die(SYNTAX, av[nap]); return 0; } av[ap++] = 0; /* change } to 0 for pexec */ if ((int1 = dofork()) > 0) wait(&ccode); /* parent */ else if (int1 == 0) /* child */ texec(av[nap], &av[nap-DCOM]); return(ccode? 0 : 1); } p1 = nxtarg(); /* file predicates */ if(eq(a, "-r")) return(tio(p1, 0)); if(eq(a, "-w")) return(tio(p1, 1)); if (eq(a, "-f")) { if (stat(p1, &sb) == -1) return 0; return ((sb.i_mode & IFMT) == 0); } if (eq(a, "-d")) { if (stat(p1, &sb) == -1) return 0; return ((sb.i_mode & IFMT) == IFDIR); } if (eq(a, "-s")) { if (stat(p1, &sb) == -1) return 0; return (sb.i_size0 || sb.i_size1); } /* string predicates */ if (eq(a, "-n")) return(p1 && *p1 != '\0'); if (eq(a, "-z")) return(p1 == 0 || *p1 == '\0'); if ((p2 = nxtarg()) == 0) goto erre3; if(eq(p1, "=")) return(eq(a, p2)); if(eq(p1, "!=")) return(!eq(a, p2)); int1 = atoi(a); int2 = atoi(p2); if(eq(p1, "-eq")) return( int1 == int2 ); if(eq(p1, "-ne")) return( int1 != int2 ); if(eq(p1, "-gt")) return( int1 > int2 ); if(eq(p1, "-lt")) return( int1 < int2 ); if(eq(p1, "-ge")) return( int1 >= int2 ); if(eq(p1, "-le")) return( int1 <= int2 ); erre3: die(SYNTAX, p1); } tio(a, f) char *a; int f; { register int fil; if ((fil = open(a, f)) >= 0) { close(fil); return(1); } return(0); } /* search: forward search for required token, type = * ZGOTO: : label * ZSWITCH: : label, : default, or unmatched ENDSW * ZBREAKSW: unmatched endsw * ZIF: unmatched else or endif (or }) (this is failed if-then) * ZELSE: unmatched endif (or }) * ZBREAK: unmatched end (break, failed while or end) * levinit = 0, except when called from else if ... then, when it is 1 */ search(type, levinit) int type, levinit; { register int level, t; register char *aword; char fifel, fbr, fswex; char wordbuf[128]; if (type == ZGOTO) bseek(0L); fifel = type == ZIF || type == ZELSE; fbr = type == ZBREAK; fswex = type == ZSWITCH || type == ZBREAKSW; aword = wordbuf; level = levinit; do { *aword = '\0'; getword(aword); if ((t = lookup(aword)) >= 0) switch(t) { case ZELSE: if (level == 0 && type == ZIF) return; /* leave rest of else line */ continue; case ZIF: while(getword(aword)); if (fifel && eq(aword, THEN)) level++; break; case ZENDIF: if (fifel) level--; break; case ZWHILE: if (fbr) level++; break; case ZEND: if (fbr) level--; break; case ZSWITCH: if (fswex) level++; break; case ZENDSW: if (fswex) level--; break; case ZLABEL: if (getword(aword)) { if (type == ZGOTO) { if (eq(aword, ARG1)) level = -1; } else if (type == ZSWITCH && level == 0 && (eq(aword, "default") || match(ARG1, aword))) level = -1; } break; } getword(0); /* gobble rest of line, if any */ } while (level >= 0 && proflag < 2); return; } /* getword: return next word from input (if any): aword != 0 : get next word (if any, return 1), if none, return 0, do not distturb *aword. aword == 0 : consume rest of line thru (nl). return 1 if word, 0 if only newline left. */ getword(aword) char *aword; { register int found; /* 1 ==> found word, 0 ==> not */ register char c, *wp; wp = aword; found = 0; c = peekc ? peekc : readc(); peekc = '\0'; do { while(c == ' ' || c == '\t') c = readc(); if (c == '\n') { if (wp) break; else return 0; /* newline only thing left */ } found++; do { if (wp) *wp++ = c; c = readc(); if (c == '\\' && (c = readc()) == '\n') c = ' '; } while (c != ' ' && c != '\t' && c != '\n'); } while (!wp); peekc = c; if (found) *wp = '\0'; return(found); } readc() { register c; if (arginp) { if (arginp == 1) goto ONLYEXIT; if ((c = *arginp++) == 0) { arginp = 1; c = '\n'; } return(c); } if (onelflg==1) goto ONLYEXIT; if (b.nleft == 0) { b.nextp = bbuf; b.start = b.start + b.gotten; /* increm by last gotten */ if ((b.nleft = read(0, b.nextp, bnread)) == 0 || proflag > 1) { if (proflag) { /* still inside .profile */ b.nleft = 5; /* fake next */ b.nextp = "next\n"; } else { eoferr(); ONLYEXIT: exit(atoi(seta[R])); } } b.gotten = b.nleft; } b.nleft--; c = *b.nextp++; if (c=='\n' && onelflg) onelflg--; return(c); } /* eoferr: issue error message if cmd was in middle of search */ eoferr() { switch (COMTYPE) { case ZGOTO: die(MISSL, ARG1); break; case ZIF: case ZELSE: die(MISS, ENDIF); break; case ZSWITCH: case ZBREAKSW: die(MISS, ENDSW); break; case ZWHILE: case ZBREAK: die(MISS, END); break; } bsynch(1); return; } /* bflush: complete input flush */ bflush() { seek(0, 0, 2); b.nleft = b.gotten = 0; return; } /* bsynch: synchronize internal buffering & outside world */ /* btarg gives nominal target value of bstate */ bsynch(btarg) register btarg; { register obstate; long f; if (btarg == bstate || promp || bnread == 1 || redirf) return; /* no seeking in any of these cases */ obstate = bstate; bstate = btarg; if (btarg == 2) return; /* just remember this; do nothing now */ switch (obstate) { case 0: /* 0 --> 1 only possible */ if (b.nleft > 0) lseek(0, BNOW, 0); /* set real = internal */ break; case 1: /* 1 --> 0 (pump begin) */ lseek(0, BEND, 0); /* ptr where expected for read */ break; case 2: /* 2 --> 0; 2 --> 1, leave as 2, since 1 dominates */ if (btarg == 0) { f = tell(0); if (f >= 0) bseek(f); } else bstate = 2; break; } } /* bseek: seek, staying within current input buffer b, if possible */ bseek(where) long where; { long bend; if (promp || bnread == 1 || redirf) return; bend = BEND; if (where >= b.start && where < bend) { b.nleft = bend - where; /* 1 <= b.nleft <= b.gotten */ b.nextp = bbuf + b.gotten - b.nleft; lseek(0, bend, 0); /* really at end ofbuffer */ } else { b.nleft = b.gotten = 0; b.start = where; lseek(0, where, 0); } return; } /* sname: simple name (last component) of file name */ char *sname(s) register char *s; { register char *p; for(p = s; *p;) if (*p++ == '/' && *p != '\0') s = p; return(s); } /* setwhere: set up wherev for $w (1st component of pathname) */ char wherev[6]; setwhere() { register char *s, *w; register i; s = seta[S]; seta[W] = w = wherev; i = 0; do { *w++ = *s++; } while (++i < 5 && *s != '/'); } /* * pexinit: fills in pathstr (seta[P]) and shellnam (seta[Z]). * may be invoked before fork to avoid unnecessary .path opening. * returns 0 if OK, -1 if any error. */ pexinit() { char pathbuf[128 + 16]; register n, f; char *newpath, *newshell; char *p; newshell = "/bin/sh"; pcat(seta[S], ".path", pathbuf, sizeof pathbuf); if ((f = open(pathbuf, 0)) < 0) newpath = uid ? ":/bin:/usr/bin" : "/bin:/etc:/"; else { n = read(f, pathbuf, sizeof pathbuf); close(f); if (n <= 0) { prs("cannot read .path"); prs("\n"); return(-1); } if (pexline(pathbuf, pathbuf + n, 128, &newpath, &p) || pexline(p, pathbuf + n, 16, &newshell, &p)) return(-1); } seta[P] = rdval(0, 0, newpath); seta[Z] = rdval(0, 0, newshell); return(0); } /* * pexline: scan for a line (if any) beginning at ptr, * ending at ptrlim -1, for line up to psize bytes long. * convert it to string, return beginning addr in pret * and addr of next line in pnext * return 0 if OK (or not present), -1 if runs off end in middle of line * or if line too long. */ pexline(ptr, ptrlim, psize, pret, pnext) register char *ptr, *ptrlim; int psize; char **pret, **pnext; { if (ptr >= ptrlim) return(0); *pret = ptr; if (ptrlim > ptr + psize) ptrlim = ptr + psize; for (; ptr < ptrlim; ptr++) if (*ptr == '\n') { *ptr++ = '\0'; *pnext = ptr; return(0); } prs(".path too long"); prs("\n"); return(-1); } /* * sh internal version of /etc/glob * (from here thru function cat) * * "*" in params matches r.e ".*" * "?" in params matches r.e. "." * "[...]" in params matches character class * "[...a-z...]" in params matches a through z. * if param does not contain "*", "[", or "?", use it as is * if it does, find all files in current directory * which match the param, sort them, and use them */ #define STRSIZ 5300 char **avx; char *string; char *ablimit; int ncoll; etcglob(argv) char *argv[]; { char ab[STRSIZ]; /* generated characters */ char *avxa[500+DCOM]; /* generated arguments */ avx = &avxa[DCOM]; string = ab; ablimit = &ab[STRSIZ]; *avx++ = *argv++; while (*argv) expand(*argv++); if (ncoll == 0) gdie("No match"); texec(avxa[DCOM], avxa); } expand(as) char *as; { register char *s, *cs; register int dirf; char **oavx; static struct { int ino; char name[16]; } entry; s = cs = as; while (*cs!='*' && *cs!='?' && *cs!='[') { if (*cs++ == 0) { *avx++ = cat(s, ""); return; } } for (;;) { if (cs==s) { dirf = open(".", 0); s = ""; break; } if (*--cs == '/') { *cs = 0; dirf = open(s==cs? "/": s, 0); *cs++ = 0200; break; } } if (dirf < 0) gdie("No directory"); oavx = avx; while (read(dirf, &entry, 16) == 16) { if (entry.ino==0) continue; if (match(entry.name, cs)) { *avx++ = cat(s, entry.name); ncoll++; } } close(dirf); sort(oavx); } sort(oavx) char **oavx; { register char **p1, **p2, **c; p1 = oavx; while (p1 < avx-1) { p2 = p1; while(++p2 < avx) { if (compar(*p1, *p2) > 0) { c = *p1; *p1 = *p2; *p2 = c; } } p1++; } } match(s, p) char *s, *p; { if (*s=='.' && *p!='.') return(0); return(amatch(s, p)); } amatch(as, ap) char *as, *ap; { register char *s, *p; register scc; int c, cc, ok, lc; s = as; p = ap; if (scc = *s++) if ((scc =& 0177) == 0) scc = 0200; switch (c = *p++) { case '[': ok = 0; lc = 077777; while (cc = *p++) { if (cc==']') { if (ok) return(amatch(s, p)); else return(0); } else if (cc=='-') { if (lc<=scc && scc<=(c = *p++)) ok++; } else if (scc == (lc=cc)) ok++; } return(0); default: if (c!=scc) return(0); case '?': if (scc) return(amatch(s, p)); return(0); case '*': return(umatch(--s, p)); case '\0': return(!scc); } } umatch(s, p) char *s, *p; { if(*p==0) return(1); while(*s) if (amatch(s++,p)) return(1); return(0); } compar(as1, as2) char *as1, *as2; { register char *s1, *s2; s1 = as1; s2 = as2; while (*s1++ == *s2) if (*s2++ == 0) return(0); return (*--s1 - *s2); } cat(as1, as2) char *as1, *as2; { register char *s1, *s2; register int c; s2 = string; s1 = as1; while (c = *s1++) { if (s2 > ablimit) gdie(ARGLNG); c =& 0177; if (c==0) { *s2++ = '/'; break; } *s2++ = c; } s1 = as2; do { if (s2 > ablimit) gdie(ARGLNG); *s2++ = c = *s1++; } while (c); s1 = string; string = s2; return(s1); } /* * gdie: terminate with error message, but no seek to EOF * purpose is to make internal glob work same as external one. * something should be done about match/nomatch actions. */ gdie(str) char *str; { prs(ARG0); prs(": "); prs(str); prs("\n"); exit(1); }