/* support for environment variables */

#include <stdio.h>
#include <ctype.h>

#include "global.h"
#include "environ.h"

extern char startup[],on_exit[],userfile[],hosts[],fingerpath[],mailspool[],
	    mailqdir[],routeqdir[],alias[],tmpdir[];

/* default names and values for environment vars */

char STARTUP[]	  = "NETSTART";		/* Initialization file */
char ONEXIT[]	  = "NETEXIT";		/* Initialization file */
char USERFILE[]	  = "NETUSERS";		/* Authorized FTP users and passwords */
char HOSTS[]	  = "NETHOSTS";		/* Network host table */
char FINGERPATH[] = "NETFINGER";	/* Finger file path */
char MAILSPOOL[]  = "NETMAILS";		/* Incoming mail */
char MAILQDIR[]	  = "NETMAILQ";		/* Outgoing mail spool */
char ROUTEQDIR[]  = "NETROUTEQ";	/* queue for router */
char ALIAS[]	  = "NETALIAS";		/* the alias file */
#ifdef ATARI_ST
char TIMEZONE[]	  = "TIMEZONE";		/* local time zone */
char TMPDIR[]	  = "TMPDIR";		/* temp files directory */
char ATARI_STYLE[]= "ENVSTYLE";		/* Environment style DOS/Atari */
#endif
#ifdef MSDOS
char TIMEZONE[]	  = "TZ";		/* local time zone */
#endif

struct env_default env_defs[] = {
	STARTUP,	startup,	/* Initialization file */
	ONEXIT,		on_exit,	/* Termination file */
	USERFILE,	userfile,	/* Authorized FTP users and passwords */
	HOSTS,		hosts,		/* Network host table */
	FINGERPATH,	fingerpath,	/* Finger file path */
	MAILSPOOL,	mailspool,	/* Incoming mail */
	MAILQDIR,	mailqdir,	/* Outgoing mail spool */
	ROUTEQDIR,	routeqdir,	/* queue for router */
	ALIAS,		alias,		/* the alias file */
#ifdef ATARI_ST
	TIMEZONE,	"GMT:0",	/* default local timezone */
	TMPDIR,		tmpdir,		/* temp files directory */
#endif
#ifdef MSDOS
	TIMEZONE,	"GMT0",		/* default local timezone */
#endif
	NULLCHAR, NULLCHAR
};

struct env_var *the_env = NULLENV;
char **env_envp = NULLCHRP;		/* stores last envp from make_env */
static char **env_wenvp;		/* working copy for fill_env */
char *env_env = NULLCHAR;		/* stores last env from make_env */
static char *env_wenv;			/* working copy for fill_env */

#ifdef ATARI_ST
static int atari_style = 1;		/* Atari-style (not MSDOS) environment */
#endif

extern char nospace[];			/* No space!! message */

/* lookup/create an environment-variable node */
static struct env_var *
env_node (name,creat)
char *name;
int creat;
{
	register struct env_var **env_pp = &the_env;
	register char *p,*q;
	int r;

	while (*env_pp != NULLENV) {
	    p = (*env_pp)->name;	/* compare current name */
	    q = name;			/* with searched name */

	    while (*p == *q) {
		if (*p == '\0' || *q == '='){
		    break;
		}
		p++;
		q++;
	    }

	    if (*q == '=')		/* test end-result */
		r = -*p;
	    else
		r = *q - *p;

	    if (!r)			/* found it? */
		return *env_pp;		/* then return the node */

	    if (r < 0)			/* smaller? */
		env_pp = &(*env_pp)->left;	/* go left */
	    else
		env_pp = &(*env_pp)->right;	/* else go right */
	}

	/* not found, create a new node? */

	if (!creat)
	    return NULLENV;

	for (p = name; *p != '\0' && *p != '='; p++) /* find namelength */
	    ;

	if ((*env_pp = (struct env_var *)
		calloc(1,sizeof(struct env_var) + p - name)) == NULLENV){
	    printf(nospace);
	    return NULLENV;
	}

	memcpy((*env_pp)->name,name,p - name);	/* copy name */

	return *env_pp;				/* return created node */
}

/* read the value of an environment variable (of the local copy, that is) */
/* it returns a pointer to the actual value, so you can do multiple calls */
/* to getenv, and save the resulting pointers. (the Aztec getenv returns  */
/* a pointer to a static area overwritten by each call, UNIX(tm) doesn't) */
char *getenv (name)
#if (defined(MSC) || defined(__TURBOC__))
const					/* agree with stdlib.h declaration */
#endif
char *name;
{
	register struct env_var *env_p;

	if ((env_p = env_node(name,0)) == NULLENV)
	    return NULLCHAR;		/* no space, does not exist */

	return env_p->value;		/* return value or NULL */
}

/* read the value of an environment variable (of the local copy, that is) */
/* same as getenv(), but return "" instead of NULL when it does not exist */
char *getnenv (name)
char *name;
{
	char *rv;

	if ((rv = getenv(name)) == NULL)
	    rv = "";			/* NULL replaced by "" */

	return rv;
}

/* set the value of an environment variable */
struct env_var *
setenv (name,value)
char *name;
char *value;
{
	register struct env_var *env_p;

	if ((env_p = env_node(name,1)) == NULLENV)
	    return NULLENV;		/* no space */

	if (env_p->value_alloc)
	    free(env_p->value);

	env_p->value = value;
	return env_p;
}

/* read the supplied environment at program startup */
void
readenv (envp)
char *envp[];
{
	register int i;
	register char *p;
	char *name,*value;
	struct env_default *defs;

	/* read the fixed default values */

	for (defs = env_defs; defs->name != NULLCHAR; defs++)
	    setenv(defs->name,defs->value);

	/* then, override these with environment values */

#if (defined(MSDOS) && !defined(MSC) && !defined(__TURBOC__))
	/* special treatment for Aztec C which does not pass envp to main() */
	{
	    extern short _PSP;		/* segment of the PSP */
	    char *buf[2];
	    char far *fp;

	    if ((buf[1] = malloc(512)) == NULLCHAR) /* allocate a near buf */
		return;

	    /* get far ptr to env, whose SEG is in 22'th WORD of the PSP */
	    fp = (char far *) ((long) (((short far *) ((long) _PSP << 16))[22]) << 16);

	    while (*fp != '\0'){	/* scan the env */
		p = buf[1];
		while ((*p++ = *fp++) != '\0') /* copy a NAME=value line */
		   ;			/* to a near buffer (8086...) */

		if (isalpha(*buf[1]))	/* is it a valid name? */
		    dosetenv(2,buf);	/* than fake a setenv command */
	    }

	    free(buf[1]);
	}
#else
	/* all others use the third arg of main() (envp) */

	if (envp == NULLCHRP)		/* no environment? */
	    return;			/* nothing to read... */

	for (i = 0; (name = envp[i]) != NULLCHAR; i++) {
	    value = NULLCHAR;
	    for (p = name; *p != '\0'; p++)
		if (*p == '=') {
#ifdef ATARI_ST
		    if (p[1] != '\0')	/* when no \0, it is MSDOS-style */
			atari_style = 0;
#endif
		    value = p + 1;	/* MSDOS-style has value after = */
		    break;
		}

#ifdef ATARI_ST
	    if (atari_style)		/* Atari-style has separate value */
		if ((value = envp[++i]) == NULLCHAR) /* get the value */
		    i--;		/* protect against passing end of env */

	    if (!strncmp(name,"ARGV",p - name))
		continue;		/* ignore ARGV= var on Atari */
#endif
#ifdef MSDOS
	    if (!isalpha(name[0]))	/* ignore funny var names */
		continue;
#endif

	    if (value != NULLCHAR)
		setenv(name,value);
	}
#endif

#ifdef ATARI_ST
	if (atari_style)
	    setenv(ATARI_STYLE,"1");	/* define atari-style env */
#endif
}

/* get the number of items in the currently stored environment */
static unsigned
env_count(env_v)
register struct env_var *env_v;
{
	if (env_v == NULLENV)			/* end of tree? */
	    return 0;

	return (env_count(env_v->left) +	/* return sum of all nodes */
		((env_v->value != NULLCHAR)? 1 : 0) +
		env_count(env_v->right));
}

/* get the size of the currently stored environment */
static unsigned
env_size(env_v)
register struct env_var *env_v;
{
	if (env_v == NULLENV)			/* end of tree? */
	    return 0;

	return (env_size(env_v->left) +		/* return sum of all nodes */
		((env_v->value != NULLCHAR)?
			(strlen(env_v->name) + strlen(env_v->value) + 2)
#ifdef ATARI_ST
			+ atari_style		/* Atari-style requires extra byte */
#endif
		:
			0) +
		env_size(env_v->right));
}

/* fill a buffer with the environment vars */
static
fill_env(env_v)
register struct env_var *env_v;
{
	register char *p;

	if (env_v != NULLENV) {			/* not end of tree? */
	    fill_env(env_v->left);

	    if (env_v->value != NULLCHAR){	/* not unset? */
		*env_wenvp++ = env_wenv;	/* put pointer in envp array */

		for (p = env_v->name; *p != '\0'; ) /* store name */
		    *env_wenv++ = *p++;

		*env_wenv++ = '=';
#ifdef ATARI_ST
		if (atari_style)
		    env_wenv++;			/* put that extra \0 */
#endif

		for (p = env_v->value; *p != '\0'; ) /* store value */
		    *env_wenv++ = *p++;

		env_wenv++;			/* term by \0 */
	    }

	    fill_env(env_v->right);
	}
}

/* allocate and fill a contiguous buffer with the environment vars */
char **
make_env()
{
#ifdef ATARI_ST
	atari_style = atoi(getnenv(ATARI_STYLE)) & 1; /* set env style */
#endif

	if ((env_env = calloc(1,env_size(the_env) + 1 + 15)) == NULLCHAR) {
	    printf("cannot alloc mem for env\n");
	    return NULLCHRP;
	}

	if ((env_envp = (char **) calloc(sizeof(char *),env_count(the_env) + 1)) == NULLCHRP) {
	    printf("cannot alloc mem for envp\n");
	    return NULLCHRP;
	}

#ifdef MSDOS
	/* MS-DOS likes to have the environment on a paragraph boundary, */
	/* so that it can be referenced using only a segment address, */
	/* and always offset 0.	 yuck. */
#ifdef LARGEDATA
	env_wenv = (char *) (((((unsigned) env_env + 0x0f) >> 4) + ((unsigned long) env_env >> 16)) << 16);
#else
	env_wenv = (char *) (((unsigned) env_env + 0x0f) & ~0x0f);
#endif
#else
	env_wenv = env_env;
#endif
	env_wenvp = env_envp;

	fill_env(the_env);		/* recursively add all vars */

	return env_envp;
}

/* free the memory asociated with the last make_env call */
void
free_env ()

{
	if (env_envp != NULLCHRP)
	    free(env_envp);

	if (env_env != NULLCHAR)
	    free(env_env);

	env_envp = NULLCHRP;
	env_env = NULLCHAR;
}

/* dump contents of environment */
static int
doenvdump(env_v)
register struct env_var *env_v;
{
	register char *p;

	if (env_v != NULLENV) {			/* not end of tree? */
	    doenvdump(env_v->left);

	    if (env_v->value != NULLCHAR) {
		printf("%s=",env_v->name);

		for (p = env_v->value; *p != '\0'; p++) {
		    if (*p < ' ') {		/* a control char? */
			putchar('^');		/* show as ^char */
			putchar(*p + 64);
		    } else
			putchar(*p);
		}

		putchar('\n');
	    }

	    doenvdump(env_v->right);
	}

	return 0;
}

/* handle the "setenv" command */
int
dosetenv(argc,argv)
int argc;
char *argv[];
{
	char *value,*avalue;
	register struct env_var *env_p;
	register char *p;
	int l;

	if (argc < 2)			/* only "setenv" typed */
	    return doenvdump(the_env);

	for (p = argv[1]; *p != '\0'; p++) {
	    if (*p == '=')
		break;

	    *p = toupper(*p);		/* upcase the name */
	}

	if (*p != '\0')			/* found an =? */
	    value = p + 1;		/* then value is right after it */
	else
	    value = argv[2];		/* value is next arg, if provided */
					/* if no value provided, unset it */

	/* allocate copy of value */

	if (value != NULLCHAR) {
	    if ((avalue = malloc(l = strlen(value) + 1)) == NULLCHAR) {
		printf(nospace);
		return -1;
	    }

	    memcpy(avalue,value,l);
	} else {
	    avalue = value;
	}

	if ((env_p = setenv(argv[1],avalue)) == NULLENV)
	    return -1;			/* no space */

	if (value != NULLCHAR)
	    env_p->value_alloc = 1;	/* remember value is allocated */
	else
	    env_p->value_alloc = 0;	/* NULL value is not allocated */

	return 0;
}

