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 : "@(#)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 X#include X#include 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 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