/* 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 #include #include #include #include "bashansi.h" #include "shell.h" #include "hash.h" #include "flags.h" #include "builtins/common.h" extern char *indirection_level_string (); extern char *find_user_command (); extern char *tilde_expand (), *named_function_string (); extern int posixly_correct; extern struct user_info current_user; /* 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; /* The array of shell assignments which are in the environment for the execution of a shell function. */ char **function_env = (char **)NULL; /* The array of shell assignments which are made only in the environment for the execution of a shell builtin command which may cause more than one command to be executed (e.g., "source"). */ char **builtin_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 static char *have_local_variables; /* XXX */ static int local_variable_stack_size = 0; /* XXX */ /* Some forward declarations. */ SHELL_VAR *set_if_not (); /* returns new or existing entry */ static void sbrand (); /* set bash random number generator */ void non_unsettable (); /* 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; } #if defined (HISTORY) extern int remember_on_history; #endif /* HISTORY */ /* Initialize the shell variables from the current environment. */ initialize_shell_variables (env) char *env[]; { extern char *primary_prompt, *secondary_prompt; extern int interactive_shell; #if defined (HISTORY) extern int remember_on_history; #endif /* HISTORY */ char *name, *string, *current_dir; 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); while (string = env[string_index++]) { int string_length; char_index = 0; string_length = strlen (string); name = (char *)alloca (1 + string_length); while ((c = *string++) && c != '=') name[char_index++] = c; name[char_index] = '\0'; /* If exported function, define it now. */ if (!privileged_mode && STREQN ("() {", string, 4)) { SHELL_VAR *f; char *eval_string; eval_string = (char *)xmalloc (3 + string_length + 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'; if (f = find_function (name)) { f->attributes |= (att_exported | att_imported); array_needs_making = 1; } else report_error ("error importing function definition for `%s'", name); } else { SHELL_VAR *v; v = bind_variable (name, string); v->attributes |= (att_exported | att_imported); array_needs_making = 1; } } /* If we got PWD from the environment, update our idea of the current working directory. In any case, make sure that PWD exists before checking it. It is possible for getwd () to fail on shell startup, and in that case, PWD would be undefined. */ temp_var = find_variable ("PWD"); if (temp_var && imported_p (temp_var)) { char *curdir; if (curdir = value_cell (temp_var)) { if (same_file (curdir, ".", (struct stat *)NULL, (struct stat *)NULL)) set_working_directory (curdir); else { current_dir = get_working_directory ("shell-init"); if (current_dir) { bind_variable ("PWD", current_dir); free (current_dir); } } } } else /* Set up $PWD. */ { current_dir = get_working_directory ("shell-init"); if (current_dir) { bind_variable ("PWD", current_dir); free (current_dir); } } /* 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); if (interactive_shell) { set_if_not ("PS1", primary_prompt); set_if_not ("PS2", secondary_prompt); } #if defined (INSECURITY) set_if_not ("IFS", " \t\n"); #else bind_variable ("IFS", " \t\n"); #endif /* INSECURITY */ /* Magic machine types. Pretty convenient. */ temp_var = set_if_not ("HOSTTYPE", HOSTTYPE); set_auto_export (temp_var); /* Default MAILPATH, and MAILCHECK for interactive shells. */ if (interactive_shell) { set_if_not ("MAILCHECK", "60"); if ((get_string_value ("MAIL") == (char *)NULL) && (get_string_value ("MAILPATH") == (char *)NULL)) { char *tem; tem = (char *)xmalloc (1 + sizeof (DEFAULT_MAIL_PATH) + strlen (current_user.user_name)); strcpy (tem, DEFAULT_MAIL_PATH); strcat (tem, current_user.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; ppid = itos ((int) getppid ()); v = find_variable ("PPID"); if (v) v->attributes &= ~(att_readonly | att_exported); 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; extern int login_shell; char *tname = find_user_command (shell_name); if ((login_shell == 1) && (*shell_name != '/')) { /* If HOME doesn't exist, set it. */ temp_var = set_if_not ("HOME", current_user.home_dir); temp_var->attributes |= att_exported; name = savestring (current_user.shell); } 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 the user's login shell. 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", current_user.shell); 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[16]; extern char *dist_version; extern int build_version; sprintf (tt, "%s.%d-CWRU", dist_version, build_version); bind_variable ("BASH_VERSION", tt); } #if defined (HISTORY) /* Set history variables to defaults, and then do whatever we would do if the variable had just been set. Do this only in the case that we are remembering commands on the history list. */ if (remember_on_history) { char *tem; if (posixly_correct) tem = tilde_expand ("~/.sh_history"); else tem = tilde_expand ("~/.bash_history"); set_if_not ("HISTFILE", tem); free (tem); set_if_not ("HISTSIZE", "500"); sv_histsize ("HISTSIZE"); } #endif /* HISTORY */ /* Seed the random number generator. */ sbrand (dollar_dollar_pid); /* Handle some "special" variables that we may have inherited from a parent shell. */ { extern int noclobber; noclobber = find_variable ("noclobber") != (SHELL_VAR *)NULL; } #if defined (HISTORY) { extern int command_oriented_history; if (interactive_shell && remember_on_history) command_oriented_history = find_variable ("command_oriented_history") != (SHELL_VAR *)NULL; } #endif /* HISTORY */ /* Initialize the dynamic variables, and seed their values. */ initialize_dynamic_variables (); non_unsettable ("PATH"); non_unsettable ("IFS"); if (interactive_shell) { non_unsettable ("PS1"); non_unsettable ("PS2"); } /* Get the users real user id, and save that in a 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; char *new_level, *old_SHLVL; int old_level; old_SHLVL = get_string_value ("SHLVL"); if (old_SHLVL) old_level = atoi (old_SHLVL); else old_level = 0; shell_level = old_level + change; if (shell_level < 0) shell_level = 0; 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. */ void 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 (STREQ (non_unsettable_vars[i], name)) 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; { int result; if ((result = (*var1)->name[0] - (*var2)->name[0]) == 0) result = strcmp ((*var1)->name, (*var2)->name); return (result); } 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; { 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; { 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; 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; 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 named NAME. If SEARCH_TEMPENV is non-zero, then also search the temporarily built list of exported variables. */ SHELL_VAR * find_variable_internal (name, search_tempenv) char *name; int search_tempenv; { extern int subshell_environment; SHELL_VAR *find_tempenv_variable (); SHELL_VAR *var = (SHELL_VAR *)NULL; /* If explicitly requested, 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. This happens if we are executing a function or builtin, or if we are looking up a variable in a "subshell environment". */ if ((search_tempenv || subshell_environment) && (temporary_env || builtin_env || function_env)) 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 variable entry named NAME. Returns the entry or NULL. */ SHELL_VAR * find_variable (name) char *name; { extern int variable_context; extern Function *this_shell_builtin; return (find_variable_internal (name, (variable_context || this_shell_builtin))); } /* 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; /* XXX */ if (local_variable_stack_size <= variable_context) { int old_size = local_variable_stack_size; while (local_variable_stack_size <= variable_context) local_variable_stack_size += 8; have_local_variables = xrealloc (have_local_variables, local_variable_stack_size); bzero (have_local_variables + old_size, local_variable_stack_size - old_size); } have_local_variables[variable_context] = 1; /* XXX */ 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); } /* Variables which are bound are visible. */ entry->attributes &= ~att_invisible; /* 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 (); 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. */ void 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; char *t; 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); } /* Have to save a copy of name here, because it might refer to old_var->name. If so, stupidly_hack_special_variables will reference freed memory. */ t = savestring (name); free (elt->key); free (elt); dispose_variable (old_var); stupidly_hack_special_variables (t); free (t); 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); } void kill_all_local_variables () { register int i, pass; register SHELL_VAR *var, **list; HASH_TABLE *varlist; /* XXX */ if (!have_local_variables || have_local_variables[variable_context] == 0) return; 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); } } have_local_variables[variable_context] = 0; /* XXX */ } /* 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); } static 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. */ static 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; if (function_p (var)) value = named_function_string ((char *)NULL, (COMMAND *)function_cell (var), 0); else value = value_cell (var); if (value) { 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); 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] == '=') { WORD_LIST *list; 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. */ fprintf (stderr, "%s%s\n", indirection_level_string (), temp); fflush (stderr); } return 1; } /* Search for NAME in ARRAY, an array of strings in the same format as the environment array (i.e, name=value). If NAME is present, make a new variable and return it. Otherwise, return NULL. */ static SHELL_VAR * find_name_in_env_array (name, array) char *name; char **array; { register int i, l = strlen (name); if (!array) return ((SHELL_VAR *)NULL); for (i = 0; array[i]; i++) { if (STREQN (array[i], name, l) && array[i][l] == '=') { SHELL_VAR *temp; temp = new_shell_variable (name); if (array[i][l + 1]) temp->value = savestring (&array[i][l + 1]); else temp->value = (char *) NULL; 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); } /* Find a variable in the temporary environment that is named NAME. The temporary environment can be either the environment provided to a simple command, or the environment provided to a shell function. We only search the function environment if we are currently executing a shell function body (variable_context > 0). Return a consed variable, or NULL if not found. */ SHELL_VAR * find_tempenv_variable (name) char *name; { SHELL_VAR *var = (SHELL_VAR *)NULL; if (temporary_env) var = find_name_in_env_array (name, temporary_env); /* We don't check this_shell_builtin because the command that needs the value from builtin_env may be a disk command run inside a script run with `.' and a temporary env. */ if (!var && builtin_env) var = find_name_in_env_array (name, builtin_env); if (!var && variable_context && function_env) var = find_name_in_env_array (name, function_env); return (var); } /* Free the storage allocated to the string array pointed to by ARRAYP, and make that variable have a null pointer as a value. */ static void dispose_temporary_vars (arrayp) char ***arrayp; { if (!*arrayp) return; free_array (*arrayp); *arrayp = (char **)NULL; array_needs_making = 1; } /* Free the storage used in the variable array for temporary environment variables. */ void dispose_used_env_vars () { dispose_temporary_vars (&temporary_env); } /* Free the storage used for temporary environment variables given to commands when executing inside of a function body. */ void dispose_function_env () { dispose_temporary_vars (&function_env); } /* Free the storage used for temporary environment variables given to commands when executing a builtin command such as "source". */ void dispose_builtin_env () { dispose_temporary_vars (&builtin_env); } /* Stupid comparison routine for qsort () ing strings. */ int qsort_string_compare (s1, s2) register char **s1, **s2; { int result; if ((result = **s1 - **s2) == 0) result = strcmp (*s1, *s2); return (result); } /* 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 && 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 (function_env) for (i = 0; function_env[i]; i++) export_env = add_or_supercede (function_env[i], export_env); if (temporary_env) for (i = 0; temporary_env[i]; i++) export_env = add_or_supercede (temporary_env[i], export_env); /* If we changed the array, then sort it alphabetically. */ if (temporary_env || function_env) 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. */ /* 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) && !defined (HPOSF1) static char *last_tempenv_value = (char *)NULL; char * getenv (name) #if defined (Linux) _CONST_HACK char *name; #else char _CONST_HACK *name; #endif /* !Linux */ { 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 && !HPOSF1 */