#/* Module Name: sh.c -- Improved shell - John Levine, Yale University Installation: cc -O -s -n sh.c;: compile new shell program if ! -r a.out exit;: quit if compile failed : because /bin/sh is a shared-text program we must use this round-about : method to force the use of a new i-node. the old i-node will be kept : until its use count drops to zero. su rm -f /bin/sh /usr/bin/-sh;: remove directory entries su mv a.out /bin/sh;: put in new version su chmod 555 /bin/sh;: protect it su chown bin /bin/sh;: it should be owned by bin and in su chgrp bin /bin/sh;: the bin group for accounting su ln /bin/sh /usr/bin/-sh;: so we can shut off interrupts Module History: Written by John Levine at Rand and Yale modified 7 Dec 77 by Greg Noel to do mail check here instead of login. */ /* #define YALE 0 /* turn on special Yale nonsense */ #define HANGUP 1 #define INTR 2 #define QUIT 3 #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 DTYP 0 #define DLEF 1 #define DRIT 2 #define DFLG 3 #define DSPR 4 #define DCOM 5 #define ENOMEM 12 #define ENOEXEC 8 #define echo(xchar) if(eflag) putc(xchar) char shnam[] "/bin/sh"; /*name of shell */ char globnam[] "/etc/glob"; /* name of glob */ char bin[32]; /* 256 bits that are set for users bin dir */ char pwbuf[80]; /* for password file entry and then current dir */ char dfltfile[30]; /* for default users bin directory */ char *vbls[52]; /* shell variables */ char *dolp; char pidp[6]; int ldivr; char **dolv; int dolc; #define promp vbls['M'-'A'] /* prompt is now $M */ char *linep; char *elinep; char **argp; char **eargp; int *treep; int *treeend; char peekc; char gflg; char error; char acctf; char uid; char setintr; int sinfil; /* real standard input during a next */ int eflag; /* -e echo flag */ char *rpromp; /* real prompt string during a next */ char *arginp; int onelflg; 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", 0, "Sig 14", "Sig 15", "Sig 16", "Sig 17", "Sig 18", "Sig 19", }; struct stime { int proct[2]; int cputim[2]; int systim[2]; } timeb; char line[LINSIZ]; char *args[ARGSIZ]; int trebuf[TRESIZ]; main(c, av) int c; char **av; { register f; register char *acname, **v; setdflt(); newbin(); for(f=2; f<15; f++) close(f); if((f=dup(1)) != 2) close(f); dolc = getpid(); for(f=4; f>=0; f--) { dolc = ldiv(0, dolc, 10); pidp[f] = ldivr+'0'; } v = av; /* * special case for -e */ if(c > 1 && v[1][0] == '-' && v[1][1] == 'e') { v[1] = v[0]; v++; c--; eflag++; } acname = "/usr/adm/sha"; promp = "% "; if(((uid = getuid())&0377) == 0) promp = "# "; acctf = open(acname, 1); if(c > 1) { if(!eflag) promp = 0; if (*v[1]=='-') { **v = '-'; 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(v[1]); err(": cannot open"); return(1); } } } if(**v == '-') { setintr++; signal(QUIT, 1); signal(INTR, 1); if(v[0][1] == 0 && (f = open(".profile",0)) >= 0) next(f); } dolv = v+1; dolc = c-1; vbls['N'-'A'] = putn(dolc); loop: if(promp != 0) { mailck(1); prs(promp); } peekc = getch(); main1(); goto loop; } main1() { register char c, *cp; register *t; argp = args; eargp = args+ARGSIZ-5; linep = line; elinep = line+LINSIZ-5; error = 0; gflg = 0; do { cp = linep; word(); } 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 execute(t); } } word() { register char c, c1; *argp++ = linep; loop: switch(c = getch()) { case ' ': case '\t': goto loop; case '\'': case '"': c1 = c; while((c=readc()) != c1) { if(c == '\n') { error++; peekc = c; return; } *linep++ = c|QUOTE; } goto pack; case '&': case ';': case '<': case '>': case '(': case ')': case '|': case '^': case '\n': *linep++ = c; *linep++ = '\0'; return; } peekc = c; pack: for(;;) { c = getch(); 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); } getch() { register char c; if(peekc) { c = peekc; peekc = 0; return(c); } if(argp > eargp) { argp =- 10; while((c=getch()) != '\n'); argp =+ 10; err("Too many args"); gflg++; return(c); } if(linep > elinep) { linep =- 10; while((c=getch()) != '\n'); linep =+ 10; err("Too many characters"); gflg++; return(c); } getd: if(dolp) { c = *dolp++; if(c != '\0') { echo(c); return(c); } dolp = 0; echo("'}'"); } c = readc(); if(c == '\\') { c = readc(); if(c == '\n') return(' '); return(c|QUOTE); } if(c == '$') { c = readc(); if(c>='0' && c<='9') { if(c-'0' < dolc) { dolp = dolv[c-'0']; echo("'{'"); } goto getd; } if(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z') { if(c >= 'a') c =+ 'Z'+1 - 'a'; if(vbls[c-'A'] == 0) goto getd; /* undefined */ dolp = vbls[c-'A']; echo("'{'"); goto getd; } if(c == '$') { dolp = pidp; echo("'{'"); goto getd; } } return(c&0177); } readc() { char cc; register c; if (arginp) { if (arginp == 1) exit(); if ((c = *arginp++) == 0) { arginp = 1; c = '\n'; } echo(c); return(c); } if (onelflg==1) exit(); if(read(0, &cc, 1) != 1) { if(sinfil) { /* end of next file */ close(0); dup(sinfil); close(sinfil); sinfil = 0; if(setintr) signal(INTR,1); if(promp = rpromp) prs(promp); return(readc()); } exit(0); } if (cc=='\n' && onelflg) onelflg--; echo(cc); return(cc); } /* * syntax * empty * syn1 */ syntax(p1, p2) char **p1, **p2; { while(p1 != p2) { if(any(**p1, ";&\n")) p1++; else return(syn1(p1, p2)); } return(0); } /* * syn1 * syn2 * syn2 & syntax * syn2 ; 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 '&': case ';': case '\n': if(l == 0) { l = **p; t = tree(4); t[DTYP] = TLST; t[DLEF] = syn2(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(syn2(p1, p2)); error++; } /* * 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(p!=p2 && **p=='>') 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); goto out; } 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]; out: t[DFLG] = flg; t[DLEF] = i; t[DRIT] = o; return(t); } scan(at, f) int *at; int (*f)(); { register char *p, c; register *t; t = at+DCOM; while(p = *t++) while(c = *p) *p++ = (*f)(c); } tglob(c) int c; { if(any(c, "[?*")) gflg = 1; return(c); } trim(c) int c; { return(c&0177); } execute(t, pf1, pf2) int *t, *pf1, *pf2; { int i, f, pv[2]; register *t1; register char *cp1, *cp2; extern errno; int unnext(); if(t != 0) switch(t[DTYP]) { case TCOM: cp1 = t[DCOM]; if(equal(cp1, "chdir")||equal(cp1,"cd")) { if(t[DCOM+1] != 0) { if(chdir(t[DCOM+1]) < 0) err("chdir: bad directory"); } else if(chdir(pwbuf) < 0) err("chdir: bad directory"); return; } if(equal(cp1, "newbin")) { newbin(); return; } if(equal(cp1, "set")) { doset(t+DCOM); return; } if(equal(cp1, "shift")) { if(dolc < 1) { prs("shift: no args\n"); return; } dolv[1] = dolv[0]; dolv++; dolc--; xfree(vbls['N'-'A']); vbls['N'-'A'] = putn(dolc); return; } if(equal(cp1, "login")) { if(promp != 0) { close(acctf); execv("/bin/login", t+DCOM); } prs("login: cannot execute\n"); return; } #ifndef YALE /* Yale doesn't have newgrp */ if(equal(cp1, "newgrp")) { if(promp != 0) { close(acctf); execv("/bin/newgrp", t+DCOM); } prs("newgrp: cannot execute\n"); return; } #endif #ifdef YALE if(equal(cp1, "home")) { execl("/bin/home","home",0); prs("home: cannot execute\n"); return; } if(equal(cp1, "logout")) exit(0); #endif if(equal(cp1, "next")) { /* next file */ if(!t[DCOM+1]) { err("next: arg count"); return; } if(sinfil) { err("next: nesting not allowed"); return; } if((f=open(t[DCOM+1],0))<0) { err("next: no file"); return; } next(f); return; } if(equal(cp1, "wait")) { /* wait by pid */ if(t[DCOM+1]) { i = getn(t[DCOM+1]); if(i == 0) return; } else i = -1; if(setintr) signal(INTR,unnext); pwait(i, 0); if(setintr) signal(INTR,1); return; } if(equal(cp1, ":")) return; case TPAR: f = t[DFLG]; i = 0; if((f&FPAR) == 0) i = fork(); if(i == -1) { err("try again"); return; } if(i != 0) { if((f&FPIN) != 0) { close(pf1[0]); close(pf1[1]); } if((f&FPRS) != 0) { prn(i); prs("\n"); xfree(vbls['P'-'A']); vbls['P'-'A'] = putn(i); } if((f&FAND) != 0) return; if((f&FPOU) == 0) pwait(i, t); return; } if(t[DLEF] != 0) { close(0); i = open(t[DLEF], 0); if(i < 0) { prs(t[DLEF]); err(": cannot open"); exit(); } } if(t[DRIT] != 0) { if((f&FCAT) != 0) { i = open(t[DRIT], 1); if(i >= 0) { seek(i, 0, 2); goto f1; } } i = creat(t[DRIT], 0644); if(i < 0) { prs(t[DRIT]); err(": cannot create"); exit(); } f1: close(1); dup(i); close(i); } if((f&FPIN) != 0) { close(0); dup(pf1[0]); close(pf1[0]); close(pf1[1]); } 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(); } close(acctf); gflg = 0; scan(t, &tglob); if(gflg) { /* tell glob about user bin dir if needed */ t[DFLG] = globnam; t[DSPR] = hashme(t[DCOM])? dfltfile: ""; execv(t[DFLG], t+DFLG); prs("glob: cannot execute\n"); exit(); } scan(t, &trim); *linep = 0; cp2 = t[DCOM]; /* khd added */ texec(cp2, t); if (*cp2 != '/') { /* khd added */ if (hashme(cp2)) { cp1 = linep; cp2 = dfltfile; while (*cp1 = *cp2++) cp1++; cp2 = t[DCOM]; while (*cp1++ = *cp2++) ; texec(linep, t); } cp1 = linep; cp2 = "/usr/bin/"; while (*cp1 = *cp2++) cp1++; cp2 = t[DCOM]; while (*cp1++ = *cp2++); texec(linep+4, t); texec(linep, t); } prs(t[DCOM]); err(": not found"); exit(); 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)); 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; } } texec(f, at) int *at; { extern errno; register int *t; t = at; execv(f, t+DCOM); if (errno==ENOEXEC) { if (*linep) t[DCOM] = linep; t[DSPR] = shnam; execv(t[DSPR], t+DSPR); prs("No shell!\n"); exit(); } if (errno==ENOMEM) { prs(t[DCOM]); err(": too large"); exit(); } } /* hashing function for user bin dir */ hashme(ap) char *ap; { register char c, *p; register i; p = ap; i = 0; while(*p) i =+ *p++; return(bin[(i>>3)&037] & 1<<(i&07)); } err(s) char *s; { prs(s); prs("\n"); if(promp == 0) { seek(0, 0, 2); if(rpromp == 0) exit(1); } } prs(as) char *as; { register char *s; s = as; while(*s) putc(*s++); } putc(c) { write(2, &c, 1); } prn(n) int n; { register a; if(a=ldiv(0,n,10)) prn(a); putc(lrem(0,n,10)+'0'); } any(c, as) int c; char *as; { register char *s; s = as; while(*s) if(*s++ == c) return(1); return(0); } equal(as1, as2) char *as1, *as2; { register char *s1, *s2; s1 = as1; s2 = as2; while(*s1++ == *s2) if(*s2++ == '\0') return(1); return(0); } pwait(i, t) int i, *t; { register p, e; int s; if(i != 0) for(;;) { times(&timeb); time(timeb.proct); p = wait(&s); if(p == -1) break; e = s&0177; if(mesg[e] != 0) { if(p != i) { prn(p); prs(": "); } prs(mesg[e]); if(s&0200) prs(" -- Core dumped"); } if(e != 0) err(""); if(i == p) { acct(t); break; } else acct(0); } } acct(t) int *t; { if(acctf<0) return; /* don't waste time */ if(t == 0) enacct("**gok"); else if(*t == TPAR) enacct("()"); else enacct(t[DCOM]); } enacct(as) char *as; { struct stime timbuf; struct { char cname[14]; char shf; char uid; int datet[2]; int realt[2]; int bcput[2]; int bsyst[2]; } tbuf; register i; register char *np, *s; s = as; times(&timbuf); time(timbuf.proct); lsub(tbuf.realt, timbuf.proct, timeb.proct); lsub(tbuf.bcput, timbuf.cputim, timeb.cputim); lsub(tbuf.bsyst, timbuf.systim, timeb.systim); do { np = s; while (*s != '\0' && *s != '/') s++; } while (*s++ != '\0'); for (i=0; i<14; i++) { tbuf.cname[i] = *np; if (*np) np++; } tbuf.datet[0] = timbuf.proct[0]; tbuf.datet[1] = timbuf.proct[1]; tbuf.uid = uid; tbuf.shf = 0; if (promp==0) tbuf.shf = 1; seek(acctf, 0, 2); write(acctf, &tbuf, sizeof(tbuf)); } setdflt() { register int i; register char *s, *t; #ifdef YALE if(getpwg(getuid()&0377, getgid()&0377, pwbuf)) { #endif #ifndef YALE if(getpw(getuid()&0377,pwbuf)) { #endif pwbuf[0] = 0; return; } s = pwbuf; t = pwbuf; i = 5; do { while (*t++ != ':'); } while (--i); /* ignore 5 ':' */ while (*t != ':') *s++ = *t++; /* pick up name */ *s = '\0'; s = dfltfile; t = pwbuf; while (*t) *s++ = *t++; /* pick up name */ for (t = "/bin/"; *s++ = *t++;); /* add on /bin/ */ } newbin() { int a[9], f; register int c, i; register char *s; if(pwbuf[0] == 0) return; a[8] = 0; for(i=0; i<32; i++) bin[i] = 0; /* clear all the bits */ if ((f = open(dfltfile,0)) >= 0) { seek(f,32,0); /* skip . and .. entries */ while (read(f,a,16) == 16) { if (a[0]) { s = &a[1]; i = 0; while (c = *s++) i =+ c; bin[(i>>3)&037] =| (1<<(i&07)); } } } } /* get a number from a string */ getn(as) char *as; { register n; register char *s; int sign; sign = 0; s = as; if(s==0) { err("missing number"); return(0); } if(*s == '-') { s++; sign++; } n = 0; while(*s) if(*s >= '0' && *s <= '9') n = n*10 + *s++ - '0'; else { err("Bad number"); return(0); } if(sign) return(-n); return(n); } /* find length of a string */ size(as) char *as; { register n; register char *s; s = as; n = 0; while(*s++) n++; return(n); } char *putp; /* write a number into a string, clumsily */ putn(n) { char *s; register m; s = putp = alloc(7); if((m=n)<0) { *putp++ = '-'; m = -m; } putn2(m); *putp = 0; return(s); } /* more of putn */ putn2(an) { register n; register m; if(an > 9) putn2(an/10); *putp++ = an%10 + '0'; } /* actual set command */ doset(av) char *av[]; { register char **v; register r; char *p; v = av; if(v[1] == 0) { yuck: err("Bad set command"); return; } r = v[1][0]; if(r < 'A' || r > 'Z' && r < 'a' || r > 'z') goto yuck; if(r > 'Z') r =+ 'Z'+1 - 'a'; r =- 'A'; if(v[2] == 0) return; if(v[3] == 0) goto yuck; switch(v[2][0]) { case '=': p = alloc(size(v[3])+1); copyit(v[3],p); stash: /* * kludge to allow setting the prompt inside next files */ if(r == 'M'-'A' && sinfil) { xfree(rpromp); rpromp = p; return; } xfree(vbls[r]); vbls[r] = p; return; case '+': p = putn(getn(vbls[r])+getn(v[3])); goto stash; case '-': p = putn(getn(vbls[r])-getn(v[3])); goto stash; default: goto yuck; } } /* fast string to string copy */ copyit(ap,aq) char *ap, *aq; { register char *p, *q; p = ap; q = aq; while(*q++ = *p++); } /* special free routine */ xfree(c) char *c; { extern char end[]; if(c >= end) free(c); } /* interpret the "next" command */ next(f) { int unnext(); sinfil = dup(0); /* save real standard input */ close(0); dup(f); close(f); rpromp = promp; /* save prompt string */ promp = 0; if(setintr) signal(INTR,unnext); } unnext() { seek(0,0,2); setexit(INTR,1); } long mail_tim, /* last modified time of the mail file */ mail_nag; /* after which we should consider nagging */ mailck(code) int code; /* set if we should consider delayed nag */ { register char *p, *q; long tim; char mailname[30]; struct { int junk[5]; int size; int more[8]; long tim1; long tim2; } statb; p = mailname; for(q = pwbuf; *p++ = *q++;); /* copy in default directory pathname */ --p; for(q = "/.mail"; *p++ = *q++;); /* put in mailbox name */ if (stat(mailname, &statb) < 0 || /* no mail file */ statb.size == 0 || /* it's empty */ statb.tim1 > statb.tim2) /* it's been looked at */ return; /* not even interesting */ /* new mail exists -- tell user, but don't bug them */ if(statb.tim2 != mail_tim) { /* it's brandy new since our last check */ time(&tim); /* we'll need current time */ } else if(code) { /* shold we consider nag note? */ time(&tim); if(tim < mail_nag) /* not yet */ return; } else return; /* not new and no nag; we give up */ /* tell user about mail, but remember not to nag them */ prs(" [you have new mail]\n"); mail_tim = statb.tim2; mail_nag = tim + 5*60; /* 5 mins to next nag */ }