From: Bill Joy Date: Sat, 10 Nov 1979 13:32:38 +0000 (-0800) Subject: BSD 3 development X-Git-Tag: BSD-3~757 X-Git-Url: https://git.subgeniuskitty.com/unix-history/.git/commitdiff_plain/4528f86f593ce2a5da3feb2f33a36d5944294d87 BSD 3 development Work on file usr/src/cmd/csh/sh.c Synthesized-from: 3bsd --- diff --git a/usr/src/cmd/csh/sh.c b/usr/src/cmd/csh/sh.c new file mode 100644 index 0000000000..17168f88ee --- /dev/null +++ b/usr/src/cmd/csh/sh.c @@ -0,0 +1,754 @@ +/* Copyright (c) 1979 Regents of the University of California */ +#include "sh.h" + +/* + * C Shell + * + * Bill Joy, UC Berkeley + * October, 1978 + */ + +char *pathlist[] = { SRCHPATH, 0 }; + +main(c, av) + int c; + char **av; +{ + register char **v, *cp; + int nofile = 0; + int reenter = 0; + bool nverbose = 0, nexececho = 0, quitit = 0, fast = 0, prompt = 1; + char *hp; + + settimes(); /* Immed. estab. timing base */ + hp = getenv("HOME"); + v = av; + if (eq(v[0], "a.out")) /* A.out's are quittable */ + quitit = 1; + uid = getuid(); +#ifdef V6 + loginsh = eq(*v, "-"); /* To do .login/.logout */ +#else + loginsh = **v == '-'; +#endif + if (loginsh) + time(&chktim); + + /* + * Move the descriptors to safe places. + * The variable didfds is 0 while we have only FSH* to work with. + * When didfds is true, we have 0,1,2 and prefer to use these. + */ + initdesc(); + + /* + * Initialize the shell variables. + * ARGV and PROMPT are initialized later. + * STATUS is also munged in several places. + * CHILD is munged when forking/waiting + */ + + set("status", "0"); + if (hp == 0) + fast++; /* No home -> can't read scripts */ + else + set("home", hp); + if (uid == 0) + pathlist[0] = "/etc"; + set1("path", saveblk(pathlist), &shvhed); + /* + * Re-initialize path if set in environment + * + cp = getenv("PATH"); + if (cp != 0) { + register int i = 0; + register char *dp; + register char **pv; + + for (dp = cp; *dp; dp++) + if (*dp == ':') + i++; + pv = calloc(i+1, sizeof (char **)); + dp = cp; + i = 0; + while (*dp) { + if (*dp == ':') { + *dp = 0; + pv[i++] = savestr(cp); + *dp = ':'; + } else if (*dp == 0) { + pv[i++] = savestr(cp); + break; + } + dp++; + } + pv[i] = 0; + set1("path", pv, &shvhed); + } +*/ + set("shell", SHELLPATH); + + doldol = putn(getpid()); /* For $$ */ + shtemp = strspl("/tmp/sh", doldol); /* For << */ + + /* + * Record the interrupt states from the parent process. + * If the parent is non-interruptible our hand must be forced + * or we (and our children) won't be either. + * Our children inherit termination from our parent. + * We catch it only if we are the login shell. + */ + parintr = signal(SIGINT, SIG_IGN); /* parents interruptibility */ + signal(SIGINT, parintr); /* ... restore */ + parterm = signal(SIGTERM, SIG_IGN); /* parents terminability */ + signal(SIGTERM, parterm); /* ... restore */ + + /* + * Process the arguments. + * + * Note that processing of -v/-x is actually delayed till after + * script processing. + * + * We set the first character of our name to be '-' if we are + * a shell running interruptible commands. Many programs which + * examine ps'es use this to filter such shells out. + */ + c--, v++; + while (c > 0 && (cp = v[0])[0] == '-') { + do switch (*cp++) { + + case 0: /* - Interruptible, no prompt */ + prompt = 0; + **av = '-'; + nofile++; + break; + + case 'c': /* -c Command input from arg */ + if (c == 1) + exit(0); + c--, v++; + arginp = v[0]; + prompt = 0; + nofile++; + break; + + case 'e': /* -e Exit on any error */ + exiterr++; + break; + + case 'f': /* -f Fast start */ + fast++; + break; + + case 'i': /* -i Interactive, even if !intty */ + intact++; + **av = '-'; + nofile++; + break; + + case 'n': /* -n Don't execute */ + noexec++; + break; + + case 'q': /* -q (Undoc'd) ... die on quit */ + quitit = 1; + break; + + case 's': /* -s Read from std input */ + nofile++; + if (isatty(SHIN)) + **v = '-'; + break; + + case 't': /* -t Read one line from input */ + onelflg = 2; + if (isatty(SHIN)) + **v = '-'; + prompt = 0; + nofile++; + break; + + case 'v': /* -v Echo hist expanded input */ + nverbose = 1; /* ... later */ + break; + + case 'x': /* -x Echo just before execution */ + nexececho = 1; /* ... later */ + break; + + case 'V': /* -V Echo hist expanded input */ + setNS("verbose"); /* NOW! */ + break; + + case 'X': /* -X Echo just before execution */ + setNS("echo"); /* NOW! */ + break; + + } while (*cp); + v++, c--; + } + + if (quitit) /* With all due haste, for debugging */ + signal(SIGQUIT, SIG_DFL); + + /* + * Unless prevented by -, -c, -i, -s, or -t, if there + * are remaining arguments the first of them is the name + * of a shell file from which to read commands. + */ + if (nofile == 0 && c > 0) { + nofile = open(v[0], 0); + if (nofile < 0) { + child++; /* So this ... */ + Perror(v[0]); /* ... doesn't return */ + } + file = v[0]; + SHIN = dmove(nofile, FSHIN); /* Replace FSHIN */ + prompt = 0; + c--, v++; + } + + /* + * Consider input a tty if it really is or we are interactive. + */ + intty = intact || isatty(SHIN); +#ifdef TELL + settell(); +#endif + /* + * Commands are interruptible if we are interactive + * or the process which created us was. + */ + if (intact || parintr == SIG_DFL) + **av = '-'; + + /* + * Save the remaining arguments in ARGV. + * Normally the system-supplied argument list is ok as + * a zero terminated value block. + * On some version 6 systems, it is -1 terminated and setting it + * to zero messes up "ps" so we change it to zero, copy + * the block of pointers, and put it back the way it was. + */ +/* + if (c == 0) + set("argv", 0); + else + */ + if ((int) v[c] == -1) { + /* ick */ + v[c] = 0, setq("argv", copyblk(v), &shvhed), v[c] = (char *) -1; + } else + setq("argv", v, &shvhed); + + /* + * Set up the prompt. + */ + if (prompt) + set("prompt", uid == 0 ? "# " : "% "); + + /* + * If we are an interactive shell, then start fiddling + * with the signals; this is a tricky game. + */ + if (**av == '-') { + setintr++; + if (!quitit) /* Wary! */ + signal(SIGQUIT, SIG_IGN); + signal(SIGINT, SIG_IGN); + signal(SIGTERM, SIG_IGN); + } + + /* + * Set an exit here in case of an interrupt or error reading + * the shell start-up scripts. + */ + setexit(); + haderr = 0; /* In case second time through */ + if (!fast && reenter == 0) { + reenter++; + /* Will have value("home") here because set fast if don't */ + srccat(value("home"), "/.cshrc"); + if (!fast && !arginp && !onelflg) + dohash(); + if (loginsh) +#ifdef NOHELP + srccat("", ".login"); +#else + srccat(value("home"), "/.login"); +#endif + } + + /* + * Now are ready for the -v and -x flags + */ + if (nverbose) + setNS("verbose"); + if (nexececho) + setNS("echo"); + + /* + * All the rest of the world is inside this call. + * The argument to process indicates whether it should + * catch "error unwinds". Thus if we are a interactive shell + * our call here will never return by being blown past on an error. + */ + process(setintr); + + /* + * Mop-up. + */ + if (loginsh) { + printf("logout\n"); + close(SHIN); + child++; + goodbye(); + } + exitstat(); +} + +/* + * Source to the file which is the catenation of the argument names. + */ +srccat(cp, dp) + char *cp, *dp; +{ + register char *ep = strspl(cp, dp); + register int unit = dmove(open(ep, 0), -1); + + /* ioctl(unit, FIOCLEX, NULL); */ + xfree(ep); + srcunit(unit, 0); +} + +/* + * Source to a unit. If onlyown it must be our file or + * we don't chance it. This occurs on ".cshrc"s and the like. + */ +srcunit(unit, onlyown) + register int unit; + bool onlyown; +{ + /* We have to push down a lot of state here */ + /* All this could go into a structure */ + int oSHIN = -1, oldintty = intty; + struct whyle *oldwhyl = whyles; + char *ogointr = gointr, *oarginp = arginp; + int oonelflg = onelflg; +#ifdef TELL + bool otell = cantell; +#endif + struct Bin saveB; + + /* The (few) real local variables */ + jmp_buf oldexit; + int reenter; + register int (*oldint)(); + + if (unit < 0) + return; + if (onlyown) { + struct stat stb; + +#ifdef CC + if (fstat(unit, &stb) < 0 || (stb.st_uid != uid && stb.st_uid != (uid &~ 0377))) { +#endif +#ifdef CORY + if (fstat(unit, &stb) < 0 || (stb.st_uid != uid && stb.st_uid != (uid &~ 0377))) { +#endif +#ifndef CC +#ifndef CORY + if (fstat(unit, &stb) < 0 || stb.st_uid != uid) { +#endif +#endif + close(unit); + return; + } + } + + /* + * There is a critical section here while we are pushing down the + * input stream since we have stuff in different structures. + * If we weren't careful an interrupt could corrupt SHIN's Bin + * structure and kill the shell. + * + * We could avoid the critical region by grouping all the stuff + * in a single structure and pointing at it to move it all at + * once. This is less efficient globally on many variable references + * however. + */ + getexit(oldexit); + reenter = 0; + oldint = signal(SIGINT, SIG_IGN); + setexit(); + reenter++; + if (reenter == 1) { + /* Setup the new values of the state stuff saved above */ + copy(&saveB, &B, sizeof saveB); + fbuf = (char **) 0; + fseekp = feobp = fblocks = 0; + oSHIN = SHIN, SHIN = unit, arginp = 0, onelflg = 0; + intty = isatty(SHIN), whyles = 0, gointr = 0; + /* + * Now if we are allowing commands to be interrupted, + * we let ourselves be interrupted. + */ + signal(SIGINT, setintr ? pintr : oldint); +#ifdef TELL + settell(); +#endif + process(0); /* 0 -> blow away on errors */ + } + signal(SIGINT, oldint); + if (oSHIN >= 0) { + register int i; + + /* We made it to the new state... free up its storage */ + /* This code could get run twice but xfree doesn't care */ + for (i = 0; i < fblocks; i++) + xfree(fbuf[i]); + xfree(fbuf); + + /* Reset input arena */ + copy(&B, &saveB, sizeof B); + + close(SHIN), SHIN = oSHIN; + arginp = oarginp, onelflg = oonelflg; + intty = oldintty, whyles = oldwhyl, gointr = ogointr; +#ifdef TELL + cantell = otell; +#endif + } + + resexit(oldexit); + /* + * If process reset() (effectively an unwind) then + * we must also unwind. + */ + if (reenter >= 2) + error(0); +} + +goodbye() +{ + + if (loginsh) { + signal(SIGQUIT, SIG_IGN); + signal(SIGINT, SIG_IGN); + signal(SIGTERM, SIG_IGN); + setintr = 0; /* No interrupts after "logout" */ + if (adrof("home")) + srccat(value("home"), "/.logout"); + } + exitstat(); +} + +exitstat() +{ + + /* + * Note that if STATUS is corrupted (i.e. getn bombs) + * then error will exit directly because we poke child here. + * Otherwise we might continue unwarrantedly (sic). + */ + child++; + exit(getn(value("status"))); +} + +/* + * Catch an interrupt, e.g. during lexical input. + * If we are an interactive shell, we reset the interrupt catch + * immediately. In any case we drain the shell output, + * and finally go through the normal error mechanism, which + * gets a chance to make the shell go away. + */ +pintr() +{ + register char **v; + + if (setintr) + signal(SIGINT, SIG_IGN); + draino(); + + /* + * If we have an active "onintr" then we search for the label. + * Note that if one does "onintr -" then we shan't be interruptible + * so we needn't worry about that here. + */ + if (gointr) { + search(ZGOTO, 0, gointr); + timflg = 0; + if (v = pargv) + pargv = 0, blkfree(v); + if (v = gargv) + gargv = 0, blkfree(v); + reset(); + } else if (intty) + printf("\n"); /* Some like this, others don't */ + error(0); +} + +/* + * Process is the main driving routine for the shell. + * It runs all command processing, except for those within { ... } + * in expressions (which is run by a routine evalav in sh.exp.c which + * is a stripped down process), and `...` evaluation which is run + * also by a subset of this code in sh.glob.c in the routine backeval. + * + * The code here is a little strange because part of it is interruptible + * and hence freeing of structures appears to occur when none is necessary + * if this is ignored. + * + * Note that if catch is not set then we will unwind on any error. + * In an end-of-file occurs, we return. + */ +process(catch) + bool catch; +{ + register char *cp; + jmp_buf osetexit; + struct wordent paraml; + struct command *t; + + getexit(osetexit); + for (;;) { + paraml.next = paraml.prev = ¶ml; + paraml.word = ""; + t = 0; + setexit(); + justpr = 0; /* A chance to execute */ + + /* + * Interruptible during interactive reads + */ + if (setintr) + signal(SIGINT, pintr); + + /* + * For the sake of reset() + */ + freelex(¶ml), freesyn(t), t = 0; + + if (haderr) { + if (!catch) { + /* unwind */ + doneinp = 0; + resexit(osetexit); + reset(); + } + haderr = 0; + /* + * Every error is eventually caught here or + * the shell dies. It is at this + * point that we clean up any left-over open + * files, by closing all but a fixed number + * of pre-defined files. Thus routines don't + * have to worry about leaving files open due + * to deeper errors... they will get closed here. + */ + closem(); + continue; + } + if (doneinp) { + doneinp = 0; + break; + } + if (intty) { + mailchk(); + /* + * If we are at the end of the input buffer + * then we are going to read fresh stuff. + * Otherwise, we are rereading input and don't + * need or want to prompt. + */ + if (fseekp == feobp) + if (!whyles) + for (cp = value("prompt"); *cp; cp++) + if (*cp == '!') + printf("%d", eventno + 1); + else { + if (*cp == '\\' && cp[1] == '!') + cp++; + putchar(*cp | QUOTE); + } + else + /* + * Prompt for forward reading loop + * body content. + */ + printf("? "); + flush(); + } + err = 0; + + /* + * Echo not only on VERBOSE, but also with history expansion. + * If there is a lexical error then we forego history echo. + */ + if (lex(¶ml) && !err && intty || adrof("verbose")) { + haderr = 1; + prlex(¶ml); + haderr = 0; + } + + /* + * The parser may lose space if interrupted. + */ + if (setintr) + signal(SIGINT, SIG_IGN); + + /* + * Save input text on the history list if it + * is from the terminal at the top level and not + * in a loop. + */ + if (catch && intty && !whyles) + savehist(¶ml); + + /* + * Print lexical error messages. + */ + if (err) + error(err); + + /* + * If had a history command :p modifier then + * this is as far as we should go + */ + if (justpr) + reset(); + + alias(¶ml); + + /* + * Parse the words of the input into a parse tree. + */ + t = syntax(paraml.next, ¶ml); + if (err) + error(err); + + /* + * Execute the parse tree + */ + execute(t); + + /* + * Made it! + */ + freelex(¶ml), freesyn(t); + } + resexit(osetexit); +} + +dosource(t) + register char **t; +{ + register char *f; + register int u; + + t++; + f = globone(*t); + u = dmove(open(f, 0), -1); + xfree(f); + if (u < 0) + Perror(f); + didfds = 0; + srcunit(u, 0); +} + +/* + * Check for mail. + * If we are a login shell, then we don't want to tell + * about any mail file unless its been modified + * after the time we started. + * This prevents us from telling the user things he already + * knows, since the login program insist on saying + * "You have mail." + */ +mailchk() +{ + register struct varent *v; + register char **vp; + time_t t; + int intvl, cnt; + + v = adrof("mail"); + if (v == 0) + return; + time(&t); + vp = v->vec; + cnt = blklen(vp); + intvl = (cnt && number(*vp)) ? (--cnt, getn(*vp++)) : MAILINTVL; + if (intvl < 1) + intvl = 1; + if (chktim + intvl > t) + return; + for (; *vp; vp++) { + bool new; + struct stat stb; + + if (stat(*vp, &stb) < 0) + continue; + /* + * We assume that a file has been read if the access time is + * greater than the mod time. + */ +#ifndef CORY + if (stb.st_size == 0) + continue; +#endif + if (stb.st_atime > stb.st_mtime || stb.st_atime < chktim) + continue; + new = stb.st_mtime > time0; + if (loginsh && !new) + continue; + if (cnt == 1) + printf("You have %smail.\n", new ? "new " : ""); + else + printf("%s in %s.\n", new ? "New mail" : "Mail", *vp); + } + chktim = t; +} + +#include +/* + * Extract a home directory from the password file + * The argument points to a buffer where the name of the + * user whose home directory is sought is currently. + * We write the home directory of the user back there. + */ +gethdir(home) + char *home; +{ + register struct passwd *pp = getpwnam(home); + + if (pp == 0) + return (1); + strcpy(home, pp->pw_dir); + return (0); +} + +/* + * Move the initial descriptors to their eventual + * resting places, closin all other units. + */ +initdesc() +{ + + didcch = 0; /* Havent closed for child */ + didfds = 0; /* 0, 1, 2 aren't set up */ + SHIN = dcopy(0, FSHIN); + SHOUT = dcopy(1, FSHOUT); + SHDIAG = dcopy(2, FSHDIAG); + OLDSTD = dcopy(SHIN, FOLDSTD); + closem(); +} + +#ifndef V6 +exit(i) + int i; +{ + + _exit(i); +} +#endif