+/* variables.c -- Functions for hacking shell variables. */
+
+/* Copyright (C) 1987,1989 Free Software Foundation, Inc.
+
+ This file is part of GNU Bash, the Bourne Again SHell.
+
+ Bash is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 1, or (at your option)
+ any later version.
+
+ Bash is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
+ License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bash; see the file COPYING. If not, write to the Free
+ Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <sys/types.h>
+
+#include "shell.h"
+#include "hash.h"
+#include "flags.h"
+
+#if defined (USG) && !defined (isc386) && !defined (sgi)
+struct passwd *getpwuid (), *getpwent ();
+#endif
+
+/* The list of shell variables that the user has created, or that came from
+ the environment. */
+HASH_TABLE *shell_variables = (HASH_TABLE *)NULL;
+
+/* The list of shell functions that the user has created, or that came from
+ the environment. */
+HASH_TABLE *shell_functions = (HASH_TABLE *)NULL;
+
+/* The current variable context. This is really a count of how deep into
+ executing functions we are. */
+int variable_context = 0;
+
+/* The array of shell assignments which are made only in the environment
+ for a single command. */
+char **temporary_env = (char **)NULL;
+
+/* Some funky variables which are known about specially. Here is where
+ "$*", "$1", and all the cruft is kept. */
+char *dollar_vars[10];
+WORD_LIST *rest_of_args = (WORD_LIST *)NULL;
+
+/* The value of $$. */
+int dollar_dollar_pid;
+
+/* An array which is passed to commands as their environment. It is
+ manufactured from the overlap of the initial environment and the
+ shell variables that are marked for export. */
+char **export_env = (char **)NULL;
+
+/* Non-zero means that we have to remake EXPORT_ENV. */
+int array_needs_making = 1;
+
+/* The list of variables that may not be unset in this shell. */
+char **non_unsettable_vars = (char **)NULL;
+
+#if defined (USG)
+#define DEFAULT_MAIL_PATH "/usr/mail/"
+#else
+#define DEFAULT_MAIL_PATH "/usr/spool/mail/"
+#endif
+
+/* Some forward declarations. */
+SHELL_VAR *set_if_not (); /* returns new or existing entry */
+static void sbrand (); /* set bash random number generator */
+
+/* Make VAR be auto-exported. VAR is a pointer to a SHELL_VAR. */
+#define set_auto_export(var) \
+{ var->attributes |= att_exported; array_needs_making = 1; }
+
+/* Initialize the shell variables from the current environment. */
+initialize_shell_variables (env)
+ char *env[];
+{
+ extern char *primary_prompt, *secondary_prompt;
+ char *name, *string;
+ int c, char_index;
+ int string_index = 0;
+ SHELL_VAR *temp_var;
+
+ if (!shell_variables)
+ shell_variables = make_hash_table (0);
+
+ if (!shell_functions)
+ shell_functions = make_hash_table (0);
+
+ /* Set up $PWD. */
+ {
+ char *get_working_directory (), *cd;
+
+ cd = get_working_directory ("shell-init");
+ if (cd)
+ {
+ bind_variable ("PWD", cd);
+ free (cd);
+ }
+ }
+
+ while (string = env[string_index++])
+ {
+ char_index = 0;
+
+ name = (char *)alloca (1 + strlen (string));
+
+ while ((c = *string++) && c != '=')
+ name[char_index++] = c;
+
+ name[char_index] = '\0';
+
+ /* If exported function, define it now. */
+ /* Posix.2 style exported function: name()=value */
+ if (strncmp ("() {", string, 4) == 0 ||
+ ((name[char_index - 1] == ')' &&
+ name[char_index - 2] == '(' &&
+ string[0] == '{')))
+ {
+ char *eval_string;
+
+ eval_string = (char *)xmalloc (3 + strlen (string) + strlen (name));
+ sprintf (eval_string, "%s %s", name, string);
+ parse_and_execute (eval_string, name);
+
+ if (name[char_index - 1] == ')')
+ name[char_index - 2] = '\0';
+ set_func_auto_export (name);
+ }
+ else
+ {
+ SHELL_VAR *v;
+
+ v = bind_variable (name, string);
+ set_auto_export (v);
+ }
+ }
+
+ /* Remember this pid. */
+ dollar_dollar_pid = (int)getpid ();
+
+ /* Now make our own defaults in case the vars that we think are
+ important are missing. */
+ temp_var = set_if_not ("PATH", DEFAULT_PATH_VALUE);
+ set_auto_export (temp_var);
+
+ temp_var = set_if_not ("TERM", "dumb");
+ set_auto_export (temp_var);
+
+ set_if_not ("PS1", primary_prompt);
+ set_if_not ("PS2", secondary_prompt);
+ set_if_not ("IFS", " \t\n");
+
+ /* Magic machine types. Pretty convenient. */
+ temp_var = set_if_not ("HOSTTYPE", HOSTTYPE);
+ set_auto_export (temp_var);
+
+ /* Default MAILPATH, and MAILCHECK. */
+ set_if_not ("MAILCHECK", "60");
+ if ((get_string_value ("MAIL") == (char *)NULL) &&
+ (get_string_value ("MAILPATH") == (char *)NULL))
+ {
+ extern char *current_user_name;
+ char *tem;
+
+ tem = (char *)xmalloc (1 + sizeof (DEFAULT_MAIL_PATH)
+ + strlen (current_user_name));
+ strcpy (tem, DEFAULT_MAIL_PATH);
+ strcat (tem, current_user_name);
+
+ bind_variable ("MAILPATH", tem);
+ free (tem);
+ }
+
+ /* Do some things with shell level. */
+ temp_var = set_if_not ("SHLVL", "0");
+ set_auto_export (temp_var);
+ adjust_shell_level (1);
+
+ /* Make a variable $PPID, which holds the pid of the shell's parent. */
+ {
+ char *ppid;
+ SHELL_VAR *v;
+ extern char *itos ();
+
+ ppid = itos ((int) getppid ());
+ v = find_variable ("PPID");
+
+ if (v)
+ v->attributes &= ~att_readonly;
+
+ v = bind_variable ("PPID", ppid);
+ v->attributes |= (att_readonly | att_integer);
+
+ non_unsettable ("PPID");
+ free (ppid);
+ }
+
+#if defined (GETOPTS_BUILTIN)
+ /* Initialize the `getopts' stuff. */
+ bind_variable ("OPTIND", "1");
+ bind_variable ("OPTERR", "1");
+#endif /* GETOPTS_BUILTIN */
+
+ /* Get the full pathname to THIS shell, and set the BASH variable
+ to it. */
+ {
+ extern char *shell_name, *find_user_command (), *full_pathname ();
+ extern int login_shell;
+ char *tname = find_user_command (shell_name);
+
+ if ((login_shell == 1) && (*shell_name != '/'))
+ {
+ struct passwd *entry = getpwuid (getuid ());
+
+ if (entry)
+ {
+ /* If HOME doesn't exist, set it. */
+ temp_var = (SHELL_VAR *)find_variable ("HOME");
+ if (!temp_var)
+ {
+ temp_var = bind_variable ("HOME", entry->pw_dir);
+ temp_var->attributes |= att_exported;
+ }
+ name = savestring (entry->pw_shell);
+ }
+ else
+ name = savestring ("a.out");
+ endpwent ();
+ }
+ else
+ {
+ if (!tname)
+ {
+ char *make_absolute ();
+ name = make_absolute (shell_name, get_string_value ("PWD"));
+ }
+ else
+ {
+ name = full_pathname (tname);
+ free (tname);
+ }
+ }
+
+ /* Make the exported environment variable SHELL be whatever the name of
+ this shell is. Note that the `tset' command looks at this variable
+ to determine what style of commands to output; if it ends in "csh",
+ then C-shell commands are output, else Bourne shell commands. */
+ temp_var = set_if_not ("SHELL", name);
+ set_auto_export (temp_var);
+
+ /* Make a variable called BASH, which is the name of THIS shell. */
+ temp_var = bind_variable ("BASH", name);
+ temp_var->attributes |= att_exported;
+
+ free (name);
+ }
+
+ /* Make a variable called BASH_VERSION which contains the version info. */
+ {
+ char tt[12];
+ extern char *dist_version;
+ extern int build_version;
+
+ sprintf (tt, "%s.%d", dist_version, build_version);
+ bind_variable ("BASH_VERSION", tt);
+ }
+
+ /* Set history variables to defaults, and then do whatever we would
+ do if the variable had just been set. */
+ {
+ char *tilde_expand ();
+ char *tem = tilde_expand ("~/.bash_history");
+
+ set_if_not ("HISTFILE", tem);
+ free (tem);
+
+ set_if_not ("HISTSIZE", "500");
+ sv_histsize ("HISTSIZE");
+ }
+
+ /* seed the random number generator */
+
+ sbrand (dollar_dollar_pid);
+
+ /* If we have inherited `noclobber' from a previous shell, then set
+ noclobbering on. */
+ {
+ extern int noclobber;
+
+ noclobber = find_variable ("noclobber") != NULL;
+ }
+
+ /* Initialize the dynamic variables, and seed their values */
+ initialize_dynamic_variables ();
+
+ non_unsettable ("PATH");
+ non_unsettable ("PS1");
+ non_unsettable ("PS2");
+ non_unsettable ("IFS");
+
+ /* Get the users real user id, and save that in an readonly variable.
+ To make the variable *really* readonly, we have added it to a special
+ list of vars. */
+
+ sv_uids ();
+ set_var_read_only ("UID");
+ set_var_read_only ("EUID");
+
+ non_unsettable ("EUID");
+ non_unsettable ("UID");
+}
+
+adjust_shell_level (change)
+ int change;
+{
+ extern int shell_level;
+ extern char *itos ();
+ char *new_level;
+ int old_level;
+
+
+ old_level = atoi (get_string_value ("SHLVL"));
+ shell_level = old_level + change;
+ new_level = itos (shell_level);
+ bind_variable ("SHLVL", new_level);
+ free (new_level);
+}
+
+/* Add NAME to the list of variables that cannot be unset
+ if it isn't already there. */
+non_unsettable (name)
+ char *name;
+{
+ register int i;
+
+ if (!non_unsettable_vars)
+ {
+ non_unsettable_vars = (char **)xmalloc (1 * sizeof (char *));
+ non_unsettable_vars[0] = (char *)NULL;
+ }
+
+ for (i = 0; non_unsettable_vars[i]; i++)
+ if (strcmp (non_unsettable_vars[i], name) == 0)
+ return;
+
+ non_unsettable_vars = (char **)
+ xrealloc (non_unsettable_vars, (2 + i) * sizeof (char *));
+ non_unsettable_vars[i] = savestring (name);
+ non_unsettable_vars[i + 1] = (char *)NULL;
+}
+
+/* Set NAME to VALUE if NAME has no value. */
+SHELL_VAR *
+set_if_not (name, value)
+ char *name, *value;
+{
+ SHELL_VAR *v = find_variable (name);
+
+ if (!v)
+ v = bind_variable (name, value);
+ return (v);
+}
+
+/* Map FUNCTION over the variables in VARIABLES. Return an array of the
+ variables that satisfy FUNCTION. Satisfy means that FUNCTION returns
+ a non-zero value for. A NULL value for FUNCTION means to use all
+ variables. */
+SHELL_VAR **
+map_over (function, var_hash_table)
+ Function *function;
+ HASH_TABLE* var_hash_table;
+{
+ register int i;
+ register BUCKET_CONTENTS *tlist;
+ SHELL_VAR *var, **list = (SHELL_VAR **)NULL;
+ int list_index = 0, list_size = 0;
+
+ for (i = 0; i < var_hash_table->nbuckets; i++)
+ {
+ tlist = get_hash_bucket (i, var_hash_table);
+
+ while (tlist)
+ {
+ var = (SHELL_VAR *)tlist->data;
+
+ if (!function || (*function) (var))
+ {
+ if (list_index + 1 >= list_size)
+ list = (SHELL_VAR **)
+ xrealloc (list, (list_size += 20) * sizeof (SHELL_VAR *));
+
+ list[list_index++] = var;
+ list[list_index] = (SHELL_VAR *)NULL;
+ }
+ tlist = tlist->next;
+ }
+ }
+ return (list);
+}
+
+int
+qsort_var_comp (var1, var2)
+ SHELL_VAR **var1, **var2;
+{
+ return (strcmp ((*var1)->name, (*var2)->name));
+}
+
+sort_variables (array)
+ SHELL_VAR **array;
+{
+ qsort (array, array_len (array), sizeof (SHELL_VAR *), qsort_var_comp);
+}
+
+/* Create a NULL terminated array of all the shell variables in TABLE. */
+static SHELL_VAR **
+all_vars (table)
+ HASH_TABLE *table;
+{
+ SHELL_VAR **list;
+
+ list = map_over ((Function *)NULL, table);
+ if (list)
+ sort_variables (list);
+ return (list);
+}
+
+/* Create a NULL terminated array of all the shell variables. */
+SHELL_VAR **
+all_shell_variables ()
+{
+ return (all_vars (shell_variables));
+}
+
+/* Create a NULL terminated array of all the shell functions. */
+SHELL_VAR **
+all_shell_functions ()
+{
+ return (all_vars (shell_functions));
+}
+
+/* Print VARS to stdout in such a way that they can be read back in. */
+print_var_list (list)
+ register SHELL_VAR **list;
+{
+ register int i;
+ register SHELL_VAR *var;
+
+ for (i = 0; list && (var = list[i]); i++)
+ if (!invisible_p (var))
+ print_assignment (var);
+}
+
+#if defined (NOTDEF)
+/* Print LIST (a linked list of shell variables) to stdout
+ by printing the names, without the values. Used to support the
+ `set +' command. */
+print_vars_no_values (list)
+ register SHELL_VAR **list;
+{
+ register int i;
+ register SHELL_VAR *var;
+
+ for (i = 0; list && (var = list[i]); i++)
+ if (!invisible_p (var))
+ printf ("%s\n", var->name);
+}
+#endif
+
+/* Print the value of a single SHELL_VAR. No newline is
+ output, but the variable is printed in such a way that
+ it can be read back in. */
+print_assignment (var)
+ SHELL_VAR *var;
+{
+ if (function_p (var) && var->value)
+ {
+ printf ("%s=", var->name);
+ print_var_function (var);
+ printf ("\n");
+ }
+ else if (var->value)
+ {
+ printf ("%s=", var->name);
+ print_var_value (var);
+ printf ("\n");
+ }
+}
+
+/* Print the value cell of VAR, a shell variable. Do not print
+ the name, nor leading/trailing newline. */
+print_var_value (var)
+ SHELL_VAR *var;
+{
+ if (var->value)
+ printf ("%s", var->value);
+}
+
+/* Print the function cell of VAR, a shell variable. Do not
+ print the name, nor leading/trailing newline. */
+print_var_function (var)
+ SHELL_VAR *var;
+{
+ char *named_function_string ();
+
+ if (function_p (var) && var->value)
+ printf ("%s", named_function_string ((char *)NULL, var->value, 1));
+}
+
+
+/* **************************************************************** */
+/* */
+/* Dynamic Variable Extension */
+/* */
+/* **************************************************************** */
+
+/* DYNAMIC VARIABLES
+
+ These are variables whose values are generated anew each time they are
+ referenced. These are implemented using a pair of function pointers
+ in the struct variable: assign_func, which is called from bind_variable,
+ and dynamic_value, which is called from find_variable.
+
+ assign_func is called from bind_variable, if bind_variable discovers
+ that the variable being assigned to has such a function. The function
+ is called as
+ SHELL_VAR *temp = (*(entry->assign_func)) (entry, value)
+ and the (SHELL_VAR *)temp is returned as the value of bind_variable. It
+ is usually ENTRY (self).
+
+ dynamic_value is called from find_variable to return a `new' value for
+ the specified dynamic varible. If this function is NULL, the variable
+ is treated as a `normal' shell variable. If it is not, however, then
+ this function is called like this:
+ tempvar = (*(var->dynamic_value)) (var);
+
+ Sometimes `tempvar' will replace the value of `var'. Other times, the
+ shell will simply use the string value. Pretty object-oriented, huh?
+
+ Be warned, though: if you `unset' a special variable, it loses its
+ special meaning, even if you subsequently set it.
+
+ The special assignment code would probably have been better put in
+ subst.c: do_assignment, in the same style as
+ stupidly_hack_special_variables, but I wanted the changes as
+ localized as possible. */
+
+/* The value of $SECONDS. This is the number of seconds since shell
+ invocation, or, the number of seconds since the last assignment + the
+ value of the last assignment. */
+static long seconds_value_assigned = (long)0;
+
+/* Originally defined in shell.c */
+extern time_t shell_start_time;
+
+SHELL_VAR *
+assign_seconds (self, value)
+ SHELL_VAR *self;
+ char *value;
+{
+ seconds_value_assigned = atol (value);
+ shell_start_time = NOW;
+ return (self);
+}
+
+SHELL_VAR *
+get_seconds (var)
+ SHELL_VAR *var;
+{
+ extern char *itos ();
+ time_t time_since_start;
+ char *p;
+
+ time_since_start = NOW - shell_start_time;
+ p = itos((int) seconds_value_assigned + time_since_start);
+
+ if (var->value)
+ free (var->value);
+
+ var->attributes |= att_integer;
+ var->value = p;
+ return (var);
+}
+
+/* The random number seed. You can change this by setting RANDOM. */
+static unsigned long rseed = 1;
+
+/* A linear congruential random number generator based on the ANSI
+ C standard. A more complicated one is overkill. */
+
+/* Returns a pseudo-random number between 0 and 32767. */
+static int
+brand ()
+{
+ rseed = rseed * 1103515245 + 12345;
+ return ((unsigned int)(rseed / 65536) % 32768);
+}
+
+/* Set the random number generator seed to SEED. */
+static void
+sbrand (seed)
+ int seed;
+{
+ rseed = seed;
+}
+
+static SHELL_VAR *
+assign_random (self, value)
+ SHELL_VAR *self;
+ char *value;
+{
+ int s = atoi (value);
+
+ sbrand (s);
+ return (self);
+}
+
+static SHELL_VAR *
+get_random (var)
+ SHELL_VAR *var;
+{
+ int rv;
+ char *p;
+ extern char *itos ();
+
+ rv = brand ();
+ p = itos ((int)rv);
+ if (var->value)
+ free (var->value);
+
+ var->attributes |= att_integer;
+ var->value = p;
+ return (var);
+}
+
+/* Function which returns the current line number. */
+static SHELL_VAR *
+get_lineno (var)
+ SHELL_VAR *var;
+{
+ extern int line_number;
+ char *p;
+ extern char *itos ();
+
+ p = itos (line_number);
+ if (var->value)
+ free (var->value);
+ var->value = p;
+ return (var);
+}
+
+initialize_dynamic_variables ()
+{
+ SHELL_VAR *v;
+
+ v = bind_variable ("SECONDS", (char *)NULL);
+ v->dynamic_value = get_seconds;
+ v->assign_func = assign_seconds;
+
+ v = bind_variable ("RANDOM", (char *)NULL);
+ v->dynamic_value = get_random;
+ v->assign_func = assign_random;
+
+ v = bind_variable ("LINENO", (char *)NULL);
+ v->dynamic_value = get_lineno;
+ v->assign_func = (DYNAMIC_FUNC *)NULL;
+}
+
+/* How to get a pointer to the shell variable or function named NAME.
+ HASHED_VARS is a pointer to the hash table containing the list
+ of interest (either variables or functions). */
+SHELL_VAR *
+var_lookup (name, hashed_vars)
+ char *name;
+ HASH_TABLE *hashed_vars;
+{
+ BUCKET_CONTENTS *bucket;
+
+ bucket = find_hash_item (name, hashed_vars);
+
+ if (bucket)
+ return ((SHELL_VAR *)bucket->data);
+ else
+ return ((SHELL_VAR *)NULL);
+}
+
+/* Look up the variable entry whose name matches STRING.
+ Returns the entry or NULL. */
+SHELL_VAR *
+find_variable (name)
+ char *name;
+{
+ extern int variable_context;
+ extern Function *this_shell_builtin;
+ SHELL_VAR *find_tempenv_variable ();
+ SHELL_VAR *var = (SHELL_VAR *)NULL;
+
+ /* If we are executing a function or builtin, first look in the
+ temporary environment for the variable. This allows constructs
+ such as "foo=x eval 'echo $foo'" to get the `exported' value
+ of $foo. */
+ if (variable_context || this_shell_builtin)
+ var = find_tempenv_variable (name);
+
+ if (!var)
+ var = var_lookup (name, shell_variables);
+
+ if (!var)
+ return ((SHELL_VAR *)NULL);
+
+ if (var->dynamic_value)
+ return ((*(var->dynamic_value)) (var));
+ else
+ return (var);
+}
+
+/* Look up the function entry whose name matches STRING.
+ Returns the entry or NULL. */
+SHELL_VAR *
+find_function (name)
+ char *name;
+{
+ return (var_lookup (name, shell_functions));
+}
+
+/* Return the string value of a variable. Return NULL if the variable
+ doesn't exist, or only has a function as a value. Don't cons a new
+ string. */
+char *
+get_string_value (var_name)
+ char *var_name;
+{
+ SHELL_VAR *var = find_variable (var_name);
+
+ if (!var)
+ return (char *)NULL;
+ else
+ return (var->value);
+}
+
+/* Create a local variable referenced by NAME. */
+SHELL_VAR *
+make_local_variable (name)
+ char *name;
+{
+ SHELL_VAR *new_var, *old_var, *bind_variable ();
+ BUCKET_CONTENTS *elt;
+
+ /* local foo; local foo; is a no-op. */
+ old_var = find_variable (name);
+ if (old_var && old_var->context == variable_context)
+ return (old_var);
+
+ elt = remove_hash_item (name, shell_variables);
+ if (elt)
+ {
+ old_var = (SHELL_VAR *)elt->data;
+ free (elt->key);
+ free (elt);
+ }
+ else
+ old_var = (SHELL_VAR *)NULL;
+
+ /* If a variable does not already exist with this name, then
+ just make a new one. */
+ if (!old_var)
+ {
+ new_var = bind_variable (name, "");
+ }
+ else
+ {
+ new_var = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
+
+ new_var->name = savestring (name);
+ new_var->value = savestring ("");
+
+ new_var->dynamic_value = (DYNAMIC_FUNC *)NULL;
+ new_var->assign_func = (DYNAMIC_FUNC *)NULL;
+
+ new_var->attributes = 0;
+
+ if (exported_p (old_var))
+ new_var->attributes |= att_exported;
+
+ new_var->prev_context = old_var;
+ elt = add_hash_item (savestring (name), shell_variables);
+ elt->data = (char *)new_var;
+ }
+
+ new_var->context = variable_context;
+ return (new_var);
+}
+
+/* Bind a variable NAME to VALUE. This conses up the name
+ and value strings. */
+SHELL_VAR *
+bind_variable (name, value)
+ char *name, *value;
+{
+ SHELL_VAR *entry = var_lookup (name, shell_variables);
+
+ if (!entry)
+ {
+ /* Make a new entry for this variable. Then do the binding. */
+ entry = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
+
+ entry->attributes = 0;
+ entry->name = savestring (name);
+
+ if (value)
+ entry->value = savestring (value);
+ else
+ entry->value = (char *)NULL;
+
+ entry->dynamic_value = (DYNAMIC_FUNC *)NULL;
+ entry->assign_func = (DYNAMIC_FUNC *)NULL;
+
+ /* Always assume variables are to be made at toplevel!
+ make_local_variable has the responsibilty of changing the
+ variable context. */
+ entry->context = 0;
+ entry->prev_context = (SHELL_VAR *)NULL;
+
+ {
+ BUCKET_CONTENTS *elt;
+
+ elt = add_hash_item (savestring (name), shell_variables);
+ elt->data = (char *)entry;
+ }
+ }
+ else if (entry->assign_func)
+ return ((*(entry->assign_func)) (entry, value));
+ else
+ {
+ if (readonly_p (entry))
+ {
+ report_error ("%s: read-only variable", name);
+ return (entry);
+ }
+
+ /* If this variable has had its type set to integer (via `declare -i'),
+ then do expression evaluation on it and store the result. The
+ functions in expr.c (evalexp and bind_int_variable) are responsible
+ for turning off the integer flag if they don't want further
+ evaluation done. */
+ if (integer_p (entry))
+ {
+ long val, evalexp();
+ extern char *itos();
+
+ val = evalexp (value);
+ /* We cannot free () entry->value before this; what if the string
+ we are working is `even=even+2'? We need the original value
+ around while we are doing the evaluation to handle any possible
+ recursion. */
+ if (entry->value)
+ free (entry->value);
+
+ entry->value = itos (val);
+ }
+ else
+ {
+ if (entry->value)
+ free (entry->value);
+
+ if (value)
+ entry->value = savestring (value);
+ else
+ entry->value = (char *)NULL;
+ }
+ }
+
+ if (mark_modified_vars)
+ entry->attributes |= att_exported;
+
+ if (exported_p (entry))
+ array_needs_making = 1;
+
+ return (entry);
+}
+
+/* Dispose of the information attached to VAR. */
+dispose_variable (var)
+ SHELL_VAR *var;
+{
+ if (!var)
+ return;
+
+ if (function_p (var))
+ dispose_command (var->value);
+ else if (var->value)
+ free (var->value);
+
+ free (var->name);
+
+ if (exported_p (var))
+ array_needs_making = 1;
+
+ free (var);
+}
+
+/* Unset the variable referenced by NAME. */
+unbind_variable (name)
+ char *name;
+{
+ SHELL_VAR *var = find_variable (name);
+
+ if (!var)
+ return (-1);
+
+ if (var->value)
+ {
+ free (var->value);
+ var->value = (char *)NULL;
+ }
+
+ makunbound (name, shell_variables);
+
+ return (0);
+}
+
+/* Make the variable associated with NAME go away. HASH_LIST is the
+ hash table from which this variable should be deleted (either
+ shell_variables or shell_functions).
+ Returns non-zero if the variable couldn't be found. */
+makunbound (name, hash_list)
+ char *name;
+ HASH_TABLE *hash_list;
+{
+ BUCKET_CONTENTS *elt;
+ SHELL_VAR *old_var, *new_var;
+
+ elt = remove_hash_item (name, hash_list);
+
+ if (!elt)
+ return (-1);
+
+ old_var = (SHELL_VAR *)elt->data;
+ new_var = old_var->prev_context;
+
+ if (old_var && exported_p (old_var))
+ array_needs_making++;
+
+ if (new_var)
+ {
+ /* Has to be a variable, functions don't have previous contexts. */
+ BUCKET_CONTENTS *new_elt;
+
+ new_elt = add_hash_item (savestring (new_var->name), hash_list);
+ new_elt->data = (char *)new_var;
+
+ if (exported_p (new_var))
+ set_var_auto_export (new_var->name);
+ }
+
+ free (elt->key);
+ free (elt);
+
+ dispose_variable (old_var);
+ stupidly_hack_special_variables (name);
+ return (0);
+}
+
+/* Remove the variable with NAME if it is a local variable in the
+ current context. */
+kill_local_variable (name)
+ char *name;
+{
+ SHELL_VAR *temp = find_variable (name);
+
+ if (temp && (temp->context == variable_context))
+ {
+ makunbound (name, shell_variables);
+ return (0);
+ }
+ return (-1);
+}
+
+/* Get rid of all of the variables in the current context. */
+int
+variable_in_context (var)
+ SHELL_VAR *var;
+{
+ return (var && var->context == variable_context);
+}
+
+kill_all_local_variables ()
+{
+ register int i, pass;
+ register SHELL_VAR *var, **list;
+ HASH_TABLE *varlist;
+
+ for (pass = 0; pass < 2; pass++)
+ {
+ varlist = pass ? shell_functions : shell_variables;
+
+ list = map_over (variable_in_context, varlist);
+
+ if (list)
+ {
+ for (i = 0; var = list[i]; i++)
+ makunbound (var->name, varlist);
+
+ free (list);
+ }
+ }
+}
+
+/* Delete the entire contents of the hash table. */
+delete_all_variables (hashed_vars)
+ HASH_TABLE *hashed_vars;
+{
+ register int i;
+ register BUCKET_CONTENTS *bucket;
+
+ for (i = 0; i < hashed_vars->nbuckets; i++)
+ {
+ bucket = hashed_vars->bucket_array[i];
+
+ while (bucket)
+ {
+ BUCKET_CONTENTS *temp = bucket;
+ SHELL_VAR *var, *prev;
+
+ bucket = bucket->next;
+
+ var = (SHELL_VAR *)temp->data;
+
+ while (var)
+ {
+ prev = var->prev_context;
+ dispose_variable (var);
+
+ var = prev;
+ }
+
+ free (temp->key);
+ free (temp);
+ }
+ hashed_vars->bucket_array[i] = (BUCKET_CONTENTS *)NULL;
+ }
+}
+
+SHELL_VAR *
+new_shell_variable (name)
+ char *name;
+{
+ SHELL_VAR *var;
+
+ var = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
+
+ bzero (var, sizeof (SHELL_VAR));
+ var->name = savestring (name);
+ return (var);
+}
+
+/* Do a function binding to a variable. You pass the name and
+ the command to bind to. This conses the name and command. */
+SHELL_VAR *
+bind_function (name, value)
+ char *name;
+ COMMAND *value;
+{
+ SHELL_VAR *entry = find_function (name);
+
+ if (!entry)
+ {
+ BUCKET_CONTENTS *elt;
+
+ elt = add_hash_item (savestring (name), shell_functions);
+
+ elt->data = (char *)new_shell_variable (name);
+ entry = (SHELL_VAR *)elt->data;
+ entry->dynamic_value = (DYNAMIC_FUNC *)NULL;
+ entry->assign_func = (DYNAMIC_FUNC *)NULL;
+
+ /* Functions are always made at the top level. This allows a
+ function to define another function (like autoload). */
+ entry->context = 0;
+ }
+
+ if (entry->value)
+ dispose_command (entry->value);
+
+ if (value) /* I don't think this can happen anymore */
+ entry->value = (char *)copy_command (value);
+ else
+ entry->value = (char *)NULL;
+
+ entry->attributes |= att_function;
+
+ if (mark_modified_vars)
+ entry->attributes |= att_exported;
+
+ entry->attributes &= ~att_invisible; /* Just to be sure */
+
+ array_needs_making = 1;
+
+ return (entry);
+}
+
+/* Copy VAR to a new data structure and return that structure. */
+SHELL_VAR *
+copy_variable (var)
+ SHELL_VAR *var;
+{
+ SHELL_VAR *copy = (SHELL_VAR *)NULL;
+
+ if (var)
+ {
+ copy = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
+
+ copy->attributes = var->attributes;
+ copy->name = savestring (var->name);
+
+ if (function_p (var))
+ copy->value = (char *)copy_command (var->value);
+ else if (var->value)
+ copy->value = savestring (var->value);
+ else
+ copy->value = (char *)NULL;
+
+ copy->dynamic_value = var->dynamic_value;
+ copy->assign_func = var->assign_func;
+
+ copy->context = var->context;
+
+ /* Don't bother copying previous contexts along with this variable. */
+ copy->prev_context = (SHELL_VAR *)NULL;
+ }
+ return (copy);
+}
+
+/* Make the variable associated with NAME be read-only.
+ If NAME does not exist yet, create it. */
+set_var_read_only (name)
+ char *name;
+{
+ SHELL_VAR *entry = find_variable (name);
+
+ if (!entry)
+ {
+ entry = bind_variable (name, "");
+ if (!no_invisible_vars)
+ entry->attributes |= att_invisible;
+ }
+ entry->attributes |= att_readonly;
+}
+
+/* Make the function associated with NAME be read-only.
+ If NAME does not exist, we just punt, like auto_export code below. */
+set_func_read_only (name)
+ char *name;
+{
+ SHELL_VAR *entry = find_function (name);
+
+ if (entry)
+ entry->attributes |= att_readonly;
+}
+
+/* Make the variable associated with NAME be auto-exported.
+ If NAME does not exist yet, create it. */
+set_var_auto_export (name)
+ char *name;
+{
+ SHELL_VAR *entry = find_variable (name);
+
+ if (!entry)
+ {
+ entry = bind_variable (name, "");
+ if (!no_invisible_vars)
+ entry->attributes |= att_invisible;
+ }
+
+ set_auto_export (entry);
+}
+
+/* Make the function associated with NAME be auto-exported. */
+set_func_auto_export (name)
+ char *name;
+{
+ SHELL_VAR *entry = find_function (name);
+
+ if (entry)
+ {
+ entry->attributes |= att_exported;
+ array_needs_making = 1;
+ }
+}
+
+/* Returns non-zero if STRING is an assignment statement. The returned value
+ is the index of the `=' sign. */
+assignment (string)
+ char *string;
+{
+ register int c, index = 0;
+
+ c = string[index];
+
+ if (!isletter (c) && c != '_')
+ return (0);
+
+ while (c = string[index])
+ {
+ /* The following is safe. Note that '=' at the start of a word
+ is not an assignment statement. */
+ if (c == '=')
+ return (index);
+
+ if (!isletter (c) && !digit (c) && c != '_')
+ return (0);
+
+ index++;
+ }
+ return (0);
+}
+
+int
+visible_var (var)
+ SHELL_VAR *var;
+{
+ return (!invisible_p (var));
+}
+
+SHELL_VAR **
+all_visible_variables ()
+{
+ SHELL_VAR **list;
+
+ list = map_over (visible_var, shell_variables);
+
+ if (list)
+ sort_variables (list);
+
+ return (list);
+}
+
+SHELL_VAR **
+all_visible_functions ()
+{
+ SHELL_VAR **list;
+
+ list = map_over (visible_var, shell_functions);
+
+ if (list)
+ sort_variables (list);
+
+ return (list);
+}
+
+/* Return non-zero if the variable VAR is visible and exported. */
+int
+visible_and_exported (var)
+ SHELL_VAR *var;
+{
+ return (!invisible_p (var) && exported_p (var));
+}
+
+/* Make an array of assignment statements from the hash table
+ HASHED_VARS which contains SHELL_VARs. Only visible, exported
+ variables are eligible. */
+char **
+make_var_array (hashed_vars)
+ HASH_TABLE *hashed_vars;
+{
+ register int i, list_index;
+ register SHELL_VAR *var;
+ char **list = (char **)NULL;
+ SHELL_VAR **vars;
+
+ vars = map_over (visible_and_exported, hashed_vars);
+
+ if (!vars)
+ return (char **)NULL;
+
+ list = (char **)xmalloc ((1 + array_len ((char **)vars)) * sizeof (char *));
+
+ for (i = 0, list_index = 0; var = vars[i]; i++)
+ {
+ char *value, *named_function_string ();
+
+ if (function_p (var))
+ {
+ value =
+ named_function_string ((char *)NULL,
+ (COMMAND *)function_cell (var), 0);
+ }
+ else
+ value = value_cell (var);
+
+ if (value)
+ {
+#if 0
+ list[list_index] =
+ (char *)xmalloc (2 + strlen (var->name) + strlen (value));
+
+ sprintf (list[list_index], "%s=%s", var->name, value);
+#else
+ /* Let's see if this makes any kind of performance difference. */
+ int name_len = strlen (var->name);
+ int value_len = strlen (value);
+ char *p;
+
+ p = list[list_index] = (char *)xmalloc (2 + name_len + value_len);
+ strcpy (p, var->name);
+ p[name_len] = '=';
+ strcpy (&p[name_len + 1], value);
+#endif
+ list_index++;
+ }
+ }
+
+ free (vars);
+ list[list_index] = (char *)NULL;
+ return (list);
+}
+
+/* Add STRING to the array of foo=bar strings that we already
+ have to add to the environment. */
+assign_in_env (string)
+ char *string;
+{
+ int size;
+
+ int offset = assignment (string);
+ char *name = savestring (string);
+ char *temp, *value = (char *)NULL;
+
+ if (name[offset] == '=')
+ {
+ char *tilde_expand (), *string_list ();
+ WORD_LIST *list, *expand_string_unsplit ();
+
+ name[offset] = 0;
+ temp = name + offset + 1;
+ temp = tilde_expand (temp);
+
+ list = expand_string_unsplit (temp, 0);
+ value = string_list (list);
+
+ if (list)
+ dispose_words (list);
+
+ free (temp);
+ }
+
+ if (!value) value = savestring ("");
+
+ temp = (char *)xmalloc (2 + strlen (name) + strlen (value));
+ sprintf (temp, "%s=%s", name, value);
+ free (name);
+
+ if (!temporary_env)
+ {
+ temporary_env = (char **)xmalloc (sizeof (char *));
+ temporary_env [0] = (char *)NULL;
+ }
+
+ size = array_len (temporary_env);
+ temporary_env = (char **)
+ xrealloc (temporary_env, (size + 2) * (sizeof (char *)));
+
+ temporary_env[size] = (temp);
+ temporary_env[size + 1] = (char *)NULL;
+ array_needs_making = 1;
+
+ if (echo_command_at_execute)
+ {
+ /* The K*rn shell prints the `+ ' in front of assignment statements,
+ so we do too. */
+ extern char *indirection_level_string ();
+ fprintf (stderr, "%s%s\n", indirection_level_string (), temp);
+ fflush (stderr);
+ }
+}
+
+/* Find a variable in the temporary environment that is named NAME.
+ Return a consed variable, or NULL if not found. */
+SHELL_VAR *
+find_tempenv_variable (name)
+ char *name;
+{
+ register int i, l = strlen (name);
+
+ if (!temporary_env)
+ return ((SHELL_VAR *)NULL);
+
+ for (i = 0; temporary_env[i]; i++)
+ {
+ if (strncmp (temporary_env[i], name, l) == 0 &&
+ temporary_env[i][l] == '=')
+ {
+ SHELL_VAR *temp;
+
+ temp = new_shell_variable (name);
+
+ if (temporary_env[i][l + 1])
+ temp->value = savestring (&temporary_env[i][l + 1]);
+ else
+ temp->value = savestring ("");
+ temp->attributes = att_exported;
+ temp->context = 0;
+ temp->prev_context = (SHELL_VAR *)NULL;
+
+ temp->dynamic_value = (DYNAMIC_FUNC *)NULL;
+ temp->assign_func = (DYNAMIC_FUNC *)NULL;
+
+ return (temp);
+ }
+ }
+ return ((SHELL_VAR *)NULL);
+}
+
+/* Free the storage used in the variable array for temporary
+ environment variables. */
+dispose_used_env_vars ()
+{
+ if (!temporary_env)
+ return;
+
+ free_array (temporary_env);
+ temporary_env = (char **)NULL;
+ array_needs_making = 1;
+}
+
+/* Stupid comparison routine for qsort () ing strings. */
+int
+qsort_string_compare (s1, s2)
+ register char **s1, **s2;
+{
+ return (strcmp (*s1, *s2));
+}
+
+/* Sort ARRAY, a null terminated array of pointers to strings. */
+sort_char_array (array)
+ char **array;
+{
+ qsort (array, array_len (array), sizeof (char *), qsort_string_compare);
+}
+
+#define ISFUNC(s, o) ((s[o + 1] == '(') && (s[o + 2] == ')'))
+
+/* Add ASSIGN to ARRAY, or supercede a previous assignment in the
+ array with the same left-hand side. Return the new array. */
+char **
+add_or_supercede (assign, array)
+ char *assign;
+ register char **array;
+{
+ register int i;
+ int equal_offset = assignment (assign);
+
+ if (!equal_offset)
+ return (array);
+
+ /* If this is a function, then only supercede the function definition.
+ We do this by including the `=(' in the comparison. */
+ if (assign[equal_offset + 1] == '(')
+ equal_offset++;
+
+ for (i = 0; array[i]; i++)
+ {
+ if (STREQN (assign, array[i], equal_offset + 1))
+ {
+ free (array[i]);
+ array[i] = savestring (assign);
+ return (array);
+ }
+ }
+ array = (char **)xrealloc (array, ((2 + i) * sizeof (char *)));
+ array[i++] = savestring (assign);
+ array[i] = (char *)NULL;
+ return (array);
+}
+
+/* Make the environment array for the command about to be executed. If the
+ array needs making. Otherwise, do nothing. If a shell action could
+ change the array that commands receive for their environment, then the
+ code should `array_needs_making++'. */
+maybe_make_export_env ()
+{
+ register int i;
+ register char **temp_array;
+
+ if (array_needs_making)
+ {
+ if (export_env)
+ free_array (export_env);
+
+#ifdef SHADOWED_ENV
+ export_env =
+ (char **)xmalloc ((1 + array_len (shell_environment)) * sizeof (char *));
+
+ for (i = 0; shell_environment[i]; i++)
+ export_env[i] = savestring (shell_environment[i]);
+ export_env[i] = (char *)NULL;
+
+#else /* !SHADOWED_ENV */
+
+ export_env = (char **)xmalloc (sizeof (char *));
+ export_env[0] = (char *)NULL;
+
+#endif /* SHADOWED_ENV */
+
+ temp_array = make_var_array (shell_variables);
+ for (i = 0; temp_array && temp_array[i]; i++)
+ export_env = add_or_supercede (temp_array[i], export_env);
+ free_array (temp_array);
+
+ temp_array = make_var_array (shell_functions);
+ for (i = 0; temp_array && temp_array[i]; i++)
+ export_env = add_or_supercede (temp_array[i], export_env);
+ free_array (temp_array);
+
+ if (temporary_env)
+ {
+ for (i = 0; temporary_env[i]; i++)
+ export_env = add_or_supercede (temporary_env[i], export_env);
+
+ /* Sort the array alphabetically. */
+ sort_char_array (export_env);
+ }
+ array_needs_making = 0;
+ }
+}
+
+/* We always put _ in the environment as the name of this command. */
+put_command_name_into_env (command_name)
+ char *command_name;
+{
+ char *dummy;
+
+ dummy = (char *)xmalloc (4 + strlen (command_name));
+
+ /* These three statements replace a call to sprintf */
+ dummy[0] = '_';
+ dummy[1] = '=';
+ strcpy (&dummy[2], command_name);
+ export_env = add_or_supercede (dummy, export_env);
+ free (dummy);
+}
+
+/* We supply our own version of getenv () because we want library
+ routines to get the changed values of exported variables. */
+char *last_tempenv_value = (char *)NULL;
+
+/* The NeXT C library has getenv () defined and used in the same file.
+ This screws our scheme. However, Bash will run on the NeXT using
+ the C library getenv (), since right now the only environment variable
+ that we care about is HOME, and that is already defined. */
+#if defined (__STDC__)
+# define _CONST_HACK const
+#else
+# define _CONST_HACK
+#endif /* !__STDC__ */
+
+#if !defined (NeXT)
+char *
+getenv (name)
+ char _CONST_HACK *name;
+{
+ SHELL_VAR *var = find_tempenv_variable (name);
+
+ if (var)
+ {
+ if (last_tempenv_value)
+ free (last_tempenv_value);
+
+ last_tempenv_value = savestring (value_cell (var));
+ dispose_variable (var);
+ return (last_tempenv_value);
+ }
+ else if (shell_variables)
+ {
+ var = find_variable (name);
+ if (var && exported_p (var))
+ return (value_cell (var));
+ }
+ else
+ {
+ register int i, len = strlen (name);
+ extern char **environ;
+
+ /* In some cases, s5r3 invokes getenv() before main(); BSD systems
+ using gprof also exhibit this behavior. This means that
+ shell_variables will be 0 when this is invoked. We look up the
+ variable in the real environment in that case. */
+
+ for (i = 0; environ[i]; i++)
+ {
+ if ((strncmp (environ[i], name, len) == 0) &&
+ (environ[i][len] == '='))
+ return (environ[i] + len + 1);
+ }
+ }
+
+ return ((char *)NULL);
+}
+#endif /* NeXT */