[comp.sources.misc] v20i012: rc - A Plan 9 shell reimplementation, Part03/04

byron@archone.tamu.edu (Byron Rakitzis) (05/22/91)

Submitted-by: Byron Rakitzis <byron@archone.tamu.edu>
Posting-number: Volume 20, Issue 12
Archive-name: rc/part03

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then feed it
# into a shell via "sh file" or similar.  To overwrite existing files,
# type "sh file -c".
# The tool that generated this appeared in the comp.sources.unix newsgroup;
# send mail to comp-sources-unix@uunet.uu.net if you want that tool.
# Contents:  CHANGES glob.c hash.c heredoc.c parse.y redir.c tree.c
#   utils.c var.c which.c
# Wrapped by kent@sparky on Wed May 22 01:21:49 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
echo If this archive is complete, you will see the following message:
echo '          "shar: End of archive 3 (of 4)."'
if test -f 'CHANGES' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'CHANGES'\"
else
  echo shar: Extracting \"'CHANGES'\" \(2395 characters\)
  sed "s/^X//" >'CHANGES' <<'END_OF_FILE'
XBugs and changes since version 0.9:
X
X-----
XA pipe with a null first command caused rc to dump core. e.g.,
X
X	|;
X-----
XWhen fifo redirection was used with builtin commands, rc would hang:
X
X	diff <{echo hi} <{echo bye}
X
Xa missing exit() was the cause of all this
X-----
XA double backquote operator was added to make inlining of ifs-type material
Xpossible; thus
X
X	`` ($ifs / :) command
X
Xis the same as
X
X	ifs=($ifs / :)
X	` command
X
Xonly without the assignment.
X-----
XAn "rc error" (e.g., trying to evaluate $()) inside a function messed up
Xrc's interactive state, i.e., it stopped printing prompts.
X
X	fn foo { echo $() }
X
Xthis was fixed by adding full-fledged exception handling, with a separate
Xexception stack
X-----
Xrc did not raise sigexit from the builtin "exit".
X-----
Xrc assumed the existence of getgroups(). Also, a typo in
Xthe getgroups() code in which.c was fixed.
X-----
Xrc now sets $0 to the name of the function being called or the file being
Xinterpreted by dot.
X-----
Xif - else statements were incorrectly exported into the environment
X-----
Xrc now has stubs for GNU readline compatability. (compile -DREADLINE)
X-----
Xrc used to use a single arena for temporary space which got clobbered if there
Xwas a command of the form:
X
X	eval foo ; bar
X
Xsince the eval would cause rc to prematurely recycle that memory. I fixed
Xthis by adding support for multiple arenas.
X-----
Xan assignment inside a compound command clobbered the global definition
Xof a variable:
X
X	foo=a
X	foo=b { stuff; foo=c; stuff }
X
X$foo was set to () after this.
X-----
Xrc -e now is much more sh-like. In particular
X
X	false || echo bletch
X
Xnow does the right thing.
X-----
Xrc did not print $prompt(2) while scanning heredocs
X-----
Xexec with a redirection but without other arguments now does the
Xsame thing as sh, i.e., it alters rc's own file descriptors
X-----
Xthe command
X
X	eval eval eval [200 times or so] echo hi
X
Xwould cause rc to allocate obnoxious amounts of memory. This has
Xbeen cleaned up.
X-----
Xrc how has full support for *all* signals, not just sigquit, sigint
Xand sighup and sigterm. Additionally, the signal code is generated via
Xan awk script operating on /usr/include/signal.h, so that rc "learns"
Xabout the signals of a particular machine at compile time.
X-----
Xrc now supports "fn prompt", a function that gets executed before
Xeach $prompt(1) is printed.
X-----
X~ () '' incorrectly returned a true status.
X-----
END_OF_FILE
  if test 2395 -ne `wc -c <'CHANGES'`; then
    echo shar: \"'CHANGES'\" unpacked with wrong size!
  fi
  # end of 'CHANGES'
fi
if test -f 'glob.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'glob.c'\"
else
  echo shar: Extracting \"'glob.c'\" \(6192 characters\)
  sed "s/^X//" >'glob.c' <<'END_OF_FILE'
X/* glob.c: rc's (ugly) globber. This code is not elegant, but it works */
X
X#include <sys/types.h>
X#include "rc.h"
X#include "glob.h"
X#include "glom.h"
X#include "nalloc.h"
X#include "utils.h"
X#include "match.h"
X#include "footobar.h"
X#include "list.h"
X#ifdef NODIRENT
X#include <sys/dir.h>
X#define dirent direct /* need to get the struct declaraction right */
X#else
X#include <dirent.h>
X#endif
X
Xstatic List *dmatch(char *, char *, char *);
Xstatic List *doglob(char *, char *);
Xstatic List *lglob(List *, char *, char *, SIZE_T);
Xstatic List *sort(List *);
X
X/*
X   matches a list of words s against a list of patterns p. Returns true iff
X   a pattern in p matches a word in s. null matches null, but otherwise null
X   patterns match nothing.
X*/
X
Xboolean lmatch(List *s, List *p) {
X	List *q;
X	int i;
X	boolean okay;
X
X	if (s == NULL) {
X		if (p == NULL) /* null matches null */
X			return TRUE;
X		for (; p != NULL; p = p->n) { /* one or more stars match null */
X			if (*p->w != '\0') { /* the null string is a special case; it does *not* match () */
X				okay = TRUE;
X				for (i = 0; p->w[i] != '\0'; i++)
X					if (p->w[i] != '*' || p->m[i] != 1) {
X						okay = FALSE;
X						break;
X					}
X				if (okay)
X					return TRUE;
X			}
X		}
X		return FALSE;
X	}
X
X	for (; s != NULL; s = s->n)
X		for (q = p; q != NULL; q = q->n)
X			if (match(q->w, q->m, s->w))
X				return TRUE;
X	return FALSE;
X}
X
X/* Matches a pattern p against the contents of directory d */
X
Xstatic List *dmatch(char *d, char *p, char *m) {
X	boolean matched = FALSE;
X	List *top, *r;
X	DIR *dirp;
X	struct dirent *dp;
X	/* prototypes for XXXdir functions. comment out if necessary */
X	extern DIR *opendir(const char *);
X	extern struct dirent *readdir(DIR *);
X	extern int closedir(DIR *);
X
X	if ((dirp = opendir(d)) == NULL)
X		return NULL;
X
X	top = r = NULL;
X
X	while ((dp = readdir(dirp)) != NULL)
X		if ((*dp->d_name != '.' || *p == '.') && match(p, m, dp->d_name)) { /* match ^. explicitly */
X			matched = TRUE;
X			if (top == NULL) {
X				top = r = nnew(List);
X			} else {
X				r->n = nnew(List);
X				r = r->n;
X			}
X			r->w = ncpy(dp->d_name);
X			r->m = NULL;
X		}
X
X	closedir(dirp);
X
X	if (!matched)
X		return NULL;
X
X	r->n = NULL;
X	return top;
X}
X
X/*
X   lglob() globs a pattern agains a list of directory roots. e.g., (/tmp /usr /bin) "*"
X   will return a list with all the files in /tmp, /usr, and /bin. NULL on no match.
X   slashcount indicates the number of slashes to stick between the directory and the
X   matched name. e.g., for matching ////tmp/////foo*
X*/
X
Xstatic List *lglob(List *s, char *p, char *m, SIZE_T slashcount) {
X	List *q, *r, *top, foo;
X	static List slash;
X	static SIZE_T slashsize = 0;
X
X	if (slashcount + 1 > slashsize) {
X		slash.w = ealloc(slashcount + 1);
X		slashsize = slashcount;
X	}
X	slash.w[slashcount] = '\0';
X	while (slashcount > 0)
X		slash.w[--slashcount] = '/';
X
X	for (top = r = NULL; s != NULL; s = s->n) {
X		q = dmatch(s->w, p, m);
X		if (q != NULL) {
X			foo.w = s->w;
X			foo.m = NULL;
X			foo.n = NULL;
X			if (!(s->w[0] == '/' && s->w[1] == '\0')) /* need to separate */
X				q = concat(&slash, q);		  /* dir/name with slash */
X			q = concat(&foo, q);
X			if (r == NULL)
X				top = r = q;
X			else
X				r->n = q;
X			while (r->n != NULL)
X				r = r->n;
X		}
X	}
X	return top;
X}
X
X/*
X   Doglob globs a pathname in pattern form against a unix path. Returns the original
X   pattern (cleaned of metacharacters) on failure, or the globbed string(s).
X*/
X
Xstatic List *doglob(char *w, char *m) {
X	static char *dir = NULL, *pattern = NULL, *metadir = NULL, *metapattern = NULL;
X	static SIZE_T dsize = 0;
X	char *d, *p, *md, *mp;
X	SIZE_T psize;
X	char *s = w;
X	List firstdir;
X	List *matched;
X	int slashcount;
X
X	if ((psize = strlen(w) + 1) > dsize || dir == NULL) {
X		efree(dir); efree(pattern); efree(metadir); efree(metapattern);
X		dir = ealloc(psize);
X		pattern = ealloc(psize);
X		metadir = ealloc(psize);
X		metapattern = ealloc(psize);
X		dsize = psize;
X	}
X
X	d = dir;
X	p = pattern;
X	md = metadir;
X	mp = metapattern;
X
X	if (*s == '/') {
X		while (*s == '/')
X			*d++ = *s++, *md++ = *m++;
X	} else {
X		while (*s != '/' && *s != '\0')
X			*d++ = *s++, *md++ = *m++; /* get first directory component */
X	}
X	*d = '\0';
X
X	/*
X	   Special case: no slashes in the pattern, i.e., open the current directory.
X	   Remember that w cannot consist of slashes alone (the other way *s could be
X	   zero) since doglob gets called iff there's a metacharacter to be matched
X	*/
X	if (*s == '\0') {
X		matched = dmatch(".", dir, metadir);
X		goto end;
X	}
X
X	if (*w == '/') {
X		firstdir.w = dir;
X		firstdir.m = metadir;
X		firstdir.n = NULL;
X		matched = &firstdir;
X	} else {
X		/*
X		   we must glob against current directory,
X		   since the first character is not a slash.
X		*/
X		matched = dmatch(".", dir, metadir);
X	}
X
X	do {
X		for (slashcount = 0; *s == '/'; s++, m++)
X			slashcount++; /* skip slashes */
X
X		while (*s != '/' && *s != '\0')
X			*p++ = *s++, *mp++ = *m++; /* get pattern */
X		*p = '\0';
X
X		matched = lglob(matched, pattern, metapattern, slashcount);
X
X		p = pattern, mp = metapattern;
X	} while (*s != '\0');
X
Xend:	if (matched == NULL) {
X		matched = nnew(List);
X		matched->w = w;
X		matched->m = NULL;
X		matched->n = NULL;
X	}
X	return matched;
X}
X
X/*
X   Globs a list; checks to see if each element in the list has a metacharacter. If it
X   does, it is globbed, and the output is sorted.
X*/
X
XList *glob(List *s) {
X	List *top, *r;
X	boolean meta;
X
X	for (r = s, meta = FALSE; r != NULL; r = r->n)
X		if (r->m != NULL)
X			meta = TRUE;
X
X	if (!meta)
X		return s; /* don't copy lists with no metacharacters in them */
X
X	for (top = r = NULL; s != NULL; s = s->n) {
X		if (s->m == NULL) { /* no metacharacters; just tack on to the return list */
X			if (top == NULL) {
X				top = r = nnew(List);
X			} else {
X				r->n = nnew(List);
X				r = r->n;
X			}
X			r->w = s->w;
X		} else {
X			if (top == NULL)
X				top = r = sort(doglob(s->w, s->m));
X			else
X				r->n = sort(doglob(s->w, s->m));
X			while (r->n != NULL)
X				r = r->n;
X		}
X	}
X
X	r->n = NULL;
X	return top;
X}
X
Xstatic List *sort(List *s) {
X	SIZE_T nel = listnel(s);
X
X	if (nel > 1) {
X		char **a;
X		List *t;
X
X		qsort(a = list2array(s, FALSE), nel, sizeof(char *), starstrcmp);
X
X		for (t = s; t != NULL; t = t->n)
X			t->w = *a++;
X	}
X
X	return s;
X}
END_OF_FILE
  if test 6192 -ne `wc -c <'glob.c'`; then
    echo shar: \"'glob.c'\" unpacked with wrong size!
  fi
  # end of 'glob.c'
fi
if test -f 'hash.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'hash.c'\"
else
  echo shar: Extracting \"'hash.c'\" \(6588 characters\)
  sed "s/^X//" >'hash.c' <<'END_OF_FILE'
X/* hash.c: hash table support for functions and variables. */
X
X/*
X   functions and variables are cached in both internal and external
X   form for performance. Thus a variable which is never "dereferenced"
X   with a $ is passed on to rc's children untouched. This is not so
X   important for variables, but is a big win for functions, where a call
X   to yyparse() is involved.
X*/
X
X#include "rc.h"
X#include "utils.h"
X#include "hash.h"
X#include "list.h"
X#include "tree.h"
X#include "footobar.h"
X
Xstatic boolean exportable(char *);
Xstatic int hash(char *, int);
Xstatic int find(char *, Htab *, int);
Xstatic void free_fn(Function *);
X
XHtab *fp;
XHtab *vp;
Xstatic int fused, fsize, vused, vsize;
Xstatic char **env;
Xstatic int bozosize;
Xstatic int envsize;
Xstatic boolean env_dirty = TRUE;
Xstatic char *dead = "";
X
X#define HASHSIZE 64 /* rc was debugged with HASHSIZE == 2; 64 is about right for normal use */
X
Xvoid inithash(void) {
X	int i;
X	Htab *fpp, *vpp;
X
X	fp = ealloc(sizeof(Htab) * HASHSIZE);
X	vp = ealloc(sizeof(Htab) * HASHSIZE);
X	fused = vused = 0;
X	fsize = vsize = HASHSIZE;
X
X	for (vpp = vp, fpp = fp, i = 0; i < HASHSIZE; i++, vpp++, fpp++)
X		vpp->name = fpp->name = NULL;
X}
X
X#define ADV()   {if ((c = *s++) == '\0') break;}
X
X/* hash function courtesy of paul haahr */
X
Xstatic int hash(char *s, int size) {
X	int n = 0;
X	int c;
X
X	while (1) {
X		ADV();
X		n += (c << 17) ^ (c << 11) ^ (c << 5) ^ (c >> 1);
X		ADV();
X		n ^= (c << 14) + (c << 7) + (c << 4) + c;
X		ADV();
X		n ^= (~c << 11) | ((c << 3) ^ (c >> 1));
X		ADV();
X		n -= (c << 16) | (c << 9) | (c << 2) | (c & 3);
X	}
X
X	if (n < 0)
X		n = ~n;
X
X	return n & (size - 1); /* need power of 2 size */
X}
X
Xstatic boolean rehash(Htab *ht) {
X	int i,j,size;
X	int newsize,newused;
X	Htab *newhtab;
X
X	if (ht == fp) {
X		if (fsize > 2 * fused)
X			return FALSE;
X		size = fsize;
X	} else {
X		if (vsize > 2 * vused)
X			return FALSE;
X		size = vsize;
X	}
X
X
X	newsize = 2 * size;
X	newhtab = ealloc(newsize * sizeof(Htab));
X	for (i = 0; i < newsize; i++)
X		newhtab[i].name = NULL;
X
X	for (i = newused = 0; i < size; i++)
X		if (ht[i].name != NULL && ht[i].name != dead) {
X			newused++;
X			j = hash(ht[i].name, newsize);
X			while (newhtab[j].name != NULL) {
X				j++;
X				j &= (newsize - 1);
X			}
X			newhtab[j].name = ht[i].name;
X			newhtab[j].p = ht[i].p;
X		}
X
X	if (ht == fp) {
X		fused = newused;
X		fp = newhtab;
X		fsize = newsize;
X	} else {
X		vused = newused;
X		vp = newhtab;
X		vsize = newsize;
X	}
X
X	efree(ht);
X	return TRUE;
X}
X
X#define varfind(s) find(s,vp,vsize)
X#define fnfind(s) find(s,fp,fsize)
X
Xstatic int find(char *s, Htab *ht, int size) {
X	int h = hash(s, size);
X
X	while (ht[h].name != NULL && !streq(ht[h].name,s)) {
X		h++;
X		h &= size - 1;
X	}
X
X	return h;
X}
X
Xvoid *lookup(char *s, Htab *ht) {
X	int h = find(s, ht, ht == fp ? fsize : vsize);
X
X	return (ht[h].name == NULL) ? NULL : ht[h].p;
X}
X
XFunction *get_fn_place(char *s) {
X	int h = fnfind(s);
X
X	env_dirty = TRUE;
X
X	if (fp[h].name == NULL) {
X		if (rehash(fp))
X			h = fnfind(s);
X		fused++;
X		fp[h].name = ecpy(s);
X		fp[h].p = enew(Function);
X	} else
X		free_fn(fp[h].p);
X
X	return fp[h].p;
X}
X
XVariable *get_var_place(char *s, boolean stack) {
X	Variable *new;
X	int h = varfind(s);
X
X	env_dirty = TRUE;
X
X	if (vp[h].name == NULL) {
X		if (rehash(vp))
X			h = varfind(s);
X		vused++;
X		vp[h].name = ecpy(s);
X		strcpy(vp[h].name, s);
X		vp[h].p = enew(Variable);
X		((Variable *)vp[h].p)->n = NULL;
X		return vp[h].p;
X	} else {
X		if (stack) {	/* increase the stack by 1 */
X			new = enew(Variable);
X			new->n = vp[h].p;
X			return vp[h].p = new;
X		} else {	/* trample the top of the stack */
X			new = vp[h].p;
X			efree(new->extdef);
X			listfree(new->def);
X			return new;
X		}
X	}
X}
X
Xvoid delete_fn(char *s) {
X	int h = fnfind(s);
X
X	if (fp[h].name == NULL)
X		return; /* not found */
X
X	env_dirty = TRUE;
X
X	free_fn(fp[h].p);
X	efree(fp[h].p);
X	efree(fp[h].name);
X	if (fp[h+1].name == NULL) {
X		--fused;
X		fp[h].name = NULL;
X	} else {
X		fp[h].name = dead;
X	}
X}
X
Xvoid delete_var(char *s, boolean stack) {
X	int h = varfind(s);
X	Variable *v;
X
X	if (vp[h].name == NULL)
X		return; /* not found */
X
X	env_dirty = TRUE;
X
X	v = vp[h].p;
X	efree(v->extdef);
X	listfree(v->def);
X
X	if (v->n != NULL) { /* This is the top of a stack */
X		if (stack) { /* pop */
X			vp[h].p = v->n;
X			efree(v);
X		} else { /* else just empty */
X			v->extdef = NULL;
X			v->def = NULL;
X		}
X	} else { /* needs to be removed from the hash table */
X		efree(v);
X		efree(vp[h].name);
X		if (vp[h+1].name == NULL) {
X			--vused;
X			vp[h].name = NULL;
X		} else {
X			vp[h].name = dead;
X		}
X	}
X}
X
Xstatic void free_fn(Function *f) {
X	treefree(f->def);
X	efree(f->extdef);
X}
X
Xvoid initenv(char **envp) {
X	int n;
X
X	for (n = 0; envp[n] != NULL; n++)
X		;
X	n++; /* one for the null terminator */
X
X	if (n < HASHSIZE)
X		n = HASHSIZE;
X
X	env = ealloc((envsize = 2 * n) * sizeof (char *));
X
X	for (; *envp != NULL; envp++)
X		if (strncmp(*envp, "fn_", sizeof("fn_") - 1) == 0)
X			fnassign_string(*envp);
X		else
X			if (!varassign_string(*envp)) /* add to bozo env */
X				env[bozosize++] = *envp;
X}
X
Xstatic boolean exportable(char *s) {
X	int i;
X	static char *notforexport[] = {
X		"sighup", "sigint", "sigquit", "sigterm", "sigexit",
X		"apid", "pid", "apid", "*", "ifs"
X	};
X
X	for (i = 0; i < arraysize(notforexport); i++)
X		if (streq(s,notforexport[i]))
X			return FALSE;
X	return TRUE;
X}
X
Xchar **makeenv(void) {
X	int ep, i;
X	char *v;
X
X	if (!env_dirty)
X		return env;
X
X	env_dirty = FALSE;
X	ep = bozosize;
X
X	if (vsize + fsize + 1 + bozosize > envsize) {
X		envsize = 2 * (bozosize + vsize + fsize + 1);
X		env = erealloc(env, envsize * sizeof(char *));
X	}
X
X	for (i = 0; i < vsize; i++) {
X		if (vp[i].name == NULL || !exportable(vp[i].name))
X			continue;
X		v = varlookup_string(vp[i].name);
X		if (v != NULL)
X			env[ep++] = v;
X	}
X	for (i = 0; i < fsize; i++) {
X		if (fp[i].name == NULL || !exportable(fp[i].name))
X			continue;
X		env[ep++] = fnlookup_string(fp[i].name);
X	}
X	env[ep] = NULL;
X	return env;
X}
X
Xvoid whatare_all_vars(void) {
X	int i;
X	List *s,*t;
X
X	for (i = 0; i < vsize; i++)
X		if (vp[i].name != NULL && (s = varlookup(vp[i].name)) != NULL) {
X			fprint(1,"%s=%s", vp[i].name, (s->n == NULL ? "" : "("));
X			for (t = s; t->n != NULL; t = t->n)
X				fprint(1,"%s ",strprint(t->w, FALSE, TRUE));
X			fprint(1,"%s%s\n",strprint(t->w, FALSE, TRUE), (s->n == NULL ? "" : ")"));
X		}
X	for (i = 0; i < fsize; i++)
X		if (fp[i].name != NULL)
X			fprint(1,"fn %s {%s}\n", fp[i].name, ptree(fnlookup(fp[i].name)));
X}
X
X/* fake getenv() for readline() follows: */
X
X#ifdef READLINE
Xchar *getenv(char *name) {
X	List *s;
X
X	if (name == NULL || (s = varlookup(name)) == NULL)
X		return NULL;
X
X	return s->w;
X}
X#endif
X
END_OF_FILE
  if test 6588 -ne `wc -c <'hash.c'`; then
    echo shar: \"'hash.c'\" unpacked with wrong size!
  fi
  # end of 'hash.c'
fi
if test -f 'heredoc.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'heredoc.c'\"
else
  echo shar: Extracting \"'heredoc.c'\" \(3252 characters\)
  sed "s/^X//" >'heredoc.c' <<'END_OF_FILE'
X/* heredoc.c: heredoc slurping is done here */
X
X#include "rc.h"
X#include "lex.h"
X#include "utils.h"
X#include "nalloc.h"
X#include "heredoc.h"
X#include "tree.h"
X#include "input.h"
X#include "hash.h"
X#include "glom.h"
X
Xstruct Hq {
X	Node *doc;
X	char *name;
X	Hq *n;
X} *hq;
X
Xstatic SIZE_T j, bufsize;
X
X/* a stupid realloc for nalloc */
X
Xstatic char *renalloc(char *buf, SIZE_T n) {
X
X	while (n >= bufsize - 2)
X		bufsize *= 2;
X
X	return memcpy(nalloc(bufsize), buf, j);
X}
X
X/*
X   read in a heredocument. A clever trick: skip over any partially matched end-of-file
X   marker storing only the number of characters matched. If the whole marker is matched,
X   return from readheredoc(). If only part of the marker is matched, copy that part into
X   the heredocument.
X*/
X
Xstatic char *readheredoc(char *eof) {
X	int c;
X	SIZE_T i, markerlen, varsize = 0;
X	char *buf, *varname = NULL, *newvarname;
X	boolean quoted = FALSE;
X	List *var;
X
X	if (*eof == '\'') {
X		quoted = TRUE;
X		eof++;
X	}
X
X	markerlen = strlen(eof);
X
X	buf = nalloc(bufsize = 512);
X	j = 0;
X
X	while (1) {
X		print_prompt2();
X
X		for (i = 0, c = gchar(); eof[i] == c && i < markerlen; i++) /* match the eof marker */
X			c = gchar();
X
X		if (i == markerlen && c == '\n') { /* if the whole marker was matched, return */
X			buf[j] = 0;
X			return buf;
X		}
X
X		if (i > 0) { /* else copy the partially matched marker into the heredocument */
X			if (i + j >= bufsize - 2)
X				buf = renalloc(buf, i + j);
X			memcpy(buf + j, eof, i);
X			j += i;
X		}
X
X		for (; c != '\n'; c = gchar()) {
X			if (c == EOF)
X				scanerror("EOF inside heredoc");
X			if (j >= bufsize - 2)
X				buf = renalloc(buf, j);
X			if (quoted || c != '$') {
X				buf[j++] = c;
X				continue; /* avoid an extra level of indentation */
X			}
X			if ((c = gchar()) == '$' || dnw[c]) {
X				if (c != '$') /* $$ quotes $, but $<nonalnum> is transcribed literally */
X					buf[j++] = '$';
X				buf[j++] = c;
X			} else {
X				if (varsize == 0)
X					varname = nalloc(varsize = 16);
X				for (i = 0; !dnw[c]; i++, c = gchar()) {
X					if (i >= varsize - 1) {
X						newvarname = nalloc(varsize *= 4);
X						memcpy(newvarname, varname, i);
X						varname = newvarname;
X					}
X					varname[i] = c;
X				}
X				varname[i] = '\0';
X				if (c != '^')
X					ugchar(c);
X				var = varlookup(varname);
X				if (varname[0] == '*' && varname[1] == '\0') /* skip $0 */
X					var = var->n;
X				if ((var = flatten(var)) != NULL) {
X					i = strlen(var->w);
X					while (j + i >= bufsize - 2)
X						buf = renalloc(buf, i + j);
X					memcpy(buf + j, var->w, i);
X					j += i;
X				}
X			}
X		}
X		buf[j++] = c;
X	}
X}
X
X/* read in heredocs when yyparse hits a newline. called from yyparse */
X
Xvoid heredoc(int end) {
X	if (end && hq != NULL)
X		rc_error("EOF on command line with heredoc");
X
X	for (; hq != NULL; hq = hq->n) {
X		hq->doc->u[0].i = HERESTRING;
X		hq->doc->u[2].p = newnode(rWORD,readheredoc(hq->name), NULL);
X	}
X}
X
X/* queue pending heredocs into a queue. called from yyparse */
X
Xvoid qdoc(Node *name, Node *n) {
X	Hq *new;
X
X	if (name->type != rWORD)
X		scanerror("eof-marker must be a single word");
X
X	if (hq == NULL) {
X		new = hq = nnew(Hq);
X	} else {
X		for (new = hq; new->n != NULL; new = new->n)
X			;
X		new->n = nnew(Hq);
X		new = new->n;
X	}
X
X	new->name = name->u[0].s;
X	new->doc = n;
X	new->n = NULL;
X}
END_OF_FILE
  if test 3252 -ne `wc -c <'heredoc.c'`; then
    echo shar: \"'heredoc.c'\" unpacked with wrong size!
  fi
  # end of 'heredoc.c'
fi
if test -f 'parse.y' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'parse.y'\"
else
  echo shar: Extracting \"'parse.y'\" \(5173 characters\)
  sed "s/^X//" >'parse.y' <<'END_OF_FILE'
X/* parse.y */
X
X/*
X * Adapted from rc grammar, v10 manuals, volume 2.
X */
X
X%{
X#include "stddef.h"
X#include "node.h"
X#include "lex.h"
X#include "tree.h"
X#include "heredoc.h"
X#include "parse.h"
X#include "utils.h"
X#undef NULL
X#define NULL 0
X#undef lint
X#define lint		/* hush up gcc -Wall, leave out the dumb sccsid's. */
Xstatic Node *star, *nolist;
XNode *parsetree;	/* not using yylval because bison declares it as an auto */
X%}
X
X%term BANG DUP ELSE END FN FOR HUH IF IN LBRACK PIPE RBRACK
X%term REDIR STAR SUB SUBSHELL SWITCH TWIDDLE WHILE WORD
X
X%left IF WHILE FOR SWITCH ')' ELSE
X%left ANDAND OROR
X%left BANG SUBSHELL
X%left PIPE
X%left '^'
X%right '$' COUNT FLAT
X%left SUB
X%left '`' BACKBACK
X
X%union {
X	struct Node *node;
X	struct Redir redir;
X	struct Pipe pipe;
X	struct Dup dup;
X	struct Word word;
X	char *keyword;
X}
X
X%type <redir> REDIR
X%type <pipe> PIPE
X%type <dup> DUP
X%type <word> WORD
X%type <keyword> keyword
X%type <node> assign body brace cmd cmdsa cmdsan comword epilog
X	     first line paren redir simple iftail word words
X
X%start rc
X
X%%
X
Xrc	: line end		{ parsetree = $1; return 1; }
X	| error end		{ yyerrok; parsetree = NULL; return -1; }
X
X/* an rc line may end in end-of-file as well as newline, e.g., rc -c 'ls' */
Xend	: END	/* EOF */	{ heredoc(1); } /* flag error if there is a heredoc in the queue */
X	| '\n'			{ heredoc(0); } /* get heredoc on \n */
X
X/* a cmdsa is a command followed by ampersand or newline (used in "line" and "body") */
Xcmdsa	: cmd ';'
X	| cmd '&'		{ $$ = ($1 != NULL ? newnode(NOWAIT,$1) : $1); }
X
X/* a line is a single command, or a command terminated by ; or & followed by a line (recursive) */
Xline	: cmd
X	| cmdsa line		{ $$ = ($1 != NULL ? newnode(BODY,$1,$2) : $2); }
X
X/* a body is like a line, only commands may also be terminated by newline */
Xbody	: cmd
X	| cmdsan body		{ $$ = ($1 != NULL ? newnode(BODY,$1,$2) : $2); }
X
Xcmdsan	: cmdsa
X	| cmd '\n'		{ $$ = $1; heredoc(0); } /* get h.d. on \n */
X
Xbrace	: '{' body '}'		{ $$ = $2; }
X
Xparen	: '(' body ')'		{ $$ = $2; }
X
Xassign	: first '=' word	{ $$ = newnode(ASSIGN,$1,$3); }
X
Xepilog	:			{ $$ = NULL; }
X	| redir epilog		{ $$ = newnode(EPILOG,$1,$2); }
X
X/* a redirection is a dup (e.g., >[1=2]) or a file redirection. (e.g., > /dev/null) */
Xredir	: DUP			{ $$ = newnode(rDUP,$1.type,$1.left,$1.right); }
X	| REDIR word		{ $$ = newnode(rREDIR,$1.type,$1.fd,$2);
X				  if ($1.type == HEREDOC) qdoc($2, $$); /* queue heredocs up */
X				}
X
X/* the tail of an if statement can be a command, or a braced command followed by else on the same line */
Xiftail	: cmd %prec IF
X	| brace ELSE cmd	{ $$ = newnode(rELSE,$1,$3); }
X
X/* skipnl() skips newlines and comments between an operator and its operand */
Xcmd	:						{ $$ = NULL; }
X	| simple
X	| brace epilog					{ $$ = ($2 == NULL ? $1 : newnode(BRACE,$1,$2)); }
X	| IF paren { skipnl(); } iftail			{ $$ = newnode(rIF,$2,$4); }
X	| FOR '(' word IN words ')' { skipnl(); } cmd	{ $$ = newnode(FORIN,$3,$5,$8); }
X	| FOR '(' word ')' { skipnl(); } cmd		{ $$ = newnode(FORIN,$3,star,$6); }
X	| WHILE paren { skipnl(); } cmd			{ $$ = newnode(rWHILE,$2,$4); }
X	| SWITCH '(' word ')' { skipnl(); } brace	{ $$ = newnode(rSWITCH,$3,$6); }
X	| TWIDDLE word words				{ $$ = newnode(MATCH,$2,$3); }
X	| cmd ANDAND { skipnl(); } cmd			{ $$ = ($1 != NULL ? newnode(rANDAND,$1,$4) : $4); }
X	| cmd OROR { skipnl(); } cmd			{ $$ = ($1 != NULL ? newnode(rOROR,$1,$4) : $4); }
X 	| cmd PIPE { skipnl(); } cmd			{ $$ = newnode(rPIPE,$2.left,$2.right,$1,$4); }
X	| redir cmd %prec BANG				{ $$ = ($2 != NULL ? newnode(PRE,$1,$2) : $1); }
X	| assign cmd %prec BANG				{ $$ = ($2 != NULL ? newnode(PRE,$1,$2) : $1); }
X	| BANG cmd					{ $$ = newnode(rBANG,$2); }
X	| SUBSHELL cmd					{ $$ = newnode(rSUBSHELL,$2); }
X	| FN words brace				{ $$ = newnode(NEWFN,$2,$3); }
X	| FN words					{ $$ = newnode(RMFN,$2); }
X
Xsimple	: first
X	| simple word			{ $$ = ($2 != NULL ? newnode(ARGS,$1,$2) : $1); }
X	| simple redir			{ $$ = newnode(ARGS,$1,$2); }
X
Xfirst	: comword
X	| first '^' word		{ $$ = newnode(CONCAT,$1,$3); }
X
Xword	: comword
X	| keyword			{ $$ = newnode(rWORD,$1, NULL); }
X	| word '^' word			{ $$ = newnode(CONCAT,$1,$3); }
X
Xcomword	: '$' word			{ $$ = newnode(VAR,$2); }
X	| '$' word SUB words ')'	{ $$ = newnode(VARSUB,$2,$4); }
X	| COUNT word			{ $$ = newnode(rCOUNT,$2); }
X	| FLAT word			{ $$ = newnode(rFLAT, $2); }
X	| WORD				{ $$ = newnode(rWORD,$1.w, $1.m); }
X	| '`' word			{ $$ = newnode(BACKQ,nolist,$2); }
X	| '`' brace			{ $$ = newnode(BACKQ,nolist,$2); }
X	| BACKBACK word	brace		{ $$ = newnode(BACKQ,$2,$3); }
X	| BACKBACK word	word		{ $$ = newnode(BACKQ,$2,$3); }
X	| '(' words ')'			{ $$ = $2; }
X	| REDIR brace			{ $$ = newnode(NMPIPE,$1.type,$1.fd,$2); }
X
Xkeyword	: FOR		{ $$ = "for"; }
X	| IN		{ $$ = "in"; }
X	| WHILE		{ $$ = "while"; }
X	| IF		{ $$ = "if"; }
X	| SWITCH	{ $$ = "switch"; }
X	| FN		{ $$ = "fn"; }
X	| ELSE		{ $$ = "else"; }
X	| TWIDDLE	{ $$ = "~"; }
X	| BANG		{ $$ = "!"; }
X	| SUBSHELL	{ $$ = "@"; }
X
Xwords	:		{ $$ = NULL; }
X	| words word	{ $$ = ($1 != NULL ? ($2 != NULL ? newnode(LAPPEND,$1,$2) : $1) : $2); }
X
X%%
X
Xvoid initparse() {
X	star = treecpy(newnode(VAR,newnode(rWORD,"*",NULL)), ealloc);
X	nolist = treecpy(newnode(VAR,newnode(rWORD,"ifs",NULL)), ealloc);
X}
END_OF_FILE
  if test 5173 -ne `wc -c <'parse.y'`; then
    echo shar: \"'parse.y'\" unpacked with wrong size!
  fi
  # end of 'parse.y'
fi
if test -f 'redir.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'redir.c'\"
else
  echo shar: Extracting \"'redir.c'\" \(2480 characters\)
  sed "s/^X//" >'redir.c' <<'END_OF_FILE'
X/* redir.c: code for opening files and piping heredocs after fork but before exec. */
X
X#include "rc.h"
X#include "lex.h"
X#include "glom.h"
X#include "glob.h"
X#include "open.h"
X#include "exec.h"
X#include "utils.h"
X#include "redir.h"
X#include "hash.h"
X
X/*
X   Walk the redirection queue, and open files and dup2 to them. Also, here-documents are treated
X   here by dumping them down a pipe. (this should make here-documents fast on systems with lots
X   of memory which do pipes right. Under sh, a file is copied to /tmp, and then read out of /tmp
X   again. I'm interested in knowing how much faster, say, shar runs when unpacking when invoked
X   with rc instead of sh. On my sun4/280, it runs in about 60-75% of the time of sh for unpacking
X   the rc source distribution.)
X*/
X
Xvoid doredirs() {
X	List *fname;
X	int fd, p[2];
X	Rq *r;
X
X	for (r = redirq; r != NULL; r = r->n) {
X		switch(r->r->type) {
X		default:
X			fprint(2,"%d: bad node in doredirs\n", r->r->type);
X			exit(1);
X			/* NOTREACHED */
X		case rREDIR:
X			if (r->r->u[0].i == HERESTRING) {
X				fname = flatten(glom(r->r->u[2].p)); /* fname is really a string */
X				if (fname == NULL) {
X					close(r->r->u[1].i); /* feature? */
X					break;
X				}
X				if (pipe(p) < 0) {
X					uerror("pipe");
X					exit(1);
X				}
X				switch (fork()) {
X				case -1:
X					uerror("fork");
X					exit(1);
X					/* NOTREACHED */
X				case 0:	/* child writes to pipe */
X					setsigdefaults();
X					close(p[0]);
X					writeall(p[1], fname->w, strlen(fname->w));
X					exit(0);
X					/* NOTREACHED */
X				default:
X					close(p[1]);
X					if (dup2(p[0], r->r->u[1].i) < 0) {
X						uerror("dup");
X						exit(1);
X					}
X					close(p[0]);
X				}
X			} else {
X				fname = glob(glom(r->r->u[2].p));
X				if (fname == NULL) {
X					fprint(2,"null filename in redirection\n");
X					exit(1);
X				}
X				if (fname->n != NULL) {
X					fprint(2,"multi-word filename in redirection\n");
X					exit(1);
X				}
X				switch (r->r->u[0].i) {
X				default:
X					fprint(2,"doredirs: this can't happen\n");
X					exit(1);
X					/* NOTREACHED */
X				case CREATE: case APPEND: case FROM:
X					fd = rc_open(fname->w, r->r->u[0].i);
X					break;
X				}
X				if (fd < 0) {
X					uerror(fname->w);
X					exit(1);
X				}
X				if (dup2(fd, r->r->u[1].i) < 0) {
X					uerror("dup");
X					exit(1);
X				}
X				close(fd);
X			}
X			break;
X		case rDUP:
X			if (r->r->u[2].i == -1)
X				close(r->r->u[1].i);
X			else if (dup2(r->r->u[2].i, r->r->u[1].i) < 0) {
X				uerror("dup");
X				exit(1);
X			}
X		}
X	}
X	redirq = NULL;
X}
END_OF_FILE
  if test 2480 -ne `wc -c <'redir.c'`; then
    echo shar: \"'redir.c'\" unpacked with wrong size!
  fi
  # end of 'redir.c'
fi
if test -f 'tree.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'tree.c'\"
else
  echo shar: Extracting \"'tree.c'\" \(4613 characters\)
  sed "s/^X//" >'tree.c' <<'END_OF_FILE'
X/* tree.c: functions for manipulating parse-trees. (create, copy, delete) */
X
X#include <stdarg.h>
X#include "rc.h"
X#include "tree.h"
X#include "utils.h"
X#include "nalloc.h"
X
X/* make a new node, pass it back to yyparse. Used to generate the parsetree. */
X
XNode *newnode(int /*enum nodetype*/ t,...) {
X	va_list ap;
X	Node *n;
X
X	va_start(ap,t);
X
X	switch (t) {
X	default:
X		fprint(2,"newnode: this can't happen\n");
X		exit(1);
X		/* NOTREACHED */
X	case rDUP:
X		n = nalloc(offsetof(Node, u[3]));
X		n->u[0].i = va_arg(ap, int);
X		n->u[1].i = va_arg(ap, int);
X		n->u[2].i = va_arg(ap, int);
X		break;
X	case rWORD:
X		n = nalloc(offsetof(Node, u[2]));
X		n->u[0].s = va_arg(ap, char *);
X		n->u[1].s = va_arg(ap, char *);
X		break;
X	case rBANG: case NOWAIT:
X	case rCOUNT: case rFLAT: case RMFN: case rSUBSHELL:
X	case VAR:
X		n = nalloc(offsetof(Node, u[1]));
X		n->u[0].p = va_arg(ap, Node *);
X		break;
X	case rANDAND: case ASSIGN: case BACKQ: case BODY: case BRACE: case CONCAT:
X	case rELSE: case EPILOG: case rIF: case NEWFN:
X	case rOROR: case PRE: case ARGS: case rSWITCH:
X	case MATCH: case VARSUB: case rWHILE: case LAPPEND:
X		n = nalloc(offsetof(Node, u[2]));
X		n->u[0].p = va_arg(ap, Node *);
X		n->u[1].p = va_arg(ap, Node *);
X		break;
X	case FORIN:
X		n = nalloc(offsetof(Node, u[3]));
X		n->u[0].p = va_arg(ap, Node *);
X		n->u[1].p = va_arg(ap, Node *);
X		n->u[2].p = va_arg(ap, Node *);
X		break;
X	case rPIPE:
X		n = nalloc(offsetof(Node, u[4]));
X		n->u[0].i = va_arg(ap, int);
X		n->u[1].i = va_arg(ap, int);
X		n->u[2].p = va_arg(ap, Node *);
X		n->u[3].p = va_arg(ap, Node *);
X		break;
X	case rREDIR:
X	case NMPIPE:
X		n = nalloc(offsetof(Node, u[3]));
X		n->u[0].i = va_arg(ap, int);
X		n->u[1].i = va_arg(ap, int);
X		n->u[2].p = va_arg(ap, Node *);
X		break;
X 	}
X	n->type = t;
X	va_end(ap);
X	return n;
X}
X
X/* copy a tree to malloc space. Used when storing the definition of a function */
X
XNode *treecpy(Node *s, void (*alloc(SIZE_T))) {
X	Node *n;
X
X	if (s == NULL)
X		return NULL;
X
X	switch (s->type) {
X	default:
X		fprint(2,"treecpy: this can't happen\n");
X		exit(1);
X		/* NOTREACHED */
X	case rDUP:
X		n = alloc(offsetof(Node, u[3]));
X		n->u[0].i = s->u[0].i;
X		n->u[1].i = s->u[1].i;
X		n->u[2].i = s->u[2].i;
X		break;
X	case rWORD:
X		n = alloc(offsetof(Node, u[2]));
X		n->u[0].s = ecpy(s->u[0].s);
X		if (s->u[1].s != NULL) {
X			SIZE_T i = strlen(s->u[0].s);
X
X			n->u[1].s = alloc(i);
X			memcpy(n->u[1].s, s->u[1].s, i);
X		} else
X			n->u[1].s = NULL;
X		break;
X	case rBANG: case NOWAIT:
X	case rCOUNT: case rFLAT: case RMFN: case rSUBSHELL: case VAR:
X		n = alloc(offsetof(Node, u[1]));
X		n->u[0].p = treecpy(s->u[0].p, alloc);
X		break;
X	case rANDAND: case ASSIGN: case BACKQ: case BODY: case BRACE: case CONCAT:
X	case rELSE: case EPILOG: case rIF: case NEWFN:
X	case rOROR: case PRE: case ARGS: case rSWITCH:
X	case MATCH: case VARSUB: case rWHILE: case LAPPEND:
X		n = alloc(offsetof(Node, u[2]));
X		n->u[0].p = treecpy(s->u[0].p, alloc);
X		n->u[1].p = treecpy(s->u[1].p, alloc);
X		break;
X	case FORIN:
X		n = alloc(offsetof(Node, u[3]));
X		n->u[0].p = treecpy(s->u[0].p, alloc);
X		n->u[1].p = treecpy(s->u[1].p, alloc);
X		n->u[2].p = treecpy(s->u[2].p, alloc);
X		break;
X	case rPIPE:
X		n = alloc(offsetof(Node, u[4]));
X		n->u[0].i = s->u[0].i;
X		n->u[1].i = s->u[1].i;
X		n->u[2].p = treecpy(s->u[2].p, alloc);
X		n->u[3].p = treecpy(s->u[3].p, alloc);
X		break;
X	case rREDIR:
X	case NMPIPE:
X		n = alloc(offsetof(Node, u[3]));
X		n->u[0].i = s->u[0].i;
X		n->u[1].i = s->u[1].i;
X		n->u[2].p = treecpy(s->u[2].p, alloc);
X		break;
X	}
X	n->type = s->type;
X	return n;
X}
X
X/* free a function definition that is no longer needed */
X
Xvoid treefree(Node *s) {
X	if (s == NULL)
X		return;
X	switch (s->type) {
X	case rDUP:
X		break;
X	case rWORD:
X		efree(s->u[0].s);
X		efree(s->u[1].s);
X		break;
X	case rBANG: case NOWAIT:
X	case rCOUNT: case rFLAT: case RMFN:
X	case rSUBSHELL: case VAR:
X		treefree(s->u[0].p);
X		efree(s->u[0].p);
X		break;
X	case rANDAND: case ASSIGN: case BACKQ: case BODY: case BRACE: case CONCAT:
X	case rELSE: case EPILOG: case rIF: case NEWFN:
X	case rOROR: case PRE: case ARGS:
X	case rSWITCH: case MATCH:  case VARSUB: case rWHILE:
X	case LAPPEND:
X		treefree(s->u[1].p);
X		treefree(s->u[0].p);
X		efree(s->u[1].p);
X		efree(s->u[0].p);
X		break;
X	case FORIN:
X		treefree(s->u[2].p);
X		treefree(s->u[1].p);
X		treefree(s->u[0].p);
X		efree(s->u[2].p);
X		efree(s->u[1].p);
X		efree(s->u[0].p);
X		break;
X	case rPIPE:
X		treefree(s->u[2].p);
X		treefree(s->u[3].p);
X		efree(s->u[2].p);
X		efree(s->u[3].p);
X		break;
X	case rREDIR:
X	case NMPIPE:
X		treefree(s->u[2].p);
X		efree(s->u[2].p);
X		break;
X	default:
X		fprint(2,"treefree: this can't happen\n");
X		exit(1);
X	}
X}
END_OF_FILE
  if test 4613 -ne `wc -c <'tree.c'`; then
    echo shar: \"'tree.c'\" unpacked with wrong size!
  fi
  # end of 'tree.c'
fi
if test -f 'utils.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'utils.c'\"
else
  echo shar: Extracting \"'utils.c'\" \(5186 characters\)
  sed "s/^X//" >'utils.c' <<'END_OF_FILE'
X/* utils.c: general utility functions like fprint, ealloc etc. */
X
X#include <stdarg.h>
X#include <errno.h>
X#include <setjmp.h>
X#include <signal.h>
X#include "rc.h"
X#include "utils.h"
X#include "nalloc.h"
X#include "status.h"
X#include "input.h"
X#include "except.h"
X#include "lex.h"	/* import char nw[]; used by strprint to see if it needs to quote a word */
X#include "walk.h"
X
Xstatic void dprint(va_list, char *, char *);
Xstatic int n2u(char *, int);
X
X/* exception handlers */
X
Xvoid rc_error(char *s) {
X	if (s != NULL) {
X		if (interactive)
X			fprint(2,"%s\n",s);
X		else
X			fprint(2,"line %d: %s\n", lineno - 1, s);
X	}
X	set(FALSE);
X	redirq = NULL;
X	cond = FALSE; /* no longer inside conditional */
X	empty_fifoq();
X	rc_raise(ERROR);
X}
X
Xvoid sig(int s) {
X	signal(SIGINT, sig); /* some unices require re-signaling */
X
X	if (errno == EINTR)
X		return; /* allow wait() to complete */
X
X	fprint(2,"\n"); /* this is the newline you see when you hit ^C while typing a command */
X	redirq = NULL;
X	cond = FALSE;
X	empty_fifoq();
X	rc_raise(ERROR);
X}
X
X/* our perror */
X
Xvoid uerror(char *s) {
X	extern int sys_nerr;
X	extern char *sys_errlist[];
X
X	if (errno > sys_nerr)
X		return;
X
X	if (s != NULL)
X		fprint(2,"%s: %s\n",s,sys_errlist[errno]);
X	else
X		fprint(2,"%s\n",sys_errlist[errno]);
X}
X
X/* printing functions */
X
Xvoid fprint(int fd, char *f,...) {
X        va_list ap;
X	char str[FPRINT_SIZE];
X
X	va_start(ap,f);
X	dprint(ap, str, f);
X	va_end(ap);
X	writeall(fd,str,strlen(str));
X}
X
Xchar *sprint(char *b, char *f,...) {
X	va_list ap;
X
X	va_start(ap, f);
X	dprint(ap, b, f);
X	va_end(ap);
X	return b;
X}
X
Xstatic void dprint(va_list ap, char *strbuf, char *f) {
X	int i;
X
X	for (i = 0; *f != '\0'; f++) {
X		if (*f != '%') {
X			strbuf[i++] = *f;
X			continue; /* avoid an ugly extra level of indentation */
X		}
X		switch (*++f) {
X		case 'a': {
X			char **a = va_arg(ap, char **);
X
X			if (*a == NULL)
X				break;
X			strcpy(strbuf + i, *a);
X			i += strlen(*a);
X			while (*++a != NULL) {
X				strbuf[i++] = ' ';
X				strcpy(strbuf + i, *a);
X				i += strlen(*a);
X			}
X			break;
X		}
X		case 'c':
X			strbuf[i++] = va_arg(ap, int);
X			break;
X		case 'd': case 'o': {
X			int v = va_arg(ap, int);
X			int j = 0;
X			int base = (*f == 'd' ? 10 : 8);
X			char num[16];
X
X			if (v == 0)
X				num[j++] = '0';
X			while (v != 0) {
X				num[j++] = (v % base) + '0';
X				v /= base;
X			}
X			while (--j >= 0)
X				strbuf[i++] = num[j];
X			break;
X		}
X		case 's': {
X			char *s = va_arg(ap, char *);
X			while (*s != '\0')
X				strbuf[i++] = *s++;
X				break;
X		}
X		default: /* on format error, just print the bad format */
X			strbuf[i++] = '%';
X			/* FALLTHROUGH */
X		case '%':
X			strbuf[i++] = *f;
X		}
X	}
X	strbuf[i] = '\0';
X}
X
X/* prints a string in rc-quoted form. e.g., a string with spaces in it must be quoted */
X
Xchar *strprint(char *s, int quotable, int metaquote) { /* really boolean, but y.tab.c includes utils.h */
X	SIZE_T i,j;
X	char *t;
X
X	if (*s == '\0')
X		return "''";
X
X	for (i = 0; s[i] != '\0'; i++)
X		if (nw[s[i]] == 1 && (metaquote || (s[i] != '*' && s[i] != '?' && s[i] != '[')))
X			quotable = TRUE;
X
X	if (!quotable)
X		return s;
X
X	for(i = j = 0; s[i] != '\0'; i++, j++)
X		if (s[i] == '\'')
X			j++;
X
X	t = nalloc(j + 3);
X
X	t[0] = '\'';
X
X	for (j = 1, i = 0; s[i] != '\0'; i++, j++) {
X		t[j] = s[i];
X		if (s[i] == '\'')
X			t[++j] = '\'';
X	}
X
X	t[j++] = '\'';
X	t[j] = '\0';
X
X	return t;
X}
X
X/* ascii -> unsigned conversion routines. -1 indicates conversion error. */
X
Xstatic int n2u(char *s, int base) {
X	int i;
X
X	for (i = 0; *s != '\0'; s++) {
X		/* small hack with unsigned ints -- one compare for range test */
X		if (((unsigned int) *s) - '0' >= (unsigned int) base)
X			return -1;
X		i = (i * base) + (*s - '0');
X	}
X	return i;
X}
X
X/* decimal -> uint */
X
Xint a2u(char *s) {
X	return n2u(s, 10);
X}
X
X/* octal -> uint */
X
Xint o2u(char *s) {
X	return n2u(s, 8);
X}
X
X/* memory allocation functions */
X
Xvoid *ealloc(SIZE_T n) {
X	char *p = malloc(n);
X
X	if (p == NULL) {
X		uerror("malloc");
X		rc_exit(1);
X	}
X
X	return p;
X}
X
Xvoid *erealloc(void *p, SIZE_T n) {
X	p = realloc(p, n);
X
X	if (p == NULL) {
X		uerror("realloc");
X		rc_exit(1);
X	}
X
X	return p;
X}
X
Xvoid efree(void *p) {
X	if (p != NULL)
X		free(p);
X}
X
X/* useful functions */
X
X/* The last word in portable ANSI: a strcmp wrapper for qsort */
X
Xint starstrcmp(const void *s1, const void *s2) {
X	return strcmp(*(char **)s1, *(char **)s2);
X}
X
X/* tests to see if pathname begins with "/", "./", or "../" */
X
Xint isabsolute(char *path) {
X	return path[0] == '/' || (path[0] == '.' && (path[1] == '/' || (path[1] == '.' && path[2] == '/')));
X}
X
X/* write a given buffer allowing for partial writes from write(2) */
X
Xvoid writeall(int fd, char *buf, SIZE_T remain) {
X	int i;
X
X	for (i = 0; remain > 0; buf += i, remain -= i)
X		i = write(fd, buf, remain);
X}
X
X/* clear out z bytes from character string s */
X
Xvoid clear(char *s, SIZE_T z) {
X	while (z != 0)
X		s[--z] = 0;
X}
X
X/* zero out the fifo queue, removing the fifos from /tmp as you go (also prints errors arising from signals) */
X
Xvoid empty_fifoq() {
X	int sp;
X
X	while (fifoq != NULL) {
X		unlink(fifoq->w);
X		wait(&sp);
X		statprint(sp);
X		fifoq = fifoq->n;
X	}
X}
X
XSIZE_T strarraylen(char **a) {
X	SIZE_T i;
X
X	for (i = 0; *a != NULL; a++)
X		i += strlen(*a) + 1;
X
X	return i;
X}
END_OF_FILE
  if test 5186 -ne `wc -c <'utils.c'`; then
    echo shar: \"'utils.c'\" unpacked with wrong size!
  fi
  # end of 'utils.c'
fi
if test -f 'var.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'var.c'\"
else
  echo shar: Extracting \"'var.c'\" \(5802 characters\)
  sed "s/^X//" >'var.c' <<'END_OF_FILE'
X/* var.c: provide "public" functions for adding and removing variables from the symbol table */
X
X#include "rc.h"
X#include "utils.h"
X#include "hash.h"
X#include "list.h"
X#include "footobar.h"
X#include "nalloc.h"
X#include "status.h"
X#include "glom.h"
X
X#ifdef READLINE /* need to reset readline() every time TERM or TERMCAP changes */
Xextern void rl_reset_terminal(char *);
X#endif
X
Xstatic void colonassign(char *, List *, boolean);
Xstatic void listassign(char *, List *, boolean);
Xstatic int hasalias(char *);
X
Xstatic char *const aliases[] = {
X	"home", "HOME", "path", "PATH", "cdpath", "CDPATH"
X};
X
X/* assign a variable in List form to a name, stacking if appropriate */
X
Xvoid varassign(char *name, List *def, boolean stack) {
X	Variable *new;
X	List *newdef = listcpy(def); /* important to do the listcpy first; get_var_place() frees old values */
X
X	new = get_var_place(name, stack);
X	new->def = newdef;
X	new->extdef = NULL;
X
X#ifdef READLINE /* need to reset readline() every time TERM or TERMCAP changes */
X	if (interactive && streq(name, "TERM") || streq(name, "TERMCAP"))
X		rl_reset_terminal(NULL);
X#endif
X}
X
X/* assign a variable in string form. Check to see if it is aliased (e.g., PATH and path) */
X
Xboolean varassign_string(char *extdef) {
X	char *name = get_name(extdef);
X	Variable *new;
X	int i;
X	static boolean aliasset[arraysize(aliases)] = {
X		FALSE, FALSE, FALSE, FALSE, FALSE, FALSE
X	};
X
X	if (name == NULL)
X		return FALSE; /* add it to bozo env */
X
X	i = hasalias(name);
X	if (i >= 0) {
X		aliasset[i] = TRUE;
X		if ((i & 1 == 0) && aliasset[i^1])
X			return TRUE; /* don't alias variables that are already set in upper case */
X	}
X	new = get_var_place(name, FALSE);
X	new->def = NULL;
X	new->extdef = ealloc(strlen(extdef) + 1);
X	strcpy(new->extdef, extdef);
X	if (hasalias(name) != -1)
X		alias(name, varlookup(name), FALSE);
X	return TRUE;
X}
X
X/*
X   Return a List based on a name lookup. If the list is in external (string) form,
X   convert it to internal (List) form. Treat $n (n is an integer) specially as $*(n).
X   Also check to see if $status is being dereferenced. (we lazily evaluate the List
X   associated with $status)
X*/
X
XList *varlookup(char *name) {
X	Variable *look;
X	List *ret, *l;
X	int sub;
X
X	if (streq(name, "status"))
X		return sgetstatus();
X
X	if (*name != '\0' && (sub = a2u(name)) != -1) { /* handle $1, $2, etc. */
X		for (l = varlookup("*"); l != NULL && sub != 0; --sub)
X			l = l->n;
X		if (l == NULL)
X			return NULL;
X		ret = nnew(List);
X		ret->w = l->w;
X		ret->m = NULL;
X		ret->n = NULL;
X		return ret;
X	}
X
X	look = lookup_var(name);
X
X	if (look == NULL)
X		return NULL; /* not found */
X	if (look->def != NULL)
X		return look->def;
X	if (look->extdef == NULL)
X		return NULL; /* variable was set to null, e.g., a=() echo foo */
X
X	ret = parse_var(name, look->extdef);
X
X	if (ret == NULL) {
X		look->extdef = NULL;
X		return NULL;
X	}
X	return look->def = ret;
X}
X
X/* lookup a variable in external (string) form, converting if necessary. Used by makeenv() */
X
Xchar *varlookup_string(char *name) {
X	Variable *look;
X
X	look = lookup_var(name);
X
X	if (look == NULL)
X		return NULL;
X	if (look->extdef != NULL)
X		return look->extdef;
X	if (look->def == NULL)
X		return NULL;
X	return look->extdef = list2str(name, look->def);
X}
X
X/* remove a variable from the symtab. "stack" determines whether a level of scoping is popped or not */
X
Xvoid varrm(char *name, boolean stack) {
X	int i = hasalias(name);
X
X	if (streq(name, "*") && !stack) { /* when assigning () to $*, we want to preserve $0 */
X		varassign("*", varlookup("0"), FALSE);
X		return;
X	}
X		
X	delete_var(name, stack);
X	if (i != -1)
X		delete_var(aliases[i^1], stack);
X}
X
X/* assign a value (List) to a variable, using array "a" as input. Used to assign $* */
X
Xvoid starassign(char *dollarzero, char **a, boolean stack) {
X	List *s, *var;
X
X	var = nnew(List);
X	var->w = dollarzero;
X
X	if (*a == NULL) {
X		var->n = NULL;
X		varassign("*", var, stack);
X		return;
X	}
X
X	var->n = s = nnew(List);
X
X	while (1) {
X		s->w = *a++;
X		if (*a == NULL) {
X			s->n = NULL;
X			break;
X		} else {
X			s->n = nnew(List);
X			s = s->n;
X		}
X	}
X	varassign("*", var, stack);
X}
X
X/* (ugly name, huh?) assign a colon-separated value to a variable (e.g., PATH) from a List (e.g., path) */
X
Xstatic void colonassign(char *name, List *def, boolean stack) {
X	char *colondef;
X	List dud;
X	SIZE_T deflen;
X	List *r;
X
X	if (def == NULL) {
X		varassign(name, NULL, stack);
X		return;
X	}
X
X	deflen = listlen(def) + 1; /* one for the null terminator */
X
X	colondef = nalloc(deflen);
X	strcpy(colondef, def->w);
X
X	for (r = def->n; r != NULL; r = r->n) {
X		strcat(colondef, ":");
X		strcat(colondef, r->w);
X	}
X
X	dud.w = colondef;
X	dud.n = NULL;
X	varassign(name, &dud, stack);
X}
X
X/* assign a List variable (e.g., path) from a colon-separated string (e.g., PATH) */
X
Xstatic void listassign(char *name, List *def, boolean stack) {
X	List *val, *r;
X	char *v, *w;
X
X	if (def == NULL) {
X		varassign(name, NULL, stack);
X		return;
X	}
X
X	v = def->w;
X
X	r = val = enew(List);
X
X	while((w = strchr(v,':')) != NULL) {
X		*w = '\0';
X		r->w = ecpy(v);
X		*w = ':';
X		v = w + 1;
X		r->n = enew(List);
X		r = r->n;
X	}
X	r->w = ecpy(v);
X	r->n = NULL;
X
X	varassign(name, val, stack);
X}
X
X/* check to see if a particular variable is aliased; return -1 on failure, or the index */
X
Xstatic int hasalias(char *name) {
X	int i;
X
X	for (i = 0; i < arraysize(aliases); i++)
X		if (streq(name, aliases[i]))
X			return i;
X	return -1;
X}
X
X/* alias a variable to its lowercase equivalent. function pointers are used to specify the conversion function */
X
Xvoid alias(char *name, List *s, boolean stack) {
X	int i = hasalias(name);
X	static void (*vectors[])(char *, List *, boolean) = {
X		varassign, varassign, colonassign, listassign, colonassign, listassign
X	};
X
X	if (i != -1)
X		vectors[i](aliases[i^1], s, stack); /* xor hack to reverse case of alias entry */
X}
END_OF_FILE
  if test 5802 -ne `wc -c <'var.c'`; then
    echo shar: \"'var.c'\" unpacked with wrong size!
  fi
  # end of 'var.c'
fi
if test -f 'which.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'which.c'\"
else
  echo shar: Extracting \"'which.c'\" \(2968 characters\)
  sed "s/^X//" >'which.c' <<'END_OF_FILE'
X/* which.c: check to see if a file is executable.
X
X   This function is isolated from the rest because of #include trouble with
X   ANSI compilers.
X
X   This function was originally written with Maarten Litmaath's which.c as
X   a template, but was changed in order to accomodate the possibility of
X   rc's running setuid or the possibility of executing files not in the
X   primary group. Much of this file has been re-vamped by Paul Haahr.
X   I re-re-vamped the functions that Paul supplied to correct minor bugs
X   and to strip out unneeded functionality.
X*/
X
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <sys/param.h>
X#include "builtins.h"
X
X#define M_USR 0700
X#define M_GRP 0070
X#define M_OTH 0007
X#define X_ALL 0111
X
X#ifndef NULL
X#define NULL 0
X#endif
X
Xtypedef struct List {
X	char *w;
X	char *m;
X	struct List *n;
X} List;
X
Xextern int stat(const char *, struct stat *);
Xextern int geteuid(void);
Xextern int getegid(void);
Xextern int getgroups(int, int *);
Xextern char *strcpy(char *, char *);
Xextern char *strcat(char *, char *);
Xextern int strlen(char *);
X
Xextern List *varlookup(char *);
Xextern void *ealloc(int);
Xextern void efree(void *);
Xextern int isabsolute(char *);
X
Xstatic int initialized = 0;
Xstatic int uid, gid;
X
X#ifdef NGROUPS
Xstatic int ngroups, gidset[NGROUPS];
X
X/* determine whether gid lies in gidset */
X
Xstatic int ingidset(int gid) {
X	int i;
X	for (i = 0; i < ngroups; i++)
X		if (gid == gidset[i])
X			return 1;
X	return 0;
X}
X#endif
X
X/*
X   A home-grown access/stat. Does the right thing for group-executable files.
X   Returns a boolean result instead of this -1 nonsense.
X*/
X
Xstatic int rc_access(char *path) {
X	struct stat st;
X	int mask;
X
X	if (stat(path, &st) != 0)
X		return 0;
X
X	mask = X_ALL;
X
X	if (uid != 0) {
X		if (uid == st.st_uid)
X			mask &= M_USR;
X#ifdef NGROUPS
X		else if (gid == st.st_gid || ingidset(st.st_gid))
X#else
X		else if (gid == st.st_gid)
X#endif
X			mask &= M_GRP;
X		else
X			mask &= M_OTH;
X	}
X	
X	return (st.st_mode & S_IFMT) == S_IFREG && (st.st_mode & mask) == mask;
X}
X
X/* return a full pathname by searching $path, and by checking the status of the file */
X
Xchar *which(char *name) {
X	static char *test = NULL;
X	static int testlen = 0;
X	List *path;
X	int len;
X
X	if (name == NULL)	/* no filename? can happen with "> foo" as a command */
X		return NULL;
X
X	if (!initialized) {
X		initialized = 1;
X		uid = geteuid();
X		gid = getegid();
X#ifdef NGROUPS
X		ngroups = getgroups(NGROUPS, gidset);
X#endif
X	}
X
X	if (isabsolute(name)) /* absolute pathname? */
X		return rc_access(name) ? name : NULL;
X
X	len = strlen(name);
X	for (path = varlookup("path"); path != NULL; path = path->n) {
X		int need = strlen(path->w) + len + 2; /* one for null terminator, one for the '/' */
X		if (testlen < need) {
X			efree(test);
X			test = ealloc(testlen = need);
X		}
X		if (*path->w == '\0') {
X			strcpy(test, name);
X		} else {
X			strcpy(test, path->w);
X			strcat(test, "/");
X			strcat(test, name);
X		}
X		if (rc_access(test))
X			return test;
X	}
X	return NULL;
X}
END_OF_FILE
  if test 2968 -ne `wc -c <'which.c'`; then
    echo shar: \"'which.c'\" unpacked with wrong size!
  fi
  # end of 'which.c'
fi
echo shar: End of archive 3 \(of 4\).
cp /dev/null ark3isdone
MISSING=""
for I in 1 2 3 4 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 4 archives.
    rm -f ark[1-9]isdone
else
    echo You still must unpack the following archives:
    echo "        " ${MISSING}
fi
exit 0
exit 0 # Just in case...
-- 
Kent Landfield                   INTERNET: kent@sparky.IMD.Sterling.COM
Sterling Software, IMD           UUCP:     uunet!sparky!kent
Phone:    (402) 291-8300         FAX:      (402) 291-4362
Please send comp.sources.misc-related mail to kent@uunet.uu.net.