/*
 *	Copyright 1989 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 */

/*
 * Shell Variable maintenance routines.
 */

#include "hostenv.h"
#ifdef	MAILER
#include "sift.h"	/* Include this BEFORE "mailer.h" ! */
#endif	/* MAILER */

#include "mailer.h"
#include "sh.h"
#include "flags.h"
#include "shconfig.h"

#include "libsh.h"

#ifdef	MAILER

int v_record = 0;	/* record variable accesses */
int v_changed = 0;	/* a variable in v_accessed was changed */
struct vaccess *v_accessed = NULL;

extern char **environ; /* Should be declated in  <stdlib.h>, but isn't.. */

void
v_written(l)
	register conscell *l;
{
	register struct vaccess *va;

	for (va = v_accessed; va != NULL; va = va->next)
		if (va->l == l) {
			++v_changed;
			return;
		}
}

void
v_touched()
{
	if (v_accessed)
		++v_changed;
}
#endif	/* MAILER */


/*
 * Shell variables and their values are stored in p-lists (property lists),
 * one property list per scope.  The envarlist variable is a list of these
 * property lists, the local scope at the car of the value.  There are usually
 * two scopes, the global scope and the exported scope (super-global, if you
 * will).  So envarlist looks something like
 *	(closest-localscope-plist ... global-plist export-plist)
 * Each property list has the standard format, i.e. alternating name value
 * pairs in a flat structure.  The envarlist itself is stored in the global
 * scope under the ENVIRONMENT name, I can't recall why except the pleasing
 * nature of the recursion (well, it might be useful some day).
 * Any variables in the export-plist will be in the environment of programs
 * started by the shell.
 */

conscell *envarlist = NULL;

/*
 * Certain mechanisms inside the shell need very frequent access to specific
 * variable values.  Instead of doing a relatively expensive lookup every
 * time, we keep a list of variables that should cause a function call whenever
 * they change (aka metered variables).  This concept should perhaps be
 * extended later to be able to call defined functions.
 */

STATIC struct vsync {
	const char	*v_name;		/* variable name */
	int	v_hash;			/* computed hash value of name */
	void	(*v_flush) __((void));	/* function to call on change */
} vcheck[] = {
{	PATH,		0,		path_flush		},
{	PS1,		0,		prompt_flush		},
{	PS2,		0,		prompt2_flush		},
{	MAIL,		0,		mail_flush		},
{	MAILPATH,	0,		mail_flush		},
{	MAILCHECK,	0,		mail_intvl		},
{	IFS,		0,		ifs_flush		},
};


/*
 * The ifs_flush() routine is defined below, it maintains this global value:
 */

char *ifs;		/* input file separator characters */

/*
 * Find the value of a named variable by looking through the scoped p-lists.
 */

conscell *
v_find(name)
	const char * name;
{
	register conscell *l, *pl, *scope;
	int nlen = strlen(name);

	if (name == NULL) return NULL; /* No input name, no output var.. */

	/* if (fvcache.namesymbol > 0 && fvcache.namesymbol == symbol(name))
		return fvcache.location; */

	pl = NULL;
	for (scope = car(envarlist); scope != NULL; scope = cdr(scope)) {
		for (l = car(scope); l != NULL; pl = cdr(l), l = cddr(l)) {
			if (l->slen == nlen &&
			    memcmp(name, l->cstring, nlen) == 0){
				if (l != car(scope)) {
					/* move it to start of scope plist */
					cdr(pl) = cddr(l);
					cddr(l) = car(scope);
					car(scope) = l;
				}
				/* fvcache.namesymbol = symbol(name);
				fvcache.location = l; */
#ifdef	MAILER
				if (v_record) {
					struct vaccess *v;

					v = (struct vaccess *)
					    emalloc(sizeof (struct vaccess));
					v->l = l;
					v->next = v_accessed;
					v_accessed = v;
				}
#endif	/* MAILER */
				return l;
			}
		}
	}
	return NULL;
}

/*
 * Variable expansion, handles all the special symbols in addition to the
 * normal variable names.  The value returned is always a copy of the
 * actual stored variable value, so destructive manipulation of the return
 * value is okay.
 */


#define ALWAYS_QUOTE_EXPANDS


conscell *
v_expand(s, caller, retcode)
	const char *s;			/* variable name */
	struct osCmd *caller;		/* caller, for $@ */
	int retcode;			/* last return code, for $? */
{
	conscell *d = NULL, *l = NULL, *tmp = NULL;
	register int n;
	register char *cp;
	char np[CHARSETSIZE+1]; /* each possible option, plus last NUL */
	int noquote = 0;

	GCVARS3;
	GCPRO3(d,l,tmp);

	/* fprintf(stderr,"v_expand('%s',...,retcode=%d)\n",s,retcode); */

	/*
	 * We only need to test the first character since the parser
	 * is supposed to enforce variable name syntax (so to speak).
	 */
	switch (*s) {
	case '@':
		noquote = NOQUOTEIFQUOTED;
	case '*':
		if (caller == NULL || cdar(caller->argv) == NULL) {
			goto end_v_expand;
		}
		d = s_copy_chain(cdar(caller->argv));
		for (l = d; l != NULL && cdr(l) != NULL ; l = cdr(l)) {
			tmp = conststring(" ",1);
			cdr(tmp) = cdr(l);
			cdr(l)   = tmp;
			l        = cdr(l);
			tmp->flags |= noquote;
#ifdef ALWAYS_QUOTE_EXPANDS
			if (!noquote)
			  tmp->flags |= QUOTEDSTRING;
#endif
		}
		/* grindef("ARGW = ", ncons(d)); */
		goto end_v_expand;

	case '0': case '1': case '2': case '3': case '4':
	case '5': case '6': case '7': case '8': case '9':
		if (caller == NULL) {
			d = NULL;
			goto end_v_expand;
		}
		if ((d = s_nth(caller->argv, atoi(s))) == NULL) {
			goto end_v_expand;
		}
		d = copycell(d);
		cdr(d) = NULL;
#ifdef ALWAYS_QUOTE_EXPANDS
		d->flags |= QUOTEDSTRING;
#endif
		/* d = s_copy_tree(d); */ /* XXX: Needed ? */
		goto end_v_expand;

	case '#':
		if (*++s == '\0')
			d = car(caller->argv), n = -1;
		else if ((d = v_find(s)) == NULL || !LIST(cdr(d))) {
			UNGCPRO3;
			return NULL;
		} else
			d = cadr(d), n = 0;
		while (d != NULL)
			d = cdr(d), ++n;
		/* print n into a string */
		sprintf(np, "%d", n);
		n = strlen(np);
		d = newstring(dupnstr(np,n),n);
		goto end_v_expand;

	case '$':
		sprintf(np, "%d", (int)getpid());
		n = strlen(np);
		d = newstring(dupnstr(np,n),n);
		goto end_v_expand;

	case '?':
		if (retcode == -123456) {
			fprintf(stderr,
				"%s: BAD magic retcode on $? expansion!\n",
				progname);
			abort(); /* Bad magic retcode on $? expansion! */
		}
		sprintf(np, "%d", retcode);
		n = strlen(np);
		d = newstring(dupnstr(np,n),n);
		goto end_v_expand;

	case '!':
		sprintf(np, "%d", lastbgpid);
		n = strlen(np);
		d = newstring(dupnstr(np,n),n);
		goto end_v_expand;

	case '-':
		cp = np;
		for (n = 0; n < 256; ++n)
			if (isset(n))
				*cp++ = (char)n;
		if (cp == np)
			*cp++ = '-';
		*cp = '\0';
		n = strlen(np);
		d = newstring(dupnstr(np,n),n);
		goto end_v_expand;

	default:
		break;
	}

	if ((d = v_find(s)) != NULL) {
		d = copycell(cdr(d));
		cdr(d) = NULL;
#ifdef ALWAYS_QUOTE_EXPANDS
		if (STRING(d))
		  d->flags |= QUOTEDSTRING;
#endif
	}
 end_v_expand:

	/* grindef("  d = ", d); */

	UNGCPRO3;

	return d;
}


/*
 * Maintain the ifs value and scanner syntax table whenever IFS changes.
 */

void
ifs_flush()
{
	conscell *d;

	d = v_find(IFS);
	if (d == NULL || cdr(d) == NULL || LIST(cdr(d)))
		return;
	ifs = cdr(d)->string;
	ShInitIFS(ifs);
}

/*
 * This routine is called on any variable assignment, to ensure that program
 * state is kept synchronized whenever relevant shell variables change.  There
 * is a comment about that at the top of this file.  Since this function is
 * called quite frequently and the list of variables to keep an eye on might
 * be quite long, we need a fast lookup method.  The hash values will be
 * unique for all variable names smaller than N characters long, where
 * N ~= # bits in int - # bits in CHARSETSIZE.  This is usually 24 on 32-bit
 * machines, which means the strcmp will almost always succeed.
 */

void
v_sync(name)
	const char *name;
{
	register unsigned int i;
	register int hash, j;
	register const char *cp;

	if (name == NULL)
		return;
	if (vcheck[0].v_hash == 0) {	/* just once */
		for (i = 0; i < sizeof vcheck / sizeof vcheck[0]; ++i) {
			cp = vcheck[i].v_name;
			hash = 0;
			for (j = CHARSETSIZE; *cp != '\0'; ++cp, j *= 2)
				hash += (*cp + j);
			vcheck[i].v_hash = hash;
		}
	}
	for (cp = name, j = CHARSETSIZE, hash = 0; *cp != '\0'; ++cp, j *= 2)
		hash += (*cp + j);
	for (i = 0; i < sizeof vcheck / sizeof vcheck[0]; ++i) {
		if (vcheck[i].v_hash == hash &&
		    strcmp(name, vcheck[i].v_name) == 0) {
			(vcheck[i].v_flush)();
			return;
		}
	}
}

/*
 * Easy interface to the assign() routine.
 */

void
v_setl(variable, value)
	const char *variable;
	conscell *value;
{
	int slen = strlen(variable);
	conscell *lhs = newstring(dupnstr(variable,slen),slen);
	GCVARS1;
	GCPRO1(lhs);
	assign(lhs, value, (struct osCmd *)NULL);
	UNGCPRO1;
}

/*
 * Easy interface to the assign() routine.
 */

void
v_set(variable, value)
	const char *variable, *value;
{
	conscell *rhs;
	GCVARS1;
	int slen = strlen(value);

	rhs = newstring(dupnstr(value,slen),slen);
	GCPRO1(rhs);
	v_setl(variable, rhs);
	UNGCPRO1;
}

/*
 * This function is called once at startup to initialize the shell variables
 * and the scope p-lists and such.
 */

void
v_envinit()
{
	conscell *s = NULL, *e = NULL;
	register char	**cpp, *cp;
	int gotpath, slen;
	memtypes oval;
	GCVARS2;

	GCPRO2(s, e);

	oval = stickymem;
	stickymem = MEM_MALLOC;
	envarlist = NIL;
	staticprot(&envarlist); /* Register this pointer to *all*
				   variables in use! */
	gotpath = 0;
	for (cpp = environ; cpp != NULL && *cpp != NULL; ++cpp) {

		cp = strchr(*cpp, '=');
		if (cp == NULL)
			continue;
		*cp++ = '\0';
		if (strcmp(*cpp, PATH) == 0)
			++gotpath;
		else if (strcmp(*cpp, IFS) == 0)
			continue;	/* don't inherit IFS */
		slen = strlen(*cpp);
		s = newstring(dupnstr(*cpp,slen),slen);
		nconc(envarlist, s);
		slen = strlen(cp);
		s = newstring(dupnstr(cp,slen),slen);
		nconc(envarlist, s);
		*--cp = '=';
	}
	if (!gotpath) {
		s = conststring(PATH,sizeof(PATH)-1);
		nconc(envarlist, s);
		s = conststring(DEFAULT_PATH,sizeof(DEFAULT_PATH)-1);
		nconc(envarlist, s);
	}
	/* now make it into a list of plists */
	e = s_copy_chain(car(envarlist));
	envarlist = ncons(envarlist);	/* ((env)) */
	/* ... and prepend the normal scope */
	/* ... and put it in the list of pre-defined non-env. variables */
	s = conststring(ENVIRONMENT,sizeof(ENVIRONMENT)-1);
	cdr(s) = envarlist;	/* must only use s_push with envarlist now */
	cdr(s_last(e)) = s;	/* (envcopy ENVIRONMENT (env))		*/
	s = ncons(e);		/* s = (envcopy ENVIRONMENT (env))	*/
	s_push(s, envarlist);	/* ((envcopy ENVIRONMENT (env)) (env))	*/
	s = NULL;

	stickymem = oval;
	UNGCPRO2;
}


/*
 * Ensure that a specific variable is going to be exported to programs.
 */

void
v_export(name)
	register const char	*name;
{
	conscell *l, *pl, *scope, *value;

	value = NULL;
	pl = NULL;
	/* find the first value of this variable that isn't exported */
	for (scope = car(envarlist); cdr(scope) != NULL; scope = cdr(scope)) {
		for (l = car(scope); l != NULL; pl = cdr(l), l = cddr(l)) {
			if (*name == *(l->string)
			    && name[1] == l->string[1]
			    && (name[1] == '\0'
				|| strcmp(name, l->string) == 0)) {
				if (l == car(scope))
					car(scope) = cddr(l);
				else
					cdr(pl) = cddr(l);
				cddr(l) = NULL;
				if (value == NULL)
					value = l;
				/* else
				   s_free_tree(l); */ /* GC work.. */
			}
		}
	}
	/* now search for the already-exported value */
	for (l = car(scope); l != NULL; pl = cdr(l), l = cddr(l)) {
		if (*name == *(l->string) && name[1] == l->string[1]
		    && (name[1] == '\0' || strcmp(name, l->string) == 0)) {
			/* it is already being exported */
			if (value == NULL)
				return;
			if (l == car(scope))
				car(scope) = value;
			else
				cdr(pl) = value;
			cddr(value) = cddr(l);
			cddr(l) = NULL;
			/* s_free_tree(l); */
			return;
		}
	}
	/* it isn't being exported */
	if (value == NULL) {
		GCVARS1;
		int slen = strlen(name);
		value = newstring(dupnstr(name,slen),slen);
		GCPRO1(value);
		cdr(value) = conststring("",0);
		UNGCPRO1;
	}
	cddr(value) = car(scope);
	car(scope) = value;
}

/*
 * Purge the dynamically closest instance of a variable.  This is intended
 * for undoing the effect of temporary variable assignments when the
 * variable didn't exist in advance.
 */

void
v_purge(name)
	const char * name;
{
	register conscell *l, *pl, *scope;

	pl = NULL;
	for (scope = car(envarlist); scope != NULL; scope = cdr(scope)) {
		for (l = car(scope); l != NULL; pl = cdr(l), l = cddr(l)) {
			if (strcmp(name, l->string) == 0) {
				if (l == car(scope))
					car(scope) = cddr(l);
				else
					cdr(pl) = cddr(l);
                                /* free it ... by dissociating the tail,
                                   GC does freeup.. */
                                cddr(l) = NULL;
				return;
			}
		}
	}
}


syntax highlighted by Code2HTML, v. 0.9.1