POSIX *env API: unleaking portable version


Carrying on from A POSIX Env Game, below is a portable unleaking implementation of the POSIX 1003.1-2001 putenv/setenv/unsetenv/getenv API.

In addition to all the structural API idiosyncracies noted in previous article, it also has to deal with the fact that the signature of putenv() changed between XPG4 and POSIX.1-2001, so we have to resort to conditional C preprocessor statements to allow the required signature to be selected – see the PUTENV_CONST macro.

Update 28th December 2016: due to a copy-and-paste snafu with the WordPress editor, the original article was missing the rather crucial internal update() function. That omission has been corrected – the code below is (now) complete and unmolested.


/* 100% portable implementation of the putenv(), setenv(),unsetenv() functions
 * as specified by POSIX.1-2001.
 *
 * Does not rely on any system-specifics or runtime-library internal
 * implementation details [is the env-list initially on the heap or stack (or
 * even in read-only memory)? how about the pointed-to strings? does the heap
 * grow up or down in the address-space?]
 *
 * Allows setenv()/unsetenv() [allocating/deallocating] to co-exist with
 * putenv() [non-allocating] and even be applied to variables created/updated
 * by the other, without any lossage or leaks.
 */
#include <stdlib.h>
#include <errno.h>
#include <assert.h>

#ifdef DEBUG
#include <stdio.h>
#define trace(x)   printf x
#undef NDEBUG
#else
#define trace(x)
#define NDEBUG 1
#endif

extern char **environ;
static char **heaped = NULL; /* indicator for the environ list-array itself,
                not the strings */

static char **tracked = NULL;   /* list of ptrs to self-allocated strings */
static int ntracked = 0;

static void *buy(void *old, size_t size)
    {
    void *p;

    /* careful to deal with differences between C89 and C99 realloc()... */
    p = old ? realloc(old, size ? size : 1) : malloc(size);
    return (p);
    }

static char **heapify_list(void)
    {
    char **p;
    int n;

    assert(heaped != environ);
    trace(("heapifying the env list @%p\n", environ));

    if (heaped)
        {
        trace(("freeing old env list @%p\n", heaped));
        free(heaped);
        }

    for (p = environ, n = 1; *p; ++p, ++n)
        ;
    if (!(heaped = malloc(n * sizeof(*environ))))
        {
        trace(("heapify failed\n"));
        return (NULL);
        }
    for (p = heaped; *environ; ++p, ++environ)
        *p = *environ;
    *p = NULL;
    environ = heaped;
    trace(("heapification complete, %i slots @%p\n", n, environ));
    return (environ);
    }

static char **lookup(const char *name, int len)
    {
    char **p;

    trace(("lookup(\"%*.*s\", %i)  ", len, len, name, len));
    for (p = environ; *p; ++p)
        {
        if (!strncmp(*p, name, len) && (*p)[len] == '=')
            {
            trace(("found \"%s\"\n", *p));
            return (p);
            }
        }
    trace(("not found\n"));
    return (NULL);
    }

#define TRACK 1
#define NOTRACK 0

static int is_tracked(char *ptr)
    {
    int i;

    for (i = 0; i < ntracked; ++i)
        {
        if (tracked[i] == ptr)
            return (i);
        }
    return (-1);
    }

static int track(char *ptr)
    {
    char **newlist;

    /* create-or-grow tracking list and add ptr to end of list */
    if (!(newlist = buy(tracked, (ntracked+1) * sizeof(*tracked))))
        return (0);
    tracked = newlist;
    tracked[ntracked++] = ptr;
    return (1);
    }

static int untrack(int idx)
    {
    char **newlist;

    assert(idx < ntracked);
    free(tracked[idx]);

    /* move trailing entry into the hole */
    tracked[idx] = tracked[--ntracked];

    /* trim the trailing entry from the list by resizing */
    if (!(newlist = buy(tracked, ntracked * sizeof(*tracked))))
        return (0);
    tracked = newlist;
    return (1);
    }

static void retrack(int old, char *new)
    {
    assert(old < ntracked);
    free(tracked[old]);
    tracked[old] = new;
    }

static void unleak(void)
    {
    char **e;
    int n;

    for (n = 0; n < ntracked; ++n)
        {
        for (e = environ; e && *e; ++e)
            {
            if (*e == tracked[n])
                break;
            }
        if (!*e)    /* one that got away */
            {
            trace(("freeing orphan @%p \"%s\"\n",
                tracked[n], tracked[n]));
            untrack(n);
            }
        }
    if (environ != heaped)
        heapify_list();
    }

static int update(char *str, int type, int overwrite)
    {
    char **p, **q;
    const char *s;
    int n;
    size_t need, namelen;

    if (!str || !*str)
        {
        errno = EINVAL;
        return (-1);
        }
    trace(("update(\"%s\", %s, %i)\n", str,
        (type != TRACK) ? "untracked" : "TRACKED", overwrite));
    unleak();

    /* computing length of name */
    for (namelen = 0, s = str; *s; ++s, ++namelen)
        {
        if (*s == '=')
            break;
        }

    /* force list-array into heap, so that we can modify it
     */
    if (environ != heaped)
        {
        if (!heapify_list())
            return (-1);
        }

    if (!*s)    /* no equals sign in str, remove the var */
        {
        if (!overwrite)
            return (0);
        if ((p = lookup(str, s - str))) /* old is there */
            {
            trace(("deleting \"%s\"\n", str));
            if ((n = is_tracked(*p)) >= 0)
                {
                trace(("untracking %s\n", tracked[n]));
                if (!untrack(n))
                    return (-1);
                }
            /* update main list */
            for (q = p; *q; ++q)
                ;
            if (--q >= p)
                *p = *q;    /* move trailing entry */
            *q = NULL;      /* move terminating NULL up */
            n = 1 + q - environ;
            q = buy(environ, n * sizeof(*environ));
            if (!q)
                return (-1);
            trace(("shrunk list, %i slots @%p\n", n, q));
            heaped = environ = q;
            }
        return (0);
        }
    
    if ((p = lookup(str, namelen))) /* env variable is already present */
        {
        if (!overwrite)
            {
            if (type == TRACK)
                free(str);
            return (0);
            }
        if ((n = is_tracked(*p)) >= 0)
            {
            trace(("old was tracked\n"));
            if (type == TRACK)
                retrack(n, str);
            else if (!untrack(n))
                return (-1);
            }
        else    /* old is from putenv */
            {
            trace(("old was untracked\n"));
            if (type == TRACK)
                {
                if (!track(str))
                    return (-1);
                }
            }
        *p = str; /* update main list */
        }
    else    /* env variable is NOT already present */
        {
        /* resize the list */
        for (n = 0, p = environ; *p; ++p, ++n)
            ;
        if (!(p = buy(environ, (n+2) * sizeof(*environ))))
            return (-1);
        trace(("grown list, %i slots @%p\n", n+2, p));
        if (type == TRACK)
            {
            if (!track(str))
                return (-1);
            }
        /* and insert new entry */
        p[n++] = str;
        p[n] = NULL;
        heaped = environ = p;
        }
    return (0);
    }

/* Several (older, obsolete) API standards declare putenv() with a
 * const-qualified argument:
 *  POSIX 1003.1-1990, 1998
 *  System V Interface Definition, version 3 (1989)
 *  Single UNIX Specification, version 1 (1995)
 *  UNIX95
 * whereas several newer API standards do not:
 *  System V Interface Definition, version 4
 *  UNIX98
 *  X/Open Portability Guide, version 4
 *  POSIX 1003.1-2001
 * and some do not define putenv() at all (so we could use non-const for them
 * except that we cannot distinguish them from other consted API environments,
 * dammit):
 *  POSIX 1003.1-1988
 */
#if defined(PUTENV_CONST)  /* so can force const override if need be */
#define QUALIFIER const
#elif defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200101L)  /*POSIX.1-2001*/
#define QUALIFIER
#elif defined(_XOPEN_SOURCE) && defined(_XOPEN_VERSION)    /* XPG4 */
#define QUALIFIER
#elif defined(_XOPEN_SOURCE) && defined(_XOPEN_SOURCE_EXTENDED) && \
        (_XOPEN_SOURCE_EXTENDED >= 1)            /* XPG 4.2 */
#define QUALIFIER
#else
#define QUALIFIER const
#endif
int putenv(QUALIFIER char *str)
    {
    int i, n;

    trace(("putenv(\"%s\")\n", str));
    i = (update((char *)str, NOTRACK, 1));
    return(i);
    }

int setenv(char *name, char *value, int overwrite)
    {
    char *str;

    if (!(str = malloc(strlen(name)+1+strlen(value)+1)))
        return (-1);
    sprintf(str, "%s=%s", name, value);
    trace(("setenv(\"%s\", \"%s\", %i) [%i]\n",
        name, value, overwrite, strlen(str)+1));
    return (update(str, TRACK, overwrite));
    }

int unsetenv(char *name)
    {
    trace(("unsetenv(\"%s\")\n", name));
    return (update(name, NOTRACK, 1));
    }

char *getenv(const char *name)
    {
    char **p;
    int namelen = strlen(name);

    if ((p = lookup(name, namelen)))
        return ((*p)+namelen+1);
    return (NULL);
    }
Advertisements

2 thoughts on “POSIX *env API: unleaking portable version

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s