From: johnl@informix.com (Jonathan Leffler)
Date: 3 Jan 1996 19:13:04 -0500

Hi,

The problem is almost certainly in the requirements of putenv(), which
takes the pointer you provide and stashes a reference to that string in the
environment area.  If your pointer was to a functions local variable, then
the environment stored by putenv() changes everytime the space the local
variable occupied is re-used by some new function -- a probable cause of
your problems.  Even if you overcome this problem by providing malloc'd
space, if you ever reset an environment variable, then you leak the
previous malloc'd value, as putenv() doesn't release the space for you --
it can't, because it doesn't know you malloc'd it!

I attach some code which provides a leak-proof version of putenv() called
setenv().  By default, it compiled with -DRADICAL_SETENV in effect, which
means that it also provides versions of getenv(), putenv() and unsetenv().
It can also be compiled with the option -DCONSERVATIVE_SETENV, in which
case setenv() uses the system-provided putenv() to handle the actual
environment modification, but it allocates memory to copy the value
provided by the caller and keeps a record of what has been allocated, and
frees it when the value is changed.  You do not get unsetenv() in this mode
because there is no reliable way to provide it.  Note that if your code
currently handles putenv() correctly (ie, it ensures that the space it
gives to putenv() is not reused, then this implementation of putenv() gives
you a leak because it automatically makes a copy of what is passed into
putenv() and does not try to free it.  That is why setenv() is the
preferred interface -- it has different semantics from putenv().  You may
want to compile the code with -DNDEBUG to disable extensive assertion
checking -- but reenable the assertions if you find yourself making
modifications as they catch a lot of problems very quickly.  Note that this
code keeps the environment in sorted order, and uses ANSI C prototypes.
Compiling with -DTEST can create a self-contained executable which tests
the package.  Adding -DPROFILING suppresses some printing that completely
wrecks timing comparisons (eg Quantify by Pure Software) run on the code.
The code has been tested with Purify, Quantify and PureCoverage and is
reasonably well behaved with all of these -- it is memory clean with
Purify.

This code is also included in the Informix WWW software (CGI compliant)
available via http://www.informix.com (choose the Freeware option).

Yours,
Jonathan Leffler (johnl@informix.com) #include <disclaimer.h>

:	"@(#)shar.sh	1.9"
#! /bin/sh
#
#	This is a shell archive.
#	Remove everything above this line and run sh on the resulting file.
#	If this archive is complete, you will see this message at the end:
#	"All files extracted"
#
#	Created: Sat Dec 30 09:46:55 PST 1995 by johnl at Informix Software Ltd.
#	Files archived in this archive:
#	setenv.c
#
#--------------------
if [ -f setenv.c -a "$1" != "-c" ]
then echo shar: setenv.c already exists
else
echo 'x - setenv.c (14915 characters)'
sed -e 's/^X//' >setenv.c <<'SHAR-EOF'
X/*
X@(#)File:            setenv.c
X@(#)Version:         1.3
X@(#)Last changed:    95/06/27
X@(#)Purpose:         setenv(3) -- an alternative to putenv(3)
X@(#)Author:          J Leffler
X@(#)Copyright:       (C) JLSS 1995
X@(#)Product:         :PRODUCT:
X*/
X
X/*TABSTOP=4*/
X
X/*
X** SETENV(3) -- an alternative to putenv(3)
X**
X** The basic problems with putenv(3) are manifest in its definition.
X** It is given a string which it makes into part of the environment.
X** If you overwrite that string subsequently, you modify the environment.
X** If the string was allocated and you subsequently reset the environment
X** variable without modifying the previous string, you have a memory leak.
X**
X** setenv() deals with these problems in the following ways:
X** 1.	It makes a copy of the string it is passed and places the copy in
X**      the environment.
X** 2.   It tracks the environment variables it has set and releases
X**      previously allocated versions of the variable.
X**
X** setenv() is implemented in two flavours.  The default flavour is to
X** completely replace putenv() by providing a putenv() entry point which is
X** actually the same as setenv().  The alternative flavour continues to use
X** putenv() to manage the environment but keeps separate records of what it
X** has allocated.
X*/
X
X/* -- Include Files */
X
X#include <string.h>
X#include <stdlib.h>
X#include <assert.h>
X#include "setenv.h"
X
X/* -- Constant Definitions */
X
X#define SETENV_FAILURE	-1
X#define SETENV_SUCCESS	0
X#define INCREMENT_SIZE	16
X
X/* -- Configuration Definitions */
X
X/* If defined conservative, override radical; else define radical */
X#ifdef CONSERVATIVE_SETENV
X#undef RADICAL_SETENV
X#else
X#define RADICAL_SETENV
X#endif	/* CONSERVATIVE_SETENV */
X
X/* -- Macro Definitions */
X
X#ifdef RADICAL_SETENV
X#define PUTENV(val) fix_env(val)
X#else
X#define PUTENV(val) putenv(val)
X#endif	/* RADICAL_SETENV */
X
X#ifdef NDEBUG
X#define ENV_CHECK() ((void)0)
X#else
X#define ENV_CHECK() env_check()
X#endif	/* NDEBUG */
X
X#ifdef PROFILING
X#define NO_EXECUTE
X#define PRINTF(x) ((void)0)
X#else
X#define PRINTF(x) printf x
X#endif	/* PROFILING */
X
X/* -- Declarations */
X
Xextern char   **environ;
X
X#ifdef RADICAL_SETENV
Xstatic char   **env_base = (char **)0;
Xstatic int      env_size = 0;
Xstatic int      env_used = 0;
X#endif	/* RADICAL_SETENV */
X
Xstatic char   **alloc_base = (char **)0;
Xstatic int      alloc_size = 0;
Xstatic int      alloc_used = 0;
X
X#ifndef lint
Xstatic char     sccs[] = "@(#)setenv.c	1.3 95/06/27";
X#endif
X
X#ifdef RADICAL_SETENV
X
X/* Environment comparison -- compare two environment entries by name */
Xstatic int      env_compare(char *s1, char *s2)
X{
X	char           *eq;
X	char           *ref;
X	size_t          len;
X	int             rc;
X
X	/* One of the strings comes from the environment and contains an '=' */
X	ref = s1;
X	eq = strchr(ref, '=');
X	if (eq == (char *)0)
X	{
X		ref = s2;
X		eq = strchr(ref, '=');
X	}
X	assert(eq != (char *)0);
X	len = eq - ref;
X
X	/* Compare up to but excluding equals sign */
X	rc = strncmp(s1, s2, len);
X
X	if (rc == 0)
X	{
X		/* Check that we are dealing with distinct variable names */
X		if (s1[len] != '\0' && s1[len] != '=')
X			rc = 1;
X		else if (s2[len] != '\0' && s2[len] != '=')
X			rc = -1;
X	}
X	return(rc);
X}
X
X/* Environment comparison -- for use by bsearch/qsort */
Xstatic int      env_cmp(const void *vp1, const void *vp2)
X{
X	return(env_compare(*(char **)vp1, *(char **)vp2));
X}
X
X#ifndef NDEBUG
X/* Check that environment is correct */
Xstatic void     env_check()
X{
X	size_t          i;
X
X	/* Check basic setup */
X	if (env_base != (char **)0)
X	{
X		assert(env_base == environ);
X		assert(env_used <= env_size);
X	}
X	else
X		assert(env_size == 0);
X	assert(environ[env_used] == (char *)0);
X
X	/* Check sorting of environment */
X	for (i = 1; i < env_used; i++)
X		assert(env_compare(environ[i - 1], environ[i]) < 0);
X}
X
X#endif	/* NDEBUG */
X
X/* Sort the environment (and check that the sort worked) */
Xstatic void     env_sort(char **env, size_t count)
X{
X	qsort(env, count, sizeof(char **), env_cmp);
X	ENV_CHECK();
X}
X
X/* Copy the existing environment into locally controlled space and sort it */
Xstatic int      env_alloc()
X{
X	char          **envp = environ;
X	char          **newp;
X	size_t          count;
X	int             rc;
X
X	/* Work out how big the environment is */
X	for (count = 0; *envp++ != (char *)0; count++)
X		;
X
X	env_base = (char **)malloc((count + 1) * sizeof(char *));
X
X	if (env_base == (char **)0)
X		rc = SETENV_FAILURE;
X	else
X	{
X		env_size = count;
X		newp = env_base;
X		for (envp = environ; *envp; envp++)
X			*newp++ = *envp;
X		*newp = (char *)0;
X		environ = env_base;
X		rc = SETENV_SUCCESS;
X	}
X
X	env_used = count;
X
X	/* Sort the environment */
X	env_sort(environ, count);
X
X	return(rc);
X}
X
X/* Add new value to environment (known not to be in environment) */
Xstatic int      add_env(char *newval)
X{
X	int             rc = SETENV_SUCCESS;
X	char          **space;
X	int             new_size;
X
X	if (env_used >= env_size)
X	{
X		assert(env_used == env_size);
X		new_size = (env_size + INCREMENT_SIZE + 1) * sizeof(char *);
X		if (env_base == (char **)0)
X			space = (char **)malloc(new_size);
X		else
X			space = (char **)realloc(env_base, new_size);
X		if (space == (char **)0)
X			rc = SETENV_FAILURE;
X		else
X		{
X			env_base = space;
X			env_size += INCREMENT_SIZE;
X			environ = env_base;
X		}
X	}
X	if (rc == SETENV_SUCCESS)
X	{
X		env_base[env_used++] = newval;
X		env_base[env_used] = (char *)0;
X		env_sort(environ, env_used);
X	}
X	return(rc);
X}
X
X/* Find pointer to environment variable in environment */
Xstatic char   **find_env(const char *newval)
X{
X	char          **env = (char **)0;
X
X	if (env_base == (char **)0)
X		env_alloc();
X	env = bsearch(&newval, environ, env_used, sizeof(char **), env_cmp);
X	return(env);
X}
X
X/* Add or insert newval into environment */
Xstatic int      fix_env(char *newval)
X{
X	char          **env;
X	int             rc;
X
X	env = find_env(newval);
X
X	if (env == (char **)0)
X	{
X		/* Not in environment -- add it */
X		rc = add_env(newval);
X	}
X	else
X	{
X		/* Value is already in environment -- replace it */
X		assert(env_compare(*env, newval) == 0);
X		*env = newval;
X		rc = SETENV_SUCCESS;
X	}
X	return(rc);
X}
X
X#endif	/* RADICAL_SETENV */
X
X/*
X** Linear search for allocations deemed adequate because it is simple
X** pointer comparison, whereas searching for values in environment is
X** definitely not a question of simple pointer comparison.
X*/
X
X/* Determine whether value was previously allocated and mark it unallocated */
Xstatic int      was_allocated(char *val)
X{
X	int             i;
X
X	if (val == (char *)0)
X		return(SETENV_FAILURE);
X	for (i = 0; i < alloc_used; i++)
X	{
X		if (alloc_base[i] == val)
X		{
X			alloc_base[i] = alloc_base[--alloc_used];
X			return(SETENV_SUCCESS);
X		}
X	}
X	return(SETENV_FAILURE);
X}
X
X/* Record that value was allocated */
Xstatic int      now_allocated(char *val)
X{
X	int             rc = SETENV_SUCCESS;
X	char          **space;
X	int             new_size;
X
X	if (alloc_used >= alloc_size)
X	{
X		assert(alloc_used == alloc_size);
X		new_size = (alloc_size + INCREMENT_SIZE) * sizeof(char *);
X		if (alloc_base == (char **)0)
X			space = (char **)malloc(new_size);
X		else
X			space = (char **)realloc(alloc_base, new_size);
X		if (space == (char **)0)
X			rc = SETENV_FAILURE;
X		else
X		{
X			alloc_base = space;
X			alloc_size += INCREMENT_SIZE;
X		}
X	}
X	if (rc == SETENV_SUCCESS)
X		alloc_base[alloc_used++] = val;
X	return(rc);
X}
X
X/* Return pointer to start of environment variable or null */
Xstatic char    *env_value(const char *val)
X{
X	char           *equ;
X	char           *env;
X	char           *dup;
X	char           *old;
X	size_t		    len;
X
X	equ = strchr(val, '=');
X	if (equ == (char *)0)
X	{
X		dup = (char *)0;
X		len = strlen(val);
X		env = getenv(val);
X	}
X	else
X	{
X		len = (equ - val) + 1;
X		dup = (char *)malloc(len + 1);
X		strncpy(dup, val, len);
X		dup[len] = '\0';
X		env = getenv(dup);
X	}
X	if (env == (char *)0)
X	{
X		/* Value not in environment yet */
X		old = (char *)0;
X	}
X	else
X		old = env - len;
X	if (dup != (char *)0)
X		free(dup);
X	return(old);
X}
X
X/* Return pointer to space previously allocated by setenv() or null pointer */
Xstatic char    *old_value(char *val)
X{
X	char           *old;
X
X	old = env_value(val);
X	if (was_allocated(old) != SETENV_SUCCESS)
X		old = (char *)0;
X	return(old);
X}
X
X/* Primary interface function -- memory safe version of putenv(3) */
X/* Efficiency could be improved if made to depend more on RADICAL_SETENV */
Xint             setenv(const char *newval)
X{
X	char           *dup;
X	char           *old;
X	int             len;
X	int             rc;
X
X	/* Check that value is properly formed */
X	if (strchr(newval, '=') == (char *)0)
X		return(SETENV_FAILURE);
X
X	/* Copy value into allocated space */
X	len = strlen(newval);
X	if ((dup = (char *)malloc(len + 1)) == (char *)0)
X		return(SETENV_FAILURE);
X	strcpy(dup, newval);
X
X	/* See if value was previously allocated by setenv() */
X	old = old_value(dup);
X
X	/* Mark new value as allocated */
X	if (now_allocated(dup) != SETENV_SUCCESS)
X	{
X		free(dup);
X		return(SETENV_FAILURE);
X	}
X
X	/* Install value in environment */
X	if (PUTENV(dup) != 0)
X	{
X		rc = was_allocated(dup);
X		assert(rc == SETENV_SUCCESS);
X		free(dup);
X		return(SETENV_FAILURE);
X	}
X
X	/* Release old value */
X	if (old != (char *)0)
X		free(old);
X
X	return(SETENV_SUCCESS);
X}
X
X#ifdef RADICAL_SETENV
X
X/* Simulate putenv(3) if setenv(3) has complete control over the environment */
Xint             putenv(const char *newval)
X{
X	return(setenv(newval));
X}
X
X/* Simulate getenv(3) if setenv(3) has complete control over the environment */
Xchar           *getenv(const char *env)
X{
X	char          **envp;
X	char           *val;
X
X	envp = find_env(env);
X	if (envp == (char **)0)
X		val = (char *)0;
X	else
X		val = *envp + strlen(env) + 1;
X
X	return(val);
X}
X
X#endif	/* RADICAL_SETENV */
X
X#ifdef RADICAL_SETENV
X
X/* Unset environment variable (possibly with value attached) */
X/* If setenv(3) has complete control over the environment */
Xvoid            unsetenv(const char *env)
X{
X	char          **envp;
X	char           *savp;
X	int				rc;
X
X	envp = find_env(env);
X	if (envp != (char **)0)
X	{
X		savp = *envp;
X		rc = was_allocated(savp);
X		/* Copy environment down */
X		for (; envp[1] != (char *)0; envp++)
X			envp[0] = envp[1];
X		envp[0] = (char *)0;
X		env_used--;
X		if (rc == SETENV_SUCCESS)
X			free(savp);
X		assert(env_used >= 0);
X		assert(env_base[env_used] == (char *)0);
X	}
X}
X
X#else
X
X/* Unset environment variable (possibly with value attached) */
X/* If setenv(3) has limited control over the environment */
Xvoid            unsetenv(const char *env)
X{
X	char          **envp;
X	char           *savp;
X	int				rc;
X
X	savp = env_value(env);
X	if (savp != (char *)0)
X	{
X		rc = was_allocated(savp);
X		/* Copy environment down */
X		for (envp = environ; *envp != (char *)0; envp++)
X			{
X			if (*envp == savp)
X				break;
X			}
X		assert(*envp == savp);
X		for (; envp[1] != (char *)0; envp++)
X			envp[0] = envp[1];
X		envp[0] = (char *)0;
X		if (rc == SETENV_SUCCESS)
X			free(savp);
X	}
X}
X
X#endif	/* RADICAL_SETENV */
X
X#ifdef TEST
X
X#include <stdio.h>
X
X#define DIM(x)		(sizeof(x)/sizeof(*(x)))
X#define MIN(a, b)	(((a) < (b)) ? (a) : (b))
X#define MAX(a, b)	(((a) > (b)) ? (a) : (b))
X
Xstatic char    *newenv[] =
X{
X	"HOME=/tmp/junk",
X	"AARDVARK=\"abelone\"",
X	"ZULU=Greenwich Mean Time",
X	"TERM=junk",
X	"WIDGET01=elephants",
X	"WIDGET02=elephants01",
X	"MAILER=femaler",
X	"WIDGET01=elephants02",
X	"WIDGET02=elephants03",
X	"WIDGET01=elephants04",
X	"HOME_01=/tmp/junk",
X	"PATH_01=/junk:/junk2",
X	"WIDGET01_01=elephants",
X	"AARDVARK_01=\"abelone\"",
X	"WIDGET02_01=elephants01",
X	"MAILER_01=femaler",
X	"WIDGET01_01=elephants02",
X	"WIDGET02_01=elephants03",
X	"WIDGET01_01=elephants04",
X	"ZULU_01=Native of Natal, South Africa",
X};
X
Xstatic char    *oldenv[] =
X{
X	"HOME",
X	"PATH",
X	"AARDVARK",
X	"ZULU",
X};
X
Xstruct Bounds
X{
X	int             lo;
X	int             hi;
X};
X
Xstruct Bounds   bounds[] =
X{
X	{0, 8},
X	{4, 12},
X	{7, 20},
X};
X
Xstatic void     chk_environ()
X{
X	char          **envp;
X
X	PRINTF(("** DUMP ENVIRONMENT **\n"));
X	for (envp = environ; *envp != (char *)0; envp++)
X		PRINTF(("%s\n", *envp));
X	PRINTF(("\n"));
X	fflush(stdout);
X}
X
X/* Checking getenv() */
Xstatic void     chk_getenv()
X{
X	int             i;
X	char           *s;
X
X	for (i = 0; i < DIM(oldenv); i++)
X	{
X		s = getenv(oldenv[i]);
X		if (s == (char *)0)
X			PRINTF(("getenv(%s) did not find it\n", oldenv[i]));
X		else
X			PRINTF(("getenv(%s)=<<%s>>\n", oldenv[i], s));
X	}
X	PRINTF(("getenv() OK\n\n"));
X	fflush(stdout);
X}
X
Xstatic void chk_compare()
X{
X#ifdef RADICAL_SETENV
X	/* Check comparison code */
X	PRINTF(("compare equals: %d\n",
X		   env_compare(newenv[0], newenv[0])));
X	PRINTF(("compare non-equals: %d\n",
X		   env_compare(newenv[0], newenv[1])));
X	PRINTF(("compare non-equals (reversed): %d\n",
X		   env_compare(newenv[1], newenv[0])));
X	PRINTF(("compare sub-string: %d\n",
X		   env_compare("AAAA=aaa", "AAA=aaa")));
X	PRINTF(("compare sub-string (reversed): %d\n",
X		   env_compare("AAA=aaa", "AAAA=aaa")));
X	PRINTF(("compare getenv-style (==): %d\n",
X		   env_compare("AAAA", "AAAA=aaa")));
X	PRINTF(("compare getenv-style (!=): %d\n",
X		   env_compare("BAAA", "AAAA=aaa")));
X	PRINTF(("compare getenv-style (sub-str): %d\n",
X		   env_compare("AAA", "AAAA=aaa")));
X	PRINTF(("compare getenv-style (sup-str): %d\n",
X		   env_compare("AAAAA", "AAAA=aaa")));
X	PRINTF(("compare sub-string: %d (should be negative)\n",
X		   env_compare("TERM=aaa", "TERMCAP=aaa")));
X	PRINTF(("Comparisons OK\n\n"));
X	fflush(stdout);
X#endif	/* RADICAL_SETENV */
X}
X
Xstatic void     chk_unsetenv()
X{
X	int             j;
X	int             k;
X	int             n;
X
X	k = 0;
X	n = MIN(DIM(newenv), bounds[0].hi);
X	for (j = bounds[0].lo; j < n; j++)
X	{
X		unsetenv(newenv[j]);
X		PRINTF(("%d -- unsetenv(\"%s\")\n", ++k, newenv[j]));
X	}
X
X	PRINTF(("%d unsetenv() calls complete\n\n", k));
X	fflush(stdout);
X	chk_environ();
X}
X
Xstatic void     chk_setenv()
X{
X	int             i;
X	int             j;
X	int             k;
X	int             n;
X	int             fails;
X
X	fails = 0;
X	k = 0;
X	for (i = 0; i < DIM(bounds); i++)
X	{
X		n = MIN(DIM(newenv), bounds[i].hi);
X		for (j = bounds[i].lo; j < n; j++)
X		{
X			k++;
X			if (setenv(newenv[j]) != 0)
X			{
X				fails++;
X				PRINTF(("Failed -- %d setenv(\"%s\")\n", k, newenv[j]));
X			}
X			else
X				PRINTF(("OK     -- %d setenv(\"%s\")\n", k, newenv[j]));
X		}
X	}
X
X	PRINTF(("setenv() calls complete -- %d failures\n\n", fails));
X	fflush(stdout);
X}
X
Xstatic void chk_execute()
X{
X#ifdef NO_EXECUTE
X	PRINTF(("\nChecking environment for executed programs -- suppressed!\n"));
X#else
X	PRINTF(("\nChecking environment for executed programs\n"));
X	fflush(stdout);
X	if (system("env") != 0)
X		PRINTF(("** Failed to exec env program\n"));
X	else
X		PRINTF(("Executed 'env' program OK\n"));
X#endif	/* NO_EXECUTE */
X}
X
Xint             main(int argc, char **argv)
X{
X	chk_compare();
X	chk_environ();
X	chk_getenv();
X	chk_setenv();
X	chk_unsetenv();
X	chk_getenv();
X	chk_environ();
X	chk_execute();
X	PRINTF(("All tests completed OK\n"));
X	return(0);
X}
X
X#endif	/* TEST */
SHAR-EOF
chmod 444 setenv.c
if [ `wc -c <setenv.c` -ne 14915 ]
then echo shar: setenv.c unpacked with wrong size
fi
# end of overwriting check
fi
echo All files extracted
exit 0
