summaryrefslogtreecommitdiffstats
path: root/lib/libc/stdlib/getenv.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libc/stdlib/getenv.c')
-rw-r--r--lib/libc/stdlib/getenv.c686
1 files changed, 686 insertions, 0 deletions
diff --git a/lib/libc/stdlib/getenv.c b/lib/libc/stdlib/getenv.c
new file mode 100644
index 0000000..b7826d7
--- /dev/null
+++ b/lib/libc/stdlib/getenv.c
@@ -0,0 +1,686 @@
+/*-
+ * Copyright (c) 2007-2009 Sean C. Farley <scf@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer,
+ * without modification, immediately at the beginning of the file.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+
+#include "namespace.h"
+#include <sys/types.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "un-namespace.h"
+
+
+static const char CorruptEnvFindMsg[] = "environment corrupt; unable to find ";
+static const char CorruptEnvValueMsg[] =
+ "environment corrupt; missing value for ";
+
+
+/*
+ * Standard environ. environ variable is exposed to entire process.
+ *
+ * origEnviron: Upon cleanup on unloading of library or failure, this
+ * allows environ to return to as it was before.
+ * environSize: Number of variables environ can hold. Can only
+ * increase.
+ * intEnviron: Internally-built environ. Exposed via environ during
+ * (re)builds of the environment.
+ */
+extern char **environ;
+static char **origEnviron;
+static char **intEnviron = NULL;
+static int environSize = 0;
+
+/*
+ * Array of environment variables built from environ. Each element records:
+ * name: Pointer to name=value string
+ * name length: Length of name not counting '=' character
+ * value: Pointer to value within same string as name
+ * value size: Size (not length) of space for value not counting the
+ * nul character
+ * active state: true/false value to signify whether variable is active.
+ * Useful since multiple variables with the same name can
+ * co-exist. At most, one variable can be active at any
+ * one time.
+ * putenv: Created from putenv() call. This memory must not be
+ * reused.
+ */
+static struct envVars {
+ size_t nameLen;
+ size_t valueSize;
+ char *name;
+ char *value;
+ bool active;
+ bool putenv;
+} *envVars = NULL;
+
+/*
+ * Environment array information.
+ *
+ * envActive: Number of active variables in array.
+ * envVarsSize: Size of array.
+ * envVarsTotal: Number of total variables in array (active or not).
+ */
+static int envActive = 0;
+static int envVarsSize = 0;
+static int envVarsTotal = 0;
+
+
+/* Deinitialization of new environment. */
+static void __attribute__ ((destructor)) __clean_env_destructor(void);
+
+
+/*
+ * A simple version of warnx() to avoid the bloat of including stdio in static
+ * binaries.
+ */
+static void
+__env_warnx(const char *msg, const char *name, size_t nameLen)
+{
+ static const char nl[] = "\n";
+ static const char progSep[] = ": ";
+
+ _write(STDERR_FILENO, _getprogname(), strlen(_getprogname()));
+ _write(STDERR_FILENO, progSep, sizeof(progSep) - 1);
+ _write(STDERR_FILENO, msg, strlen(msg));
+ _write(STDERR_FILENO, name, nameLen);
+ _write(STDERR_FILENO, nl, sizeof(nl) - 1);
+
+ return;
+}
+
+
+/*
+ * Inline strlen() for performance. Also, perform check for an equals sign.
+ * Cheaper here than peforming a strchr() later.
+ */
+static inline size_t
+__strleneq(const char *str)
+{
+ const char *s;
+
+ for (s = str; *s != '\0'; ++s)
+ if (*s == '=')
+ return (0);
+
+ return (s - str);
+}
+
+
+/*
+ * Comparison of an environment name=value to a name.
+ */
+static inline bool
+strncmpeq(const char *nameValue, const char *name, size_t nameLen)
+{
+ if (strncmp(nameValue, name, nameLen) == 0 && nameValue[nameLen] == '=')
+ return (true);
+
+ return (false);
+}
+
+
+/*
+ * Using environment, returns pointer to value associated with name, if any,
+ * else NULL. If the onlyActive flag is set to true, only variables that are
+ * active are returned else all are.
+ */
+static inline char *
+__findenv(const char *name, size_t nameLen, int *envNdx, bool onlyActive)
+{
+ int ndx;
+
+ /*
+ * Find environment variable from end of array (more likely to be
+ * active). A variable created by putenv is always active, or it is not
+ * tracked in the array.
+ */
+ for (ndx = *envNdx; ndx >= 0; ndx--)
+ if (envVars[ndx].putenv) {
+ if (strncmpeq(envVars[ndx].name, name, nameLen)) {
+ *envNdx = ndx;
+ return (envVars[ndx].name + nameLen +
+ sizeof ("=") - 1);
+ }
+ } else if ((!onlyActive || envVars[ndx].active) &&
+ (envVars[ndx].nameLen == nameLen &&
+ strncmpeq(envVars[ndx].name, name, nameLen))) {
+ *envNdx = ndx;
+ return (envVars[ndx].value);
+ }
+
+ return (NULL);
+}
+
+
+/*
+ * Using environ, returns pointer to value associated with name, if any, else
+ * NULL. Used on the original environ passed into the program.
+ */
+static char *
+__findenv_environ(const char *name, size_t nameLen)
+{
+ int envNdx;
+
+ /* Find variable within environ. */
+ for (envNdx = 0; environ[envNdx] != NULL; envNdx++)
+ if (strncmpeq(environ[envNdx], name, nameLen))
+ return (&(environ[envNdx][nameLen + sizeof("=") - 1]));
+
+ return (NULL);
+}
+
+
+/*
+ * Remove variable added by putenv() from variable tracking array.
+ */
+static void
+__remove_putenv(int envNdx)
+{
+ envVarsTotal--;
+ if (envVarsTotal > envNdx)
+ memmove(&(envVars[envNdx]), &(envVars[envNdx + 1]),
+ (envVarsTotal - envNdx) * sizeof (*envVars));
+ memset(&(envVars[envVarsTotal]), 0, sizeof (*envVars));
+
+ return;
+}
+
+
+/*
+ * Deallocate the environment built from environ as well as environ then set
+ * both to NULL. Eases debugging of memory leaks.
+ */
+static void
+__clean_env(bool freeVars)
+{
+ int envNdx;
+
+ /* Deallocate environment and environ if created by *env(). */
+ if (envVars != NULL) {
+ for (envNdx = envVarsTotal - 1; envNdx >= 0; envNdx--)
+ /* Free variables or deactivate them. */
+ if (envVars[envNdx].putenv) {
+ if (!freeVars)
+ __remove_putenv(envNdx);
+ } else {
+ if (freeVars)
+ free(envVars[envNdx].name);
+ else
+ envVars[envNdx].active = false;
+ }
+ if (freeVars) {
+ free(envVars);
+ envVars = NULL;
+ } else
+ envActive = 0;
+
+ /* Restore original environ if it has not updated by program. */
+ if (origEnviron != NULL) {
+ if (environ == intEnviron)
+ environ = origEnviron;
+ free(intEnviron);
+ intEnviron = NULL;
+ environSize = 0;
+ }
+ }
+
+ return;
+}
+
+
+/*
+ * Using the environment, rebuild the environ array for use by other C library
+ * calls that depend upon it.
+ */
+static int
+__rebuild_environ(int newEnvironSize)
+{
+ char **tmpEnviron;
+ int envNdx;
+ int environNdx;
+ int tmpEnvironSize;
+
+ /* Resize environ. */
+ if (newEnvironSize > environSize) {
+ tmpEnvironSize = newEnvironSize * 2;
+ tmpEnviron = realloc(intEnviron, sizeof (*intEnviron) *
+ (tmpEnvironSize + 1));
+ if (tmpEnviron == NULL)
+ return (-1);
+ environSize = tmpEnvironSize;
+ intEnviron = tmpEnviron;
+ }
+ envActive = newEnvironSize;
+
+ /* Assign active variables to environ. */
+ for (envNdx = envVarsTotal - 1, environNdx = 0; envNdx >= 0; envNdx--)
+ if (envVars[envNdx].active)
+ intEnviron[environNdx++] = envVars[envNdx].name;
+ intEnviron[environNdx] = NULL;
+
+ /* Always set environ which may have been replaced by program. */
+ environ = intEnviron;
+
+ return (0);
+}
+
+
+/*
+ * Enlarge new environment.
+ */
+static inline bool
+__enlarge_env(void)
+{
+ int newEnvVarsSize;
+ struct envVars *tmpEnvVars;
+
+ envVarsTotal++;
+ if (envVarsTotal > envVarsSize) {
+ newEnvVarsSize = envVarsTotal * 2;
+ tmpEnvVars = realloc(envVars, sizeof (*envVars) *
+ newEnvVarsSize);
+ if (tmpEnvVars == NULL) {
+ envVarsTotal--;
+ return (false);
+ }
+ envVarsSize = newEnvVarsSize;
+ envVars = tmpEnvVars;
+ }
+
+ return (true);
+}
+
+
+/*
+ * Using environ, build an environment for use by standard C library calls.
+ */
+static int
+__build_env(void)
+{
+ char **env;
+ int activeNdx;
+ int envNdx;
+ int savedErrno;
+ size_t nameLen;
+
+ /* Check for non-existant environment. */
+ if (environ == NULL || environ[0] == NULL)
+ return (0);
+
+ /* Count environment variables. */
+ for (env = environ, envVarsTotal = 0; *env != NULL; env++)
+ envVarsTotal++;
+ envVarsSize = envVarsTotal * 2;
+
+ /* Create new environment. */
+ envVars = calloc(1, sizeof (*envVars) * envVarsSize);
+ if (envVars == NULL)
+ goto Failure;
+
+ /* Copy environ values and keep track of them. */
+ for (envNdx = envVarsTotal - 1; envNdx >= 0; envNdx--) {
+ envVars[envNdx].putenv = false;
+ envVars[envNdx].name =
+ strdup(environ[envVarsTotal - envNdx - 1]);
+ if (envVars[envNdx].name == NULL)
+ goto Failure;
+ envVars[envNdx].value = strchr(envVars[envNdx].name, '=');
+ if (envVars[envNdx].value != NULL) {
+ envVars[envNdx].value++;
+ envVars[envNdx].valueSize =
+ strlen(envVars[envNdx].value);
+ } else {
+ __env_warnx(CorruptEnvValueMsg, envVars[envNdx].name,
+ strlen(envVars[envNdx].name));
+ errno = EFAULT;
+ goto Failure;
+ }
+
+ /*
+ * Find most current version of variable to make active. This
+ * will prevent multiple active variables from being created
+ * during this initialization phase.
+ */
+ nameLen = envVars[envNdx].value - envVars[envNdx].name - 1;
+ envVars[envNdx].nameLen = nameLen;
+ activeNdx = envVarsTotal - 1;
+ if (__findenv(envVars[envNdx].name, nameLen, &activeNdx,
+ false) == NULL) {
+ __env_warnx(CorruptEnvFindMsg, envVars[envNdx].name,
+ nameLen);
+ errno = EFAULT;
+ goto Failure;
+ }
+ envVars[activeNdx].active = true;
+ }
+
+ /* Create a new environ. */
+ origEnviron = environ;
+ environ = NULL;
+ if (__rebuild_environ(envVarsTotal) == 0)
+ return (0);
+
+Failure:
+ savedErrno = errno;
+ __clean_env(true);
+ errno = savedErrno;
+
+ return (-1);
+}
+
+
+/*
+ * Destructor function with default argument to __clean_env().
+ */
+static void
+__clean_env_destructor(void)
+{
+ __clean_env(true);
+
+ return;
+}
+
+
+/*
+ * Returns the value of a variable or NULL if none are found.
+ */
+char *
+getenv(const char *name)
+{
+ int envNdx;
+ size_t nameLen;
+
+ /* Check for malformed name. */
+ if (name == NULL || (nameLen = __strleneq(name)) == 0) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ /*
+ * Variable search order:
+ * 1. Check for an empty environ. This allows an application to clear
+ * the environment.
+ * 2. Search the external environ array.
+ * 3. Search the internal environment.
+ *
+ * Since malloc() depends upon getenv(), getenv() must never cause the
+ * internal environment storage to be generated.
+ */
+ if (environ == NULL || environ[0] == NULL)
+ return (NULL);
+ else if (envVars == NULL || environ != intEnviron)
+ return (__findenv_environ(name, nameLen));
+ else {
+ envNdx = envVarsTotal - 1;
+ return (__findenv(name, nameLen, &envNdx, true));
+ }
+}
+
+
+/*
+ * Set the value of a variable. Older settings are labeled as inactive. If an
+ * older setting has enough room to store the new value, it will be reused. No
+ * previous variables are ever freed here to avoid causing a segmentation fault
+ * in a user's code.
+ *
+ * The variables nameLen and valueLen are passed into here to allow the caller
+ * to calculate the length by means besides just strlen().
+ */
+static int
+__setenv(const char *name, size_t nameLen, const char *value, int overwrite)
+{
+ bool reuse;
+ char *env;
+ int envNdx;
+ int newEnvActive;
+ size_t valueLen;
+
+ /* Find existing environment variable large enough to use. */
+ envNdx = envVarsTotal - 1;
+ newEnvActive = envActive;
+ valueLen = strlen(value);
+ reuse = false;
+ if (__findenv(name, nameLen, &envNdx, false) != NULL) {
+ /* Deactivate entry if overwrite is allowed. */
+ if (envVars[envNdx].active) {
+ if (overwrite == 0)
+ return (0);
+ envVars[envNdx].active = false;
+ newEnvActive--;
+ }
+
+ /* putenv() created variable cannot be reused. */
+ if (envVars[envNdx].putenv)
+ __remove_putenv(envNdx);
+
+ /* Entry is large enough to reuse. */
+ else if (envVars[envNdx].valueSize >= valueLen)
+ reuse = true;
+ }
+
+ /* Create new variable if none was found of sufficient size. */
+ if (! reuse) {
+ /* Enlarge environment. */
+ envNdx = envVarsTotal;
+ if (!__enlarge_env())
+ return (-1);
+
+ /* Create environment entry. */
+ envVars[envNdx].name = malloc(nameLen + sizeof ("=") +
+ valueLen);
+ if (envVars[envNdx].name == NULL) {
+ envVarsTotal--;
+ return (-1);
+ }
+ envVars[envNdx].nameLen = nameLen;
+ envVars[envNdx].valueSize = valueLen;
+
+ /* Save name of name/value pair. */
+ env = stpcpy(envVars[envNdx].name, name);
+ if ((envVars[envNdx].name)[nameLen] != '=')
+ env = stpcpy(env, "=");
+ }
+ else
+ env = envVars[envNdx].value;
+
+ /* Save value of name/value pair. */
+ strcpy(env, value);
+ envVars[envNdx].value = env;
+ envVars[envNdx].active = true;
+ newEnvActive++;
+
+ /* No need to rebuild environ if an active variable was reused. */
+ if (reuse && newEnvActive == envActive)
+ return (0);
+ else
+ return (__rebuild_environ(newEnvActive));
+}
+
+
+/*
+ * If the program attempts to replace the array of environment variables
+ * (environ) environ or sets the first varible to NULL, then deactivate all
+ * variables and merge in the new list from environ.
+ */
+static int
+__merge_environ(void)
+{
+ char **env;
+ char *equals;
+
+ /*
+ * Internally-built environ has been replaced or cleared (detected by
+ * using the count of active variables against a NULL as the first value
+ * in environ). Clean up everything.
+ */
+ if (intEnviron != NULL && (environ != intEnviron || (envActive > 0 &&
+ environ[0] == NULL))) {
+ /* Deactivate all environment variables. */
+ if (envActive > 0) {
+ origEnviron = NULL;
+ __clean_env(false);
+ }
+
+ /*
+ * Insert new environ into existing, yet deactivated,
+ * environment array.
+ */
+ origEnviron = environ;
+ if (origEnviron != NULL)
+ for (env = origEnviron; *env != NULL; env++) {
+ if ((equals = strchr(*env, '=')) == NULL) {
+ __env_warnx(CorruptEnvValueMsg, *env,
+ strlen(*env));
+ errno = EFAULT;
+ return (-1);
+ }
+ if (__setenv(*env, equals - *env, equals + 1,
+ 1) == -1)
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+
+/*
+ * The exposed setenv() that peforms a few tests before calling the function
+ * (__setenv()) that does the actual work of inserting a variable into the
+ * environment.
+ */
+int
+setenv(const char *name, const char *value, int overwrite)
+{
+ size_t nameLen;
+
+ /* Check for malformed name. */
+ if (name == NULL || (nameLen = __strleneq(name)) == 0) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ /* Initialize environment. */
+ if (__merge_environ() == -1 || (envVars == NULL && __build_env() == -1))
+ return (-1);
+
+ return (__setenv(name, nameLen, value, overwrite));
+}
+
+
+/*
+ * Insert a "name=value" string into the environment. Special settings must be
+ * made to keep setenv() from reusing this memory block and unsetenv() from
+ * allowing it to be tracked.
+ */
+int
+putenv(char *string)
+{
+ char *equals;
+ int envNdx;
+ int newEnvActive;
+ size_t nameLen;
+
+ /* Check for malformed argument. */
+ if (string == NULL || (equals = strchr(string, '=')) == NULL ||
+ (nameLen = equals - string) == 0) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ /* Initialize environment. */
+ if (__merge_environ() == -1 || (envVars == NULL && __build_env() == -1))
+ return (-1);
+
+ /* Deactivate previous environment variable. */
+ envNdx = envVarsTotal - 1;
+ newEnvActive = envActive;
+ if (__findenv(string, nameLen, &envNdx, true) != NULL) {
+ /* Reuse previous putenv slot. */
+ if (envVars[envNdx].putenv) {
+ envVars[envNdx].name = string;
+ return (__rebuild_environ(envActive));
+ } else {
+ newEnvActive--;
+ envVars[envNdx].active = false;
+ }
+ }
+
+ /* Enlarge environment. */
+ envNdx = envVarsTotal;
+ if (!__enlarge_env())
+ return (-1);
+
+ /* Create environment entry. */
+ envVars[envNdx].name = string;
+ envVars[envNdx].nameLen = -1;
+ envVars[envNdx].value = NULL;
+ envVars[envNdx].valueSize = -1;
+ envVars[envNdx].putenv = true;
+ envVars[envNdx].active = true;
+ newEnvActive++;
+
+ return (__rebuild_environ(newEnvActive));
+}
+
+
+/*
+ * Unset variable with the same name by flagging it as inactive. No variable is
+ * ever freed.
+ */
+int
+unsetenv(const char *name)
+{
+ int envNdx;
+ size_t nameLen;
+
+ /* Check for malformed name. */
+ if (name == NULL || (nameLen = __strleneq(name)) == 0) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ /* Initialize environment. */
+ if (__merge_environ() == -1 || (envVars == NULL && __build_env() == -1))
+ return (-1);
+
+ /* Deactivate specified variable. */
+ envNdx = envVarsTotal - 1;
+ if (__findenv(name, nameLen, &envNdx, true) != NULL) {
+ envVars[envNdx].active = false;
+ if (envVars[envNdx].putenv)
+ __remove_putenv(envNdx);
+ __rebuild_environ(envActive - 1);
+ }
+
+ return (0);
+}
OpenPOWER on IntegriCloud