rsalz@uunet.uu.net (Rich Salz) (05/31/89)
Submitted-by: ka@june.cs.washington.edu (Kenneth Almquist) Posting-number: Volume 19, Issue 3 Archive-name: ash/part03 # This is part 3 of ash. To unpack, feed it into the shell (not csh). # The ash distribution consists of eight pieces. Be sure you get them all. # After you unpack everything, read the file README. echo extracting exec.c cat > exec.c <<\EOF /* * When commands are first encountered, they are entered in a hash table. * This ensures that a full path search will not have to be done for them * on each invocation. * * We should investigate converting to a linear search, even though that * would make the command name "hash" a misnomer. */ #include "shell.h" #include "main.h" #include "nodes.h" #include "parser.h" #include "redir.h" #include "eval.h" #include "exec.h" #include "builtins.h" #include "var.h" #include "options.h" #include "input.h" #include "output.h" #include "syntax.h" #include "memalloc.h" #include "error.h" #include "init.h" #include "mystring.h" #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include "myerrno.h" #define CMDTABLESIZE 31 /* should be prime */ #define ARB 1 /* actual size determined at run time */ struct tblentry { struct tblentry *next; /* next entry in hash chain */ union param param; /* definition of builtin function */ short cmdtype; /* index identifying command */ char rehash; /* if set, cd done since entry created */ char cmdname[ARB]; /* name of command */ }; STATIC struct tblentry *cmdtable[CMDTABLESIZE]; STATIC int builtinloc; /* index in path of %builtin, or -1 */ #ifdef __STDC__ STATIC void tryexec(char *, char **, char **); STATIC void execinterp(char **, char **); STATIC void printentry(struct tblentry *); STATIC void clearcmdentry(int); STATIC struct tblentry *cmdlookup(char *, int); STATIC void delete_cmd_entry(void); #else STATIC void tryexec(); STATIC void execinterp(); STATIC void printentry(); STATIC void clearcmdentry(); STATIC struct tblentry *cmdlookup(); STATIC void delete_cmd_entry(); #endif /* * Exec a program. Never returns. If you change this routine, you may * have to change the find_command routine as well. */ void shellexec(argv, envp, path, index) char **argv, **envp; char *path; { char *cmdname; int e; if (strchr(argv[0], '/') != NULL) { tryexec(argv[0], argv, envp); e = errno; } else { e = ENOENT; while ((cmdname = padvance(&path, argv[0])) != NULL) { if (--index < 0 && pathopt == NULL) { tryexec(cmdname, argv, envp); if (errno != ENOENT && errno != ENOTDIR) e = errno; } stunalloc(cmdname); } } error2(argv[0], errmsg(e, E_EXEC)); } STATIC void tryexec(cmd, argv, envp) char *cmd; char **argv; char **envp; { int e; char *p; #ifdef SYSV do { execve(cmd, argv, envp); } while (errno == EINTR); #else execve(cmd, argv, envp); #endif e = errno; if (e == ENOEXEC) { initshellproc(); setinputfile(cmd, 0); commandname = arg0 = savestr(argv[0]); #ifndef BSD pgetc(); pungetc(); /* fill up input buffer */ p = parsenextc; if (parsenleft > 2 && p[0] == '#' && p[1] == '!') { argv[0] = cmd; execinterp(argv, envp); } #endif setparam(argv + 1); raise(EXSHELLPROC); /*NOTREACHED*/ } errno = e; } #ifndef BSD /* * Execute an interpreter introduced by "#!", for systems where this * feature has not been built into the kernel. If the interpreter is * the shell, return (effectively ignoring the "#!"). If the execution * of the interpreter fails, exit. * * This code peeks inside the input buffer in order to avoid actually * reading any input. It would benefit from a rewrite. */ #define NEWARGS 5 STATIC void execinterp(argv, envp) char **argv, **envp; { int n; char *inp; char *outp; char c; char *p; char **ap; char *newargs[NEWARGS]; int i; char **ap2; char **new; n = parsenleft - 2; inp = parsenextc + 2; ap = newargs; for (;;) { while (--n >= 0 && (*inp == ' ' || *inp == '\t')) inp++; if (n < 0) goto bad; if ((c = *inp++) == '\n') break; if (ap == &newargs[NEWARGS]) bad: error("Bad #! line"); STARTSTACKSTR(outp); do { STPUTC(c, outp); } while (--n >= 0 && (c = *inp++) != ' ' && c != '\t' && c != '\n'); STPUTC('\0', outp); n++, inp--; *ap++ = grabstackstr(outp); } if (ap == newargs + 1) { /* if no args, maybe no exec is needed */ p = newargs[0]; for (;;) { if (equal(p, "sh") || equal(p, "ash")) { return; } while (*p != '/') { if (*p == '\0') goto break2; p++; } p++; } break2:; } i = (char *)ap - (char *)newargs; /* size in bytes */ if (i == 0) error("Bad #! line"); for (ap2 = argv ; *ap2++ != NULL ; ); new = ckmalloc(i + ((char *)ap2 - (char *)argv)); ap = newargs, ap2 = new; while ((i -= sizeof (char **)) >= 0) *ap2++ = *ap++; ap = argv; while (*ap2++ = *ap++); shellexec(new, envp, pathval(), 0); } #endif /* * Do a path search. The variable path (passed by reference) should be * set to the start of the path before the first call; padvance will update * this value as it proceeds. Successive calls to padvance will return * the possible path expansions in sequence. If an option (indicated by * a percent sign) appears in the path entry then the global variable * pathopt will be set to point to it; otherwise pathopt will be set to * NULL. */ char *pathopt; char * padvance(path, name) char **path; char *name; { register char *p, *q; char *start; int len; if (*path == NULL) return NULL; start = *path; for (p = start ; *p && *p != ':' && *p != '%' ; p++); len = p - start + strlen(name) + 2; /* "2" is for '/' and '\0' */ while (stackblocksize() < len) growstackblock(); q = stackblock(); if (p != start) { bcopy(start, q, p - start); q += p - start; *q++ = '/'; } strcpy(q, name); pathopt = NULL; if (*p == '%') { pathopt = ++p; while (*p && *p != ':') p++; } if (*p == ':') *path = p + 1; else *path = NULL; return stalloc(len); } /*** Command hashing code ***/ hashcmd(argc, argv) char **argv; { struct tblentry **pp; struct tblentry *cmdp; int c; int verbose; struct cmdentry entry; char *name; if (argc <= 1) { for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) { for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { printentry(cmdp); } } return 0; } verbose = 0; while ((c = nextopt("rv")) != '\0') { if (c == 'r') { clearcmdentry(0); } else if (c == 'v') { verbose++; } } while ((name = *argptr) != NULL) { if ((cmdp = cmdlookup(name, 0)) != NULL && (cmdp->cmdtype == CMDNORMAL || cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0)) delete_cmd_entry(); find_command(name, &entry, 1); if (verbose) { if (entry.cmdtype != CMDUNKNOWN) { /* if no error msg */ cmdp = cmdlookup(name, 0); printentry(cmdp); } flushall(); } argptr++; } return 0; } STATIC void printentry(cmdp) struct tblentry *cmdp; { int index; char *path; char *name; if (cmdp->cmdtype == CMDNORMAL) { index = cmdp->param.index; path = pathval(); do { name = padvance(&path, cmdp->cmdname); stunalloc(name); } while (--index >= 0); out1str(name); } else if (cmdp->cmdtype == CMDBUILTIN) { out1fmt("builtin %s", cmdp->cmdname); } else if (cmdp->cmdtype == CMDFUNCTION) { out1fmt("function %s", cmdp->cmdname); #ifdef DEBUG } else { error("internal error: cmdtype %d", cmdp->cmdtype); #endif } if (cmdp->rehash) out1c('*'); out1c('\n'); } /* * Resolve a command name. If you change this routine, you may have to * change the shellexec routine as well. */ void find_command(name, entry, printerr) char *name; struct cmdentry *entry; { struct tblentry *cmdp; int index; int prev; char *path; char *fullname; struct stat statb; int e; int i; /* If name contains a slash, don't use the hash table */ if (strchr(name, '/') != NULL) { entry->cmdtype = CMDNORMAL; entry->u.index = 0; return; } /* If name is in the table, and not invalidated by cd, we're done */ if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->rehash == 0) goto success; /* If %builtin not in path, check for builtin next */ if (builtinloc < 0 && (i = find_builtin(name)) >= 0) { INTOFF; cmdp = cmdlookup(name, 1); cmdp->cmdtype = CMDBUILTIN; cmdp->param.index = i; INTON; goto success; } /* We have to search path. */ prev = -1; /* where to start */ if (cmdp) { /* doing a rehash */ if (cmdp->cmdtype == CMDBUILTIN) prev = builtinloc; else prev = cmdp->param.index; } path = pathval(); e = ENOENT; index = -1; loop: while ((fullname = padvance(&path, name)) != NULL) { stunalloc(fullname); index++; if (pathopt) { if (prefix("builtin", pathopt)) { if ((i = find_builtin(name)) < 0) goto loop; INTOFF; cmdp = cmdlookup(name, 1); cmdp->cmdtype = CMDBUILTIN; cmdp->param.index = i; INTON; goto success; } else if (prefix("func", pathopt)) { /* handled below */ } else { goto loop; /* ignore unimplemented options */ } } /* if rehash, don't redo absolute path names */ if (fullname[0] == '/' && index <= prev) { if (index < prev) goto loop; TRACE(("searchexec \"%s\": no change\n", name)); goto success; } while (stat(fullname, &statb) < 0) { #ifdef SYSV if (errno == EINTR) continue; #endif if (errno != ENOENT && errno != ENOTDIR) e = errno; goto loop; } e = EACCES; /* if we fail, this will be the error */ if ((statb.st_mode & S_IFMT) != S_IFREG) goto loop; if (pathopt) { /* this is a %func directory */ stalloc(strlen(fullname) + 1); readcmdfile(fullname); if ((cmdp = cmdlookup(name, 0)) == NULL || cmdp->cmdtype != CMDFUNCTION) error("%s not defined in %s", name, fullname); stunalloc(fullname); goto success; } if (statb.st_uid == geteuid()) { if ((statb.st_mode & 0100) == 0) goto loop; } else if (statb.st_gid == getegid()) { if ((statb.st_mode & 010) == 0) goto loop; } else { if ((statb.st_mode & 01) == 0) goto loop; } TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname)); INTOFF; cmdp = cmdlookup(name, 1); cmdp->cmdtype = CMDNORMAL; cmdp->param.index = index; INTON; goto success; } /* We failed. If there was an entry for this command, delete it */ if (cmdp) delete_cmd_entry(); if (printerr) outfmt(out2, "%s: %s\n", name, errmsg(e, E_EXEC)); entry->cmdtype = CMDUNKNOWN; return; success: cmdp->rehash = 0; entry->cmdtype = cmdp->cmdtype; entry->u = cmdp->param; } /* * Search the table of builtin commands. */ int find_builtin(name) char *name; { const register struct builtincmd *bp; for (bp = builtincmd ; bp->name ; bp++) { if (*bp->name == *name && equal(bp->name, name)) return bp->code; } return -1; } /* * Called when a cd is done. Marks all commands so the next time they * are executed they will be rehashed. */ void hashcd() { struct tblentry **pp; struct tblentry *cmdp; for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) { for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { if (cmdp->cmdtype == CMDNORMAL || cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0) cmdp->rehash = 1; } } } /* * Called before PATH is changed. The argument is the new value of PATH; * pathval() still returns the old value at this point. Called with * interrupts off. */ void changepath(newval) char *newval; { char *old, *new; int index; int firstchange; int bltin; old = pathval(); new = newval; firstchange = 9999; /* assume no change */ index = 0; bltin = -1; for (;;) { if (*old != *new) { firstchange = index; if (*old == '\0' && *new == ':' || *old == ':' && *new == '\0') firstchange++; old = new; /* ignore subsequent differences */ } if (*new == '\0') break; if (*new == '%' && bltin < 0 && prefix("builtin", new + 1)) bltin = index; if (*new == ':') { index++; } new++, old++; } if (builtinloc < 0 && bltin >= 0) builtinloc = bltin; /* zap builtins */ if (builtinloc >= 0 && bltin < 0) firstchange = 0; clearcmdentry(firstchange); builtinloc = bltin; } /* * Clear out command entries. The argument specifies the first entry in * PATH which has changed. */ STATIC void clearcmdentry(firstchange) { struct tblentry **tblp; struct tblentry **pp; struct tblentry *cmdp; INTOFF; for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) { pp = tblp; while ((cmdp = *pp) != NULL) { if (cmdp->cmdtype == CMDNORMAL && cmdp->param.index >= firstchange || cmdp->cmdtype == CMDBUILTIN && builtinloc >= firstchange) { *pp = cmdp->next; ckfree(cmdp); } else { pp = &cmdp->next; } } } INTON; } /* * Delete all functions. */ #ifdef mkinit MKINIT void deletefuncs(); SHELLPROC { deletefuncs(); } #endif void deletefuncs() { struct tblentry **tblp; struct tblentry **pp; struct tblentry *cmdp; INTOFF; for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) { pp = tblp; while ((cmdp = *pp) != NULL) { if (cmdp->cmdtype == CMDFUNCTION) { *pp = cmdp->next; freefunc(cmdp->param.func); ckfree(cmdp); } else { pp = &cmdp->next; } } } INTON; } /* * Locate a command in the command hash table. If "add" is nonzero, * add the command to the table if it is not already present. The * variable "lastcmdentry" is set to point to the address of the link * pointing to the entry, so that delete_cmd_entry can delete the * entry. */ struct tblentry **lastcmdentry; STATIC struct tblentry * cmdlookup(name, add) char *name; { int hashval; register char *p; struct tblentry *cmdp; struct tblentry **pp; p = name; hashval = *p << 4; while (*p) hashval += *p++; hashval &= 0x7FFF; pp = &cmdtable[hashval % CMDTABLESIZE]; for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { if (equal(cmdp->cmdname, name)) break; pp = &cmdp->next; } if (add && cmdp == NULL) { INTOFF; cmdp = *pp = ckmalloc(sizeof (struct tblentry) - ARB + strlen(name) + 1); cmdp->next = NULL; cmdp->cmdtype = CMDUNKNOWN; cmdp->rehash = 0; strcpy(cmdp->cmdname, name); INTON; } lastcmdentry = pp; return cmdp; } /* * Delete the command entry returned on the last lookup. */ STATIC void delete_cmd_entry() { struct tblentry *cmdp; INTOFF; cmdp = *lastcmdentry; *lastcmdentry = cmdp->next; ckfree(cmdp); INTON; } #ifdef notdef void getcmdentry(name, entry) char *name; struct cmdentry *entry; { struct tblentry *cmdp = cmdlookup(name, 0); if (cmdp) { entry->u = cmdp->param; entry->cmdtype = cmdp->cmdtype; } else { entry->cmdtype = CMDUNKNOWN; entry->u.index = 0; } } #endif /* * Add a new command entry, replacing any existing command entry for * the same name. */ void addcmdentry(name, entry) char *name; struct cmdentry *entry; { struct tblentry *cmdp; INTOFF; cmdp = cmdlookup(name, 1); if (cmdp->cmdtype == CMDFUNCTION) { freefunc(cmdp->param.func); } cmdp->cmdtype = entry->cmdtype; cmdp->param = entry->u; INTON; } /* * Define a shell function. */ void defun(name, func) char *name; union node *func; { struct cmdentry entry; INTOFF; entry.cmdtype = CMDFUNCTION; entry.u.func = copyfunc(func); addcmdentry(name, &entry); INTON; } /* * Delete a function if it exists. */ void unsetfunc(name) char *name; { struct tblentry *cmdp; if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->cmdtype == CMDFUNCTION) { freefunc(cmdp->param.func); delete_cmd_entry(); } } EOF if test `wc -c < exec.c` -ne 16808 then echo 'exec.c is the wrong size' fi echo extracting expand.h cat > expand.h <<\EOF /* * Copyright (C) 1989 by Kenneth Almquist. All rights reserved. * This file is part of ash, which is distributed under the terms specified * by the Ash General Public License. See the file named LICENSE. */ struct strlist { struct strlist *next; char *text; }; struct arglist { struct strlist *list; struct strlist **lastp; }; #ifdef __STDC__ void expandarg(union node *, struct arglist *, int); void expandhere(union node *, int); int patmatch(char *, char *); void rmescapes(char *); int casematch(union node *, char *); #else void expandarg(); void expandhere(); int patmatch(); void rmescapes(); int casematch(); #endif EOF if test `wc -c < expand.h` -ne 662 then echo 'expand.h is the wrong size' fi echo extracting expand.c cat > expand.c <<\EOF /* * Routines to expand arguments to commands. We have to deal with * backquotes, shell variables, and file metacharacters. * * Copyright (C) 1989 by Kenneth Almquist. All rights reserved. * This file is part of ash, which is distributed under the terms specified * by the Ash General Public License. See the file named LICENSE. */ #include "shell.h" #include "main.h" #include "nodes.h" #include "eval.h" #include "expand.h" #include "syntax.h" #include "parser.h" #include "jobs.h" #include "options.h" #include "var.h" #include "input.h" #include "output.h" #include "memalloc.h" #include "error.h" #include "mystring.h" #include <sys/types.h> #include <sys/stat.h> #include "mydirent.h" #include "myerrno.h" /* * Structure specifying which parts of the string should be searched * for IFS characters. */ struct ifsregion { struct ifsregion *next; /* next region in list */ int begoff; /* offset of start of region */ int endoff; /* offset of end of region */ int nulonly; /* search for nul bytes only */ }; char *expdest; /* output of current string */ struct nodelist *argbackq; /* list of back quote expressions */ struct ifsregion ifsfirst; /* first struct in list of ifs regions */ struct ifsregion *ifslastp; /* last struct in list */ struct arglist exparg; /* holds expanded arg list */ #if UDIR /* * Set if the last argument processed had /u/logname expanded. This * variable is read by the cd command. */ int didudir; #endif #ifdef __STDC__ STATIC void argstr(char *, int); STATIC void expbackq(union node *, int, int); STATIC char *evalvar(char *, int); STATIC int varisset(int); STATIC void varvalue(int, int, int); STATIC void recordregion(int, int, int); STATIC void ifsbreakup(char *, struct arglist *); STATIC void expandmeta(struct strlist *); STATIC void expmeta(char *, char *); STATIC void addfname(char *); STATIC struct strlist *expsort(struct strlist *); STATIC struct strlist *msort(struct strlist *, int); STATIC int pmatch(char *, char *); #else STATIC void argstr(); STATIC void expbackq(); STATIC char *evalvar(); STATIC int varisset(); STATIC void varvalue(); STATIC void recordregion(); STATIC void ifsbreakup(); STATIC void expandmeta(); STATIC void expmeta(); STATIC void addfname(); STATIC struct strlist *expsort(); STATIC struct strlist *msort(); STATIC int pmatch(); #endif #if UDIR #ifdef __STDC__ STATIC char *expudir(char *); #else STATIC char *expudir(); #endif #endif /* UDIR */ /* * Expand shell variables and backquotes inside a here document. */ void expandhere(arg, fd) union node *arg; /* the document */ int fd; /* where to write the expanded version */ { herefd = fd; expandarg(arg, (struct arglist *)NULL, 0); xwrite(fd, stackblock(), expdest - stackblock()); } /* * Perform variable substitution and command substitution on an argument, * placing the resulting list of arguments in arglist. If full is true, * perform splitting and file name expansion. When arglist is NULL, perform * here document expansion. */ void expandarg(arg, arglist, full) union node *arg; struct arglist *arglist; { struct strlist *sp; char *p; #if UDIR didudir = 0; #endif argbackq = arg->narg.backquote; STARTSTACKSTR(expdest); ifsfirst.next = NULL; ifslastp = NULL; argstr(arg->narg.text, full); if (arglist == NULL) return; /* here document expanded */ STPUTC('\0', expdest); p = grabstackstr(expdest); exparg.lastp = &exparg.list; if (full) { ifsbreakup(p, &exparg); *exparg.lastp = NULL; exparg.lastp = &exparg.list; expandmeta(exparg.list); } else { sp = (struct strlist *)stalloc(sizeof (struct strlist)); sp->text = p; *exparg.lastp = sp; exparg.lastp = &sp->next; } while (ifsfirst.next != NULL) { struct ifsregion *ifsp; INTOFF; ifsp = ifsfirst.next->next; ckfree(ifsfirst.next); ifsfirst.next = ifsp; INTON; } *exparg.lastp = NULL; if (exparg.list) { *arglist->lastp = exparg.list; arglist->lastp = exparg.lastp; } } /* * Perform variable and command substitution. If full is set, output CTLESC * characters to allow for further processing. If full is not set, treat * $@ like $* since no splitting will be performed. */ STATIC void argstr(p, full) register char *p; { char c; for (;;) { switch (c = *p++) { case '\0': case CTLENDVAR: goto breakloop; case CTLESC: if (full) STPUTC(c, expdest); c = *p++; STPUTC(c, expdest); break; case CTLVAR: p = evalvar(p, full); break; case CTLBACKQ: case CTLBACKQ|CTLQUOTE: expbackq(argbackq->n, c & CTLQUOTE, full); argbackq = argbackq->next; break; default: STPUTC(c, expdest); } } breakloop:; } /* * Expand stuff in backwards quotes. */ STATIC void expbackq(cmd, quoted, full) union node *cmd; { struct backcmd in; int i; char buf[128]; char *p; char *dest = expdest; struct ifsregion saveifs, *savelastp; struct nodelist *saveargbackq; char lastc; int startloc = dest - stackblock(); char const *syntax = quoted? DQSYNTAX : BASESYNTAX; int saveherefd; INTOFF; saveifs = ifsfirst; savelastp = ifslastp; saveargbackq = argbackq; saveherefd = herefd; herefd = -1; p = grabstackstr(dest); evalbackcmd(cmd, &in); ungrabstackstr(p, dest); ifsfirst = saveifs; ifslastp = savelastp; argbackq = saveargbackq; herefd = saveherefd; p = in.buf; lastc = '\0'; for (;;) { if (--in.nleft < 0) { if (in.fd < 0) break; while ((i = read(in.fd, buf, sizeof buf)) < 0 && errno == EINTR); TRACE(("expbackq: read returns %d\n", i)); if (i <= 0) break; p = buf; in.nleft = i - 1; } lastc = *p++; if (lastc != '\0') { if (full && syntax[lastc] == CCTL) STPUTC(CTLESC, dest); STPUTC(lastc, dest); } } if (lastc == '\n') { STUNPUTC(dest); } if (in.fd >= 0) close(in.fd); if (in.buf) ckfree(in.buf); if (in.jp) waitforjob(in.jp); if (quoted == 0) recordregion(startloc, dest - stackblock(), 0); TRACE(("evalbackq: size=%d: \"%.*s\"\n", (dest - stackblock()) - startloc, (dest - stackblock()) - startloc, stackblock() + startloc)); expdest = dest; INTON; } /* * Expand a variable, and return a pointer to the next character in the * input string. */ STATIC char * evalvar(p, full) char *p; { int subtype; int flags; char *var; char *val; int c; int set; int special; int startloc; flags = *p++; subtype = flags & VSTYPE; var = p; special = 0; if (! is_name(*p)) special = 1; p = strchr(p, '=') + 1; again: /* jump here after setting a variable with ${var=text} */ if (special) { set = varisset(*var); val = NULL; } else { val = lookupvar(var); if (val == NULL || (flags & VSNUL) && val[0] == '\0') { val = NULL; set = 0; } else set = 1; } startloc = expdest - stackblock(); if (set && subtype != VSPLUS) { /* insert the value of the variable */ if (special) { varvalue(*var, flags & VSQUOTE, full); } else { char const *syntax = (flags & VSQUOTE)? DQSYNTAX : BASESYNTAX; while (*val) { if (full && syntax[*val] == CCTL) STPUTC(CTLESC, expdest); STPUTC(*val++, expdest); } } } if (subtype == VSPLUS) set = ! set; if (((flags & VSQUOTE) == 0 || (*var == '@' && shellparam.nparam != 1)) && (set || subtype == VSNORMAL)) recordregion(startloc, expdest - stackblock(), flags & VSQUOTE); if (! set && subtype != VSNORMAL) { if (subtype == VSPLUS || subtype == VSMINUS) { argstr(p, full); } else { char *startp; int saveherefd = herefd; herefd = -1; argstr(p, 0); STACKSTRNUL(expdest); herefd = saveherefd; startp = stackblock() + startloc; if (subtype == VSASSIGN) { setvar(var, startp, 0); STADJUST(startp - expdest, expdest); flags &=~ VSNUL; goto again; } /* subtype == VSQUESTION */ if (*p != CTLENDVAR) { outfmt(&errout, "%s\n", startp); error((char *)NULL); } error("%.*s: parameter %snot set", p - var - 1, var, (flags & VSNUL)? "null or " : nullstr); } } if (subtype != VSNORMAL) { /* skip to end of alternative */ int nesting = 1; for (;;) { if ((c = *p++) == CTLESC) p++; else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) { if (set) argbackq = argbackq->next; } else if (c == CTLVAR) { if ((*p++ & VSTYPE) != VSNORMAL) nesting++; } else if (c == CTLENDVAR) { if (--nesting == 0) break; } } } return p; } /* * Test whether a specialized variable is set. */ STATIC int varisset(name) char name; { char **ap; if (name == '!') { if (backgndpid == -1) return 0; } else if (name == '@' || name == '*') { if (*shellparam.p == NULL) return 0; } else if ((unsigned)(name -= '1') <= '9' - '1') { ap = shellparam.p; do { if (*ap++ == NULL) return 0; } while (--name >= 0); } return 1; } /* * Add the value of a specialized variable to the stack string. */ STATIC void varvalue(name, quoted, allow_split) char name; { int num; char temp[32]; char *p; int i; extern int exitstatus; char sep; char **ap; char const *syntax; switch (name) { case '$': num = rootpid; goto numvar; case '?': num = exitstatus; goto numvar; case '#': num = shellparam.nparam; goto numvar; case '!': num = backgndpid; numvar: p = temp + 31; temp[31] = '\0'; do { *--p = num % 10 + '0'; } while ((num /= 10) != 0); while (*p) STPUTC(*p++, expdest); break; case '-': for (i = 0 ; optchar[i] ; i++) { if (optval[i]) STPUTC(optchar[i], expdest); } break; case '@': if (allow_split) { sep = '\0'; goto allargs; } /* fall through */ case '*': sep = ' '; allargs: syntax = quoted? DQSYNTAX : BASESYNTAX; for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { /* should insert CTLESC characters */ while (*p) { if (syntax[*p] == CCTL) STPUTC(CTLESC, expdest); STPUTC(*p++, expdest); } if (*ap) STPUTC(sep, expdest); } break; case '0': p = arg0; string: syntax = quoted? DQSYNTAX : BASESYNTAX; while (*p) { if (syntax[*p] == CCTL) STPUTC(CTLESC, expdest); STPUTC(*p++, expdest); } break; default: if ((unsigned)(name -= '1') <= '9' - '1') { p = shellparam.p[name]; goto string; } break; } } /* * Record the the fact that we have to scan this region of the * string for IFS characters. */ STATIC void recordregion(start, end, nulonly) { register struct ifsregion *ifsp; if (ifslastp == NULL) { ifsp = &ifsfirst; } else { ifsp = (struct ifsregion *)ckmalloc(sizeof (struct ifsregion)); ifslastp->next = ifsp; } ifslastp = ifsp; ifslastp->next = NULL; ifslastp->begoff = start; ifslastp->endoff = end; ifslastp->nulonly = nulonly; } /* * Break the argument string into pieces based upon IFS and add the * strings to the argument list. The regions of the string to be * searched for IFS characters have been stored by recordregion. */ STATIC void ifsbreakup(string, arglist) char *string; struct arglist *arglist; { struct ifsregion *ifsp; struct strlist *sp; char *start; register char *p; char *q; char *ifs; start = string; if (ifslastp != NULL) { ifsp = &ifsfirst; do { p = string + ifsp->begoff; ifs = ifsp->nulonly? nullstr : ifsval(); while (p < string + ifsp->endoff) { q = p; if (*p == CTLESC) p++; if (strchr(ifs, *p++)) { if (q > start || *ifs != ' ') { *q = '\0'; sp = (struct strlist *)stalloc(sizeof *sp); sp->text = start; *arglist->lastp = sp; arglist->lastp = &sp->next; } if (*ifs == ' ') { for (;;) { if (p >= string + ifsp->endoff) break; q = p; if (*p == CTLESC) p++; if (strchr(ifs, *p++) == NULL) { p = q; break; } } } start = p; } } } while ((ifsp = ifsp->next) != NULL); if (*start || (*ifs != ' ' && start > string)) { sp = (struct strlist *)stalloc(sizeof *sp); sp->text = start; *arglist->lastp = sp; arglist->lastp = &sp->next; } } else { sp = (struct strlist *)stalloc(sizeof *sp); sp->text = start; *arglist->lastp = sp; arglist->lastp = &sp->next; } } /* * Expand shell metacharacters. At this point, the only control characters * should be escapes. The results are stored in the list exparg. */ char *expdir; STATIC void expandmeta(str) struct strlist *str; { char *p; struct strlist **savelastp; struct strlist *sp; char c; while (str) { if (fflag) goto nometa; p = str->text; #if UDIR if (p[0] == '/' && p[1] == 'u' && p[2] == '/') str->text = p = expudir(p); #endif for (;;) { /* fast check for meta chars */ if ((c = *p++) == '\0') goto nometa; if (c == '*' || c == '?' || c == '[' || c == '!') break; } savelastp = exparg.lastp; INTOFF; if (expdir == NULL) expdir = ckmalloc(1024); /* I hope this is big enough */ expmeta(expdir, str->text); ckfree(expdir); expdir = NULL; INTON; if (exparg.lastp == savelastp) { if (! zflag) { nometa: *exparg.lastp = str; rmescapes(str->text); exparg.lastp = &str->next; } } else { *exparg.lastp = NULL; *savelastp = sp = expsort(*savelastp); while (sp->next != NULL) sp = sp->next; exparg.lastp = &sp->next; } str = str->next; } } #if UDIR /* * Expand /u/username into the home directory for the specified user. * We could use the getpw stuff here, but then we would have to load * in stdio and who knows what else. */ #define MAXLOGNAME 32 #define MAXPWLINE 128 char *pfgets(); STATIC char * expudir(path) char *path; { register char *p, *q, *r; char name[MAXLOGNAME]; char line[MAXPWLINE]; int i; r = path; /* result on failure */ p = r + 3; /* the 3 skips "/u/" */ q = name; while (*p && *p != '/') { if (q >= name + MAXLOGNAME - 1) return r; /* fail, name too long */ *q++ = *p++; } *q = '\0'; setinputfile("/etc/passwd", 1); q = line + strlen(name); while (pfgets(line, MAXPWLINE) != NULL) { if (line[0] == name[0] && prefix(name, line) && *q == ':') { /* skip to start of home directory */ i = 4; do { while (*++q && *q != ':'); } while (--i > 0); if (*q == '\0') break; /* fail, corrupted /etc/passwd */ q++; for (r = q ; *r && *r != '\n' && *r != ':' ; r++); *r = '\0'; /* nul terminate home directory */ i = r - q; /* i = strlen(q) */ r = stalloc(i + strlen(p) + 1); scopy(q, r); scopy(p, r + i); TRACE(("expudir converts %s to %s\n", path, r)); didudir = 1; path = r; /* succeed */ break; } } popfile(); return r; } #endif /* * Do metacharacter (i.e. *, ?, [...]) expansion. */ STATIC void expmeta(enddir, name) char *enddir; char *name; { register char *p; char *q; char *start; char *endname; int metaflag; struct stat statb; DIR *dirp; struct dirent *dp; int atend; int matchdot; metaflag = 0; start = name; for (p = name ; ; p++) { if (*p == '*' || *p == '?') metaflag = 1; else if (*p == '[') { q = p + 1; if (*q == '!') q++; for (;;) { if (*q == CTLESC) q++; if (*q == '/' || *q == '\0') break; if (*++q == ']') { metaflag = 1; break; } } } else if (*p == '!' && p[1] == '!' && (p == name || p[-1] == '/')) { metaflag = 1; } else if (*p == '\0') break; else if (*p == CTLESC) p++; if (*p == '/') { if (metaflag) break; start = p + 1; } } if (metaflag == 0) { /* we've reached the end of the file name */ if (enddir != expdir) metaflag++; for (p = name ; ; p++) { if (*p == CTLESC) p++; *enddir++ = *p; if (*p == '\0') break; } if (metaflag == 0 || stat(expdir, &statb) >= 0) addfname(expdir); return; } endname = p; if (start != name) { p = name; while (p < start) { if (*p == CTLESC) p++; *enddir++ = *p++; } } if (enddir == expdir) { p = "."; } else if (enddir == expdir + 1 && *expdir == '/') { p = "/"; } else { p = expdir; enddir[-1] = '\0'; } if ((dirp = opendir(p)) == NULL) return; if (enddir != expdir) enddir[-1] = '/'; if (*endname == 0) { atend = 1; } else { atend = 0; *endname++ = '\0'; } matchdot = 0; if (start[0] == '.' || start[0] == CTLESC && start[1] == '.') matchdot++; while (! int_pending() && (dp = readdir(dirp)) != NULL) { if (dp->d_name[0] == '.' && ! matchdot) continue; if (patmatch(start, dp->d_name)) { if (atend) { scopy(dp->d_name, enddir); addfname(expdir); } else { char *q; for (p = enddir, q = dp->d_name ; *p++ = *q++ ;); p[-1] = '/'; expmeta(p, endname); } } } closedir(dirp); if (! atend) endname[-1] = '/'; } /* * Add a file name to the list. */ STATIC void addfname(name) char *name; { char *p; struct strlist *sp; p = stalloc(strlen(name) + 1); scopy(name, p); sp = (struct strlist *)stalloc(sizeof *sp); sp->text = p; *exparg.lastp = sp; exparg.lastp = &sp->next; } /* * Sort the results of file name expansion. It calculates the number of * strings to sort and then calls msort (short for merge sort) to do the * work. */ STATIC struct strlist * expsort(str) struct strlist *str; { int len; struct strlist *sp; len = 0; for (sp = str ; sp ; sp = sp->next) len++; return msort(str, len); } STATIC struct strlist * msort(list, len) struct strlist *list; { struct strlist *p, *q; struct strlist **lpp; int half; int n; if (len <= 1) return list; half = len >> 1; p = list; for (n = half ; --n >= 0 ; ) { q = p; p = p->next; } q->next = NULL; /* terminate first half of list */ q = msort(list, half); /* sort first half of list */ p = msort(p, len - half); /* sort second half */ lpp = &list; for (;;) { if (strcmp(p->text, q->text) < 0) { *lpp = p; lpp = &p->next; if ((p = *lpp) == NULL) { *lpp = q; break; } } else { *lpp = q; lpp = &q->next; if ((q = *lpp) == NULL) { *lpp = p; break; } } } return list; } /* * Returns true if the pattern matches the string. */ int patmatch(pattern, string) char *pattern; char *string; { if (pattern[0] == '!' && pattern[1] == '!') return 1 - pmatch(pattern + 2, string); else return pmatch(pattern, string); } STATIC int pmatch(pattern, string) char *pattern; char *string; { register char *p, *q; register char c; p = pattern; q = string; for (;;) { switch (c = *p++) { case '\0': goto breakloop; case CTLESC: if (*q++ != *p++) return 0; break; case '?': if (*q++ == '\0') return 0; break; case '*': c = *p; if (c != CTLESC && c != '?' && c != '*' && c != '[') { while (*q != c) { if (*q == '\0') return 0; q++; } } do { if (pmatch(p, q)) return 1; } while (*q++ != '\0'); return 0; case '[': { char *endp; int invert, found; char chr; endp = p; if (*endp == '!') endp++; for (;;) { if (*endp == '\0') goto dft; /* no matching ] */ if (*endp == CTLESC) endp++; if (*++endp == ']') break; } invert = 0; if (*p == '!') { invert++; p++; } found = 0; chr = *q++; c = *p++; do { if (c == CTLESC) c = *p++; if (*p == '-' && p[1] != ']') { p++; if (*p == CTLESC) p++; if (chr >= c && chr <= *p) found = 1; p++; } else { if (chr == c) found = 1; } } while ((c = *p++) != ']'); if (found == invert) return 0; break; } dft: default: if (*q++ != c) return 0; break; } } breakloop: if (*q != '\0') return 0; return 1; } /* * Remove any CTLESC characters from a string. */ void rmescapes(str) char *str; { register char *p, *q; p = str; while (*p != CTLESC) { if (*p++ == '\0') return; } q = p; while (*p) { if (*p == CTLESC) p++; *q++ = *p++; } *q = '\0'; } /* * See if a pattern matches in a case statement. */ int casematch(pattern, val) union node *pattern; char *val; { struct stackmark smark; int result; char *p; setstackmark(&smark); argbackq = pattern->narg.backquote; STARTSTACKSTR(expdest); ifslastp = NULL; argstr(pattern->narg.text, 0); STPUTC('\0', expdest); p = grabstackstr(expdest); result = patmatch(p, val); popstackmark(&smark); return result; } EOF if test `wc -c < expand.c` -ne 22554 then echo 'expand.c is the wrong size' fi echo extracting init.h cat > init.h <<\EOF /* * Copyright (C) 1989 by Kenneth Almquist. All rights reserved. * This file is part of ash, which is distributed under the terms specified * by the Ash General Public License. See the file named LICENSE. */ #ifdef __STDC__ void init(void); void reset(void); void initshellproc(void); #else void init(); void reset(); void initshellproc(); #endif EOF if test `wc -c < init.h` -ne 355 then echo 'init.h is the wrong size' fi echo extracting input.h cat > input.h <<\EOF /* * Copyright (C) 1989 by Kenneth Almquist. All rights reserved. * This file is part of ash, which is distributed under the terms specified * by the Ash General Public License. See the file named LICENSE. */ /* PEOF (the end of file marker) is defined in syntax.h */ /* * The input line number. Input.c just defines this variable, and saves * and restores it when files are pushed and popped. The user of this * package must set its value. */ extern int plinno; extern int parsenleft; /* number of characters left in input buffer */ extern char *parsenextc; /* next character in input buffer */ #ifdef __STDC__ char *pfgets(char *, int); int pgetc(void); int preadbuffer(void); void pungetc(void); void ppushback(char *, int); void setinputfile(char *, int); void setinputfd(int, int); void setinputstring(char *, int); void popfile(void); void popallfiles(void); void closescript(void); #else char *pfgets(); int pgetc(); int preadbuffer(); void pungetc(); void ppushback(); void setinputfile(); void setinputfd(); void setinputstring(); void popfile(); void popallfiles(); void closescript(); #endif #define pgetc_macro() (--parsenleft >= 0? *parsenextc++ : preadbuffer()) EOF if test `wc -c < input.h` -ne 1194 then echo 'input.h is the wrong size' fi echo extracting input.c cat > input.c <<\EOF /* * Copyright (C) 1989 by Kenneth Almquist. All rights reserved. * This file is part of ash, which is distributed under the terms specified * by the Ash General Public License. See the file named LICENSE. * * This file implements the input routines used by the parser. */ #include <stdio.h> /* defines BUFSIZ */ #include "shell.h" #include "syntax.h" #include "input.h" #include "output.h" #include "memalloc.h" #include "error.h" #include <fcntl.h> #include "myerrno.h" #define EOF_NLEFT -99 /* value of parsenleft when EOF pushed back */ /* * The parsefile structure pointed to by the global variable parsefile * contains information about the current file being read. */ MKINIT struct parsefile { int linno; /* current line */ int fd; /* file descriptor (or -1 if string) */ int nleft; /* number of chars left in buffer */ char *nextc; /* next char in buffer */ struct parsefile *prev; /* preceding file on stack */ char *buf; /* input buffer */ }; int plinno = 1; /* input line number */ MKINIT int parsenleft; /* copy of parsefile->nleft */ char *parsenextc; /* copy of parsefile->nextc */ MKINIT struct parsefile basepf; /* top level input file */ char basebuf[BUFSIZ]; /* buffer for top level input file */ struct parsefile *parsefile = &basepf; /* current input file */ char *pushedstring; /* copy of parsenextc when text pushed back */ int pushednleft; /* copy of parsenleft when text pushed back */ #ifdef __STDC__ STATIC void pushfile(void); #else STATIC void pushfile(); #endif #ifdef mkinit INCLUDE "input.h" INCLUDE "error.h" INIT { extern char basebuf[]; basepf.nextc = basepf.buf = basebuf; } RESET { if (exception != EXSHELLPROC) parsenleft = 0; /* clear input buffer */ popallfiles(); } SHELLPROC { popallfiles(); } #endif /* * Read a line from the script. */ char * pfgets(line, len) char *line; { register char *p = line; int nleft = len; int c; while (--nleft > 0) { c = pgetc_macro(); if (c == PEOF) { if (p == line) return NULL; break; } *p++ = c; if (c == '\n') break; } *p = '\0'; return line; } /* * Read a character from the script, returning PEOF on end of file. * Nul characters in the input are silently discarded. */ int pgetc() { return pgetc_macro(); } /* * Refill the input buffer and return the next input character: * * 1) If a string was pushed back on the input, switch back to the regular * buffer. * 2) If an EOF was pushed back (parsenleft == EOF_NLEFT) or we are reading * from a string so we can't refill the buffer, return EOF. * 3) Call read to read in the characters. * 4) Delete all nul characters from the buffer. */ int preadbuffer() { register char *p, *q; register int i; if (pushedstring) { parsenextc = pushedstring; pushedstring = NULL; parsenleft = pushednleft; if (--parsenleft >= 0) return *parsenextc++; } if (parsenleft == EOF_NLEFT || parsefile->buf == NULL) return PEOF; flushout(&output); flushout(&errout); retry: p = parsenextc = parsefile->buf; i = read(parsefile->fd, p, BUFSIZ); if (i <= 0) { if (i < 0) { if (errno == EINTR) goto retry; #ifdef BSD if (parsefile->fd == 0 && errno == EWOULDBLOCK) { int flags = fcntl(0, F_GETFL, 0); if (flags >= 0 && flags & FNDELAY) { flags &=~ FNDELAY; if (fcntl(0, F_SETFL, flags) >= 0) { out2str("ash: turning off NDELAY mode\n"); goto retry; } } } #endif } #ifdef SYSV if (i == 0 && parsefile->fd == 0) { int flags = fcntl(0, F_GETFL, 0); if (flags >= 0 && flags & O_NDELAY) { flags &=~ O_NDELAY; if (fcntl(0, F_SETFL, flags) >= 0) { out2str("ash: turning off NDELAY mode\n"); goto retry; } } } #endif parsenleft = EOF_NLEFT; return PEOF; } parsenleft = i - 1; /* delete nul characters */ for (;;) { if (*p++ == '\0') break; if (--i <= 0) return *parsenextc++; /* no nul characters */ } q = p - 1; while (--i > 0) { if (*p != '\0') *q++ = *p; p++; } if (q == parsefile->buf) goto retry; /* buffer contained nothing but nuls */ parsenleft = q - parsefile->buf - 1; return *parsenextc++; } /* * Undo the last call to pgetc. Only one character may be pushed back. * PEOF may be pushed back. */ void pungetc() { parsenleft++; parsenextc--; } /* * Push a string back onto the input. This code doesn't work if the user * tries to push back more than one string at once. */ void ppushback(string, length) char *string; { pushedstring = parsenextc; pushednleft = parsenleft; parsenextc = string; parsenleft = length; } /* * Set the input to take input from a file. If push is set, push the * old input onto the stack first. */ void setinputfile(fname, push) char *fname; { int fd; int fd2; INTOFF; if ((fd = open(fname, O_RDONLY)) < 0) error("Can't open %s", fname); if (fd < 10) { fd2 = copyfd(fd, 10); close(fd); if (fd2 < 0) error("Out of file descriptors"); fd = fd2; } setinputfd(fd, push); INTON; } /* * Like setinputfile, but takes an open file descriptor. Call this with * interrupts off. */ void setinputfd(fd, push) { if (push) { pushfile(); parsefile->buf = ckmalloc(BUFSIZ); } if (parsefile->fd > 0) close(parsefile->fd); parsefile->fd = fd; if (parsefile->buf == NULL) parsefile->buf = ckmalloc(BUFSIZ); parsenleft = 0; plinno = 1; } /* * Like setinputfile, but takes input from a string. */ void setinputstring(string, push) char *string; { INTOFF; if (push) pushfile(); parsenextc = string; parsenleft = strlen(string); parsefile->buf = NULL; plinno = 1; INTON; } /* * To handle the "." command, a stack of input files is used. Pushfile * adds a new entry to the stack and popfile restores the previous level. */ STATIC void pushfile() { struct parsefile *pf; parsefile->nleft = parsenleft; parsefile->nextc = parsenextc; parsefile->linno = plinno; pf = (struct parsefile *)ckmalloc(sizeof (struct parsefile)); pf->prev = parsefile; pf->fd = -1; parsefile = pf; } void popfile() { struct parsefile *pf = parsefile; INTOFF; if (pf->fd >= 0) close(pf->fd); if (pf->buf) ckfree(pf->buf); parsefile = pf->prev; ckfree(pf); parsenleft = parsefile->nleft; parsenextc = parsefile->nextc; plinno = parsefile->linno; INTON; } /* * Return to top level. */ void popallfiles() { while (parsefile != &basepf) popfile(); } /* * Close the file(s) that the shell is reading commands from. Called * after a fork is done. */ void closescript() { popallfiles(); if (parsefile->fd > 0) { close(parsefile->fd); parsefile->fd = 0; } } EOF if test `wc -c < input.c` -ne 7288 then echo 'input.c is the wrong size' fi echo Archive 3 unpacked exit -- Please send comp.sources.unix-related mail to rsalz@uunet.uu.net. Use a domain-based address or give alternate paths, or you may lose out.