Re: Mixing 4gl and C Functions, How ?
Posted in 1996
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 }@