summaryrefslogtreecommitdiffstats
path: root/generic/tclEnv.c
diff options
context:
space:
mode:
Diffstat (limited to 'generic/tclEnv.c')
-rw-r--r--generic/tclEnv.c744
1 files changed, 744 insertions, 0 deletions
diff --git a/generic/tclEnv.c b/generic/tclEnv.c
new file mode 100644
index 0000000..66ddb57
--- /dev/null
+++ b/generic/tclEnv.c
@@ -0,0 +1,744 @@
+/*
+ * tclEnv.c --
+ *
+ * Tcl support for environment variables, including a setenv function.
+ * This file contains the generic portion of the environment module. It
+ * is primarily responsible for keeping the "env" arrays in sync with the
+ * system environment variables.
+ *
+ * Copyright (c) 1991-1994 The Regents of the University of California.
+ * Copyright (c) 1994-1998 Sun Microsystems, Inc.
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#include "tclInt.h"
+
+TCL_DECLARE_MUTEX(envMutex) /* To serialize access to environ. */
+
+static struct {
+ int cacheSize; /* Number of env strings in cache. */
+ char **cache; /* Array containing all of the environment
+ * strings that Tcl has allocated. */
+#ifndef USE_PUTENV
+ char **ourEnviron; /* Cache of the array that we allocate. We
+ * need to track this in case another
+ * subsystem swaps around the environ array
+ * like we do. */
+ int ourEnvironSize; /* Non-zero means that the environ array was
+ * malloced and has this many total entries
+ * allocated to it (not all may be in use at
+ * once). Zero means that the environment
+ * array is in its original static state. */
+#endif
+} env;
+
+/*
+ * Declarations for local functions defined in this file:
+ */
+
+static char * EnvTraceProc(ClientData clientData, Tcl_Interp *interp,
+ const char *name1, const char *name2, int flags);
+static void ReplaceString(const char *oldStr, char *newStr);
+MODULE_SCOPE void TclSetEnv(const char *name, const char *value);
+MODULE_SCOPE void TclUnsetEnv(const char *name);
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclSetupEnv --
+ *
+ * This function is invoked for an interpreter to make environment
+ * variables accessible from that interpreter via the "env" associative
+ * array.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The interpreter is added to a list of interpreters managed by us, so
+ * that its view of envariables can be kept consistent with the view in
+ * other interpreters. If this is the first call to TclSetupEnv, then
+ * additional initialization happens, such as copying the environment to
+ * dynamically-allocated space for ease of management.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TclSetupEnv(
+ Tcl_Interp *interp) /* Interpreter whose "env" array is to be
+ * managed. */
+{
+ Var *varPtr, *arrayPtr;
+ Tcl_Obj *varNamePtr;
+ Tcl_DString envString;
+ Tcl_HashTable namesHash;
+ Tcl_HashEntry *hPtr;
+ Tcl_HashSearch search;
+
+ /*
+ * Synchronize the values in the environ array with the contents of the
+ * Tcl "env" variable. To do this:
+ * 1) Remove the trace that fires when the "env" var is updated.
+ * 2) Find the existing contents of the "env", storing in a hash table.
+ * 3) Create/update elements for each environ variable, removing
+ * elements from the hash table as we go.
+ * 4) Remove the elements for each remaining entry in the hash table,
+ * which must have existed before yet have no analog in the environ
+ * variable.
+ * 5) Add a trace that synchronizes the "env" array.
+ */
+
+ Tcl_UntraceVar2(interp, "env", NULL,
+ TCL_GLOBAL_ONLY | TCL_TRACE_WRITES | TCL_TRACE_UNSETS |
+ TCL_TRACE_READS | TCL_TRACE_ARRAY, EnvTraceProc, NULL);
+
+ /*
+ * Find out what elements are currently in the global env array.
+ */
+
+ TclNewLiteralStringObj(varNamePtr, "env");
+ Tcl_IncrRefCount(varNamePtr);
+ Tcl_InitObjHashTable(&namesHash);
+ varPtr = TclObjLookupVarEx(interp, varNamePtr, NULL, TCL_GLOBAL_ONLY,
+ /*msg*/ 0, /*createPart1*/ 0, /*createPart2*/ 0, &arrayPtr);
+ TclFindArrayPtrElements(varPtr, &namesHash);
+
+ /*
+ * Go through the environment array and transfer its values into Tcl. At
+ * the same time, remove those elements we add/update from the hash table
+ * of existing elements, so that after this part processes, that table
+ * will hold just the parts to remove.
+ */
+
+ if (environ[0] != NULL) {
+ int i;
+
+ Tcl_MutexLock(&envMutex);
+ for (i = 0; environ[i] != NULL; i++) {
+ Tcl_Obj *obj1, *obj2;
+ char *p1, *p2;
+
+ p1 = Tcl_ExternalToUtfDString(NULL, environ[i], -1, &envString);
+ p2 = strchr(p1, '=');
+ if (p2 == NULL) {
+ /*
+ * This condition seem to happen occasionally under some
+ * versions of Solaris, or when encoding accidents swallow the
+ * '='; ignore the entry.
+ */
+
+ continue;
+ }
+ p2++;
+ p2[-1] = '\0';
+ obj1 = Tcl_NewStringObj(p1, -1);
+ obj2 = Tcl_NewStringObj(p2, -1);
+ Tcl_DStringFree(&envString);
+
+ Tcl_IncrRefCount(obj1);
+ Tcl_IncrRefCount(obj2);
+ Tcl_ObjSetVar2(interp, varNamePtr, obj1, obj2, TCL_GLOBAL_ONLY);
+ hPtr = Tcl_FindHashEntry(&namesHash, obj1);
+ if (hPtr != NULL) {
+ Tcl_DeleteHashEntry(hPtr);
+ }
+ Tcl_DecrRefCount(obj1);
+ Tcl_DecrRefCount(obj2);
+ }
+ Tcl_MutexUnlock(&envMutex);
+ }
+
+ /*
+ * Delete those elements that existed in the array but which had no
+ * counterparts in the environment array.
+ */
+
+ for (hPtr=Tcl_FirstHashEntry(&namesHash, &search); hPtr!=NULL;
+ hPtr=Tcl_NextHashEntry(&search)) {
+ Tcl_Obj *elemName = Tcl_GetHashValue(hPtr);
+
+ TclObjUnsetVar2(interp, varNamePtr, elemName, TCL_GLOBAL_ONLY);
+ }
+ Tcl_DeleteHashTable(&namesHash);
+ Tcl_DecrRefCount(varNamePtr);
+
+ /*
+ * Re-establish the trace.
+ */
+
+ Tcl_TraceVar2(interp, "env", NULL,
+ TCL_GLOBAL_ONLY | TCL_TRACE_WRITES | TCL_TRACE_UNSETS |
+ TCL_TRACE_READS | TCL_TRACE_ARRAY, EnvTraceProc, NULL);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclSetEnv --
+ *
+ * Set an environment variable, replacing an existing value or creating a
+ * new variable if there doesn't exist a variable by the given name. This
+ * function is intended to be a stand-in for the UNIX "setenv" function
+ * so that applications using that function will interface properly to
+ * Tcl. To make it a stand-in, the Makefile must define "TclSetEnv" to
+ * "setenv".
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The environ array gets updated.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TclSetEnv(
+ const char *name, /* Name of variable whose value is to be set
+ * (UTF-8). */
+ const char *value) /* New value for variable (UTF-8). */
+{
+ Tcl_DString envString;
+ unsigned nameLength, valueLength;
+ int index, length;
+ char *p, *oldValue;
+ const char *p2;
+
+ /*
+ * Figure out where the entry is going to go. If the name doesn't already
+ * exist, enlarge the array if necessary to make room. If the name exists,
+ * free its old entry.
+ */
+
+ Tcl_MutexLock(&envMutex);
+ index = TclpFindVariable(name, &length);
+
+ if (index == -1) {
+#ifndef USE_PUTENV
+ /*
+ * We need to handle the case where the environment may be changed
+ * outside our control. ourEnvironSize is only valid if the current
+ * environment is the one we allocated. [Bug 979640]
+ */
+
+ if ((env.ourEnviron != environ) || (length+2 > env.ourEnvironSize)) {
+ char **newEnviron = ckalloc((length + 5) * sizeof(char *));
+
+ memcpy(newEnviron, environ, length * sizeof(char *));
+ if ((env.ourEnvironSize != 0) && (env.ourEnviron != NULL)) {
+ ckfree(env.ourEnviron);
+ }
+ environ = env.ourEnviron = newEnviron;
+ env.ourEnvironSize = length + 5;
+ }
+ index = length;
+ environ[index + 1] = NULL;
+#endif /* USE_PUTENV */
+ oldValue = NULL;
+ nameLength = strlen(name);
+ } else {
+ const char *env;
+
+ /*
+ * Compare the new value to the existing value. If they're the same
+ * then quit immediately (e.g. don't rewrite the value or propagate it
+ * to other interpreters). Otherwise, when there are N interpreters
+ * there will be N! propagations of the same value among the
+ * interpreters.
+ */
+
+ env = Tcl_ExternalToUtfDString(NULL, environ[index], -1, &envString);
+ if (strcmp(value, env + (length + 1)) == 0) {
+ Tcl_DStringFree(&envString);
+ Tcl_MutexUnlock(&envMutex);
+ return;
+ }
+ Tcl_DStringFree(&envString);
+
+ oldValue = environ[index];
+ nameLength = (unsigned) length;
+ }
+
+ /*
+ * Create a new entry. Build a complete UTF string that contains a
+ * "name=value" pattern. Then convert the string to the native encoding,
+ * and set the environ array value.
+ */
+
+ valueLength = strlen(value);
+ p = ckalloc(nameLength + valueLength + 2);
+ memcpy(p, name, nameLength);
+ p[nameLength] = '=';
+ memcpy(p+nameLength+1, value, valueLength+1);
+ p2 = Tcl_UtfToExternalDString(NULL, p, -1, &envString);
+
+ /*
+ * Copy the native string to heap memory.
+ */
+
+ p = ckrealloc(p, Tcl_DStringLength(&envString) + 1);
+ memcpy(p, p2, (unsigned) Tcl_DStringLength(&envString) + 1);
+ Tcl_DStringFree(&envString);
+
+#ifdef USE_PUTENV
+ /*
+ * Update the system environment.
+ */
+
+ putenv(p);
+ index = TclpFindVariable(name, &length);
+#else
+ environ[index] = p;
+#endif /* USE_PUTENV */
+
+ /*
+ * Watch out for versions of putenv that copy the string (e.g. VC++). In
+ * this case we need to free the string immediately. Otherwise update the
+ * string in the cache.
+ */
+
+ if ((index != -1) && (environ[index] == p)) {
+ ReplaceString(oldValue, p);
+#ifdef HAVE_PUTENV_THAT_COPIES
+ } else {
+ /*
+ * This putenv() copies instead of taking ownership.
+ */
+
+ ckfree(p);
+#endif /* HAVE_PUTENV_THAT_COPIES */
+ }
+
+ Tcl_MutexUnlock(&envMutex);
+
+ if (!strcmp(name, "HOME")) {
+ /*
+ * If the user's home directory has changed, we must invalidate the
+ * filesystem cache, because '~' expansions will now be incorrect.
+ */
+
+ Tcl_FSMountsChanged(NULL);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_PutEnv --
+ *
+ * Set an environment variable. Similar to setenv except that the
+ * information is passed in a single string of the form NAME=value,
+ * rather than as separate name strings. This function is intended to be
+ * a stand-in for the UNIX "putenv" function so that applications using
+ * that function will interface properly to Tcl. To make it a stand-in,
+ * the Makefile will define "Tcl_PutEnv" to "putenv".
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The environ array gets updated, as do all of the interpreters that we
+ * manage.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+Tcl_PutEnv(
+ const char *assignment) /* Info about environment variable in the form
+ * NAME=value. (native) */
+{
+ Tcl_DString nameString;
+ const char *name;
+ char *value;
+
+ if (assignment == NULL) {
+ return 0;
+ }
+
+ /*
+ * First convert the native string to UTF. Then separate the string into
+ * name and value parts, and call TclSetEnv to do all of the real work.
+ */
+
+ name = Tcl_ExternalToUtfDString(NULL, assignment, -1, &nameString);
+ value = strchr(name, '=');
+
+ if ((value != NULL) && (value != name)) {
+ value[0] = '\0';
+ TclSetEnv(name, value+1);
+ }
+
+ Tcl_DStringFree(&nameString);
+ return 0;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclUnsetEnv --
+ *
+ * Remove an environment variable, updating the "env" arrays in all
+ * interpreters managed by us. This function is intended to replace the
+ * UNIX "unsetenv" function (but to do this the Makefile must be modified
+ * to redefine "TclUnsetEnv" to "unsetenv".
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Interpreters are updated, as is environ.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TclUnsetEnv(
+ const char *name) /* Name of variable to remove (UTF-8). */
+{
+ char *oldValue;
+ int length;
+ int index;
+#ifdef USE_PUTENV_FOR_UNSET
+ Tcl_DString envString;
+ char *string;
+#else
+ char **envPtr;
+#endif /* USE_PUTENV_FOR_UNSET */
+
+ Tcl_MutexLock(&envMutex);
+ index = TclpFindVariable(name, &length);
+
+ /*
+ * First make sure that the environment variable exists to avoid doing
+ * needless work and to avoid recursion on the unset.
+ */
+
+ if (index == -1) {
+ Tcl_MutexUnlock(&envMutex);
+ return;
+ }
+
+ /*
+ * Remember the old value so we can free it if Tcl created the string.
+ */
+
+ oldValue = environ[index];
+
+ /*
+ * Update the system environment. This must be done before we update the
+ * interpreters or we will recurse.
+ */
+
+#ifdef USE_PUTENV_FOR_UNSET
+ /*
+ * For those platforms that support putenv to unset, Linux indicates
+ * that no = should be included, and Windows requires it.
+ */
+
+#if defined(_WIN32)
+ string = ckalloc(length + 2);
+ memcpy(string, name, (size_t) length);
+ string[length] = '=';
+ string[length+1] = '\0';
+#else
+ string = ckalloc(length + 1);
+ memcpy(string, name, (size_t) length);
+ string[length] = '\0';
+#endif /* _WIN32 */
+
+ Tcl_UtfToExternalDString(NULL, string, -1, &envString);
+ string = ckrealloc(string, Tcl_DStringLength(&envString) + 1);
+ memcpy(string, Tcl_DStringValue(&envString),
+ (unsigned) Tcl_DStringLength(&envString)+1);
+ Tcl_DStringFree(&envString);
+
+ putenv(string);
+
+ /*
+ * Watch out for versions of putenv that copy the string (e.g. VC++). In
+ * this case we need to free the string immediately. Otherwise update the
+ * string in the cache.
+ */
+
+ if (environ[index] == string) {
+ ReplaceString(oldValue, string);
+#ifdef HAVE_PUTENV_THAT_COPIES
+ } else {
+ /*
+ * This putenv() copies instead of taking ownership.
+ */
+
+ ckfree(string);
+#endif /* HAVE_PUTENV_THAT_COPIES */
+ }
+#else /* !USE_PUTENV_FOR_UNSET */
+ for (envPtr = environ+index+1; ; envPtr++) {
+ envPtr[-1] = *envPtr;
+ if (*envPtr == NULL) {
+ break;
+ }
+ }
+ ReplaceString(oldValue, NULL);
+#endif /* USE_PUTENV_FOR_UNSET */
+
+ Tcl_MutexUnlock(&envMutex);
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * TclGetEnv --
+ *
+ * Retrieve the value of an environment variable.
+ *
+ * Results:
+ * The result is a pointer to a string specifying the value of the
+ * environment variable, or NULL if that environment variable does not
+ * exist. Storage for the result string is allocated in valuePtr; the
+ * caller must call Tcl_DStringFree() when the result is no longer
+ * needed.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+const char *
+TclGetEnv(
+ const char *name, /* Name of environment variable to find
+ * (UTF-8). */
+ Tcl_DString *valuePtr) /* Uninitialized or free DString in which the
+ * value of the environment variable is
+ * stored. */
+{
+ int length, index;
+ const char *result;
+
+ Tcl_MutexLock(&envMutex);
+ index = TclpFindVariable(name, &length);
+ result = NULL;
+ if (index != -1) {
+ Tcl_DString envStr;
+
+ result = Tcl_ExternalToUtfDString(NULL, environ[index], -1, &envStr);
+ result += length;
+ if (*result == '=') {
+ result++;
+ Tcl_DStringInit(valuePtr);
+ Tcl_DStringAppend(valuePtr, result, -1);
+ result = Tcl_DStringValue(valuePtr);
+ } else {
+ result = NULL;
+ }
+ Tcl_DStringFree(&envStr);
+ }
+ Tcl_MutexUnlock(&envMutex);
+ return result;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * EnvTraceProc --
+ *
+ * This function is invoked whenever an environment variable is read,
+ * modified or deleted. It propagates the change to the global "environ"
+ * array.
+ *
+ * Results:
+ * Returns NULL to indicate success, or an error-message if the array
+ * element being handled doesn't exist.
+ *
+ * Side effects:
+ * Environment variable changes get propagated. If the whole "env" array
+ * is deleted, then we stop managing things for this interpreter (usually
+ * this happens because the whole interpreter is being deleted).
+ *
+ *----------------------------------------------------------------------
+ */
+
+ /* ARGSUSED */
+static char *
+EnvTraceProc(
+ ClientData clientData, /* Not used. */
+ Tcl_Interp *interp, /* Interpreter whose "env" variable is being
+ * modified. */
+ const char *name1, /* Better be "env". */
+ const char *name2, /* Name of variable being modified, or NULL if
+ * whole array is being deleted (UTF-8). */
+ int flags) /* Indicates what's happening. */
+{
+ /*
+ * For array traces, let TclSetupEnv do all the work.
+ */
+
+ if (flags & TCL_TRACE_ARRAY) {
+ TclSetupEnv(interp);
+ return NULL;
+ }
+
+ /*
+ * If name2 is NULL, then return and do nothing.
+ */
+
+ if (name2 == NULL) {
+ return NULL;
+ }
+
+ /*
+ * If a value is being set, call TclSetEnv to do all of the work.
+ */
+
+ if (flags & TCL_TRACE_WRITES) {
+ const char *value;
+
+ value = Tcl_GetVar2(interp, "env", name2, TCL_GLOBAL_ONLY);
+ TclSetEnv(name2, value);
+ }
+
+ /*
+ * If a value is being read, call TclGetEnv to do all of the work.
+ */
+
+ if (flags & TCL_TRACE_READS) {
+ Tcl_DString valueString;
+ const char *value = TclGetEnv(name2, &valueString);
+
+ if (value == NULL) {
+ return (char *) "no such variable";
+ }
+ Tcl_SetVar2(interp, name1, name2, value, 0);
+ Tcl_DStringFree(&valueString);
+ }
+
+ /*
+ * For unset traces, let TclUnsetEnv do all the work.
+ */
+
+ if (flags & TCL_TRACE_UNSETS) {
+ TclUnsetEnv(name2);
+ }
+ return NULL;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ReplaceString --
+ *
+ * Replace one string with another in the environment variable cache. The
+ * cache keeps track of all of the environment variables that Tcl has
+ * modified so they can be freed later.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * May free the old string.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+ReplaceString(
+ const char *oldStr, /* Old environment string. */
+ char *newStr) /* New environment string. */
+{
+ int i;
+
+ /*
+ * Check to see if the old value was allocated by Tcl. If so, it needs to
+ * be deallocated to avoid memory leaks. Note that this algorithm is O(n),
+ * not O(1). This will result in n-squared behavior if lots of environment
+ * changes are being made.
+ */
+
+ for (i = 0; i < env.cacheSize; i++) {
+ if (env.cache[i]==oldStr || env.cache[i]==NULL) {
+ break;
+ }
+ }
+ if (i < env.cacheSize) {
+ /*
+ * Replace or delete the old value.
+ */
+
+ if (env.cache[i]) {
+ ckfree(env.cache[i]);
+ }
+
+ if (newStr) {
+ env.cache[i] = newStr;
+ } else {
+ for (; i < env.cacheSize-1; i++) {
+ env.cache[i] = env.cache[i+1];
+ }
+ env.cache[env.cacheSize-1] = NULL;
+ }
+ } else {
+ /*
+ * We need to grow the cache in order to hold the new string.
+ */
+
+ const int growth = 5;
+
+ env.cache = ckrealloc(env.cache,
+ (env.cacheSize + growth) * sizeof(char *));
+ env.cache[env.cacheSize] = newStr;
+ (void) memset(env.cache+env.cacheSize+1, 0,
+ (size_t) (growth-1) * sizeof(char *));
+ env.cacheSize += growth;
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclFinalizeEnvironment --
+ *
+ * This function releases any storage allocated by this module that isn't
+ * still in use by the global environment. Any strings that are still in
+ * the environment will be leaked.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * May deallocate storage.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TclFinalizeEnvironment(void)
+{
+ /*
+ * For now we just deallocate the cache array and none of the environment
+ * strings. This may leak more memory that strictly necessary, since some
+ * of the strings may no longer be in the environment. However,
+ * determining which ones are ok to delete is n-squared, and is pretty
+ * unlikely, so we don't bother.
+ */
+
+ if (env.cache) {
+ ckfree(env.cache);
+ env.cache = NULL;
+ env.cacheSize = 0;
+#ifndef USE_PUTENV
+ env.ourEnvironSize = 0;
+#endif
+ }
+}
+
+/*
+ * Local Variables:
+ * mode: c
+ * c-basic-offset: 4
+ * fill-column: 78
+ * End:
+ */