diff options
Diffstat (limited to '')
-rw-r--r-- | variables.c | 6347 |
1 files changed, 6347 insertions, 0 deletions
diff --git a/variables.c b/variables.c new file mode 100644 index 0000000..610629a --- /dev/null +++ b/variables.c @@ -0,0 +1,6347 @@ +/* variables.c -- Functions for hacking shell variables. */ + +/* Copyright (C) 1987-2018 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 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include "bashtypes.h" +#include "posixstat.h" +#include "posixtime.h" + +#if defined (__QNX__) +# if defined (__QNXNTO__) +# include <sys/netmgr.h> +# else +# include <sys/vc.h> +# endif /* !__QNXNTO__ */ +#endif /* __QNX__ */ + +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif + +#include <stdio.h> +#include "chartypes.h" +#if defined (HAVE_PWD_H) +# include <pwd.h> +#endif +#include "bashansi.h" +#include "bashintl.h" + +#define NEED_XTRACE_SET_DECL + +#include "shell.h" +#include "parser.h" +#include "flags.h" +#include "execute_cmd.h" +#include "findcmd.h" +#include "mailcheck.h" +#include "input.h" +#include "hashcmd.h" +#include "pathexp.h" +#include "alias.h" +#include "jobs.h" + +#include "version.h" + +#include "builtins/getopt.h" +#include "builtins/common.h" +#include "builtins/builtext.h" + +#if defined (READLINE) +# include "bashline.h" +# include <readline/readline.h> +#else +# include <tilde/tilde.h> +#endif + +#if defined (HISTORY) +# include "bashhist.h" +# include <readline/history.h> +#endif /* HISTORY */ + +#if defined (PROGRAMMABLE_COMPLETION) +# include "pcomplete.h" +#endif + +#define VARIABLES_HASH_BUCKETS 1024 /* must be power of two */ +#define FUNCTIONS_HASH_BUCKETS 512 +#define TEMPENV_HASH_BUCKETS 4 /* must be power of two */ + +#define BASHFUNC_PREFIX "BASH_FUNC_" +#define BASHFUNC_PREFLEN 10 /* == strlen(BASHFUNC_PREFIX */ +#define BASHFUNC_SUFFIX "%%" +#define BASHFUNC_SUFFLEN 2 /* == strlen(BASHFUNC_SUFFIX) */ + +/* flags for find_variable_internal */ + +#define FV_FORCETEMPENV 0x01 +#define FV_SKIPINVISIBLE 0x02 + +extern char **environ; + +/* Variables used here and defined in other files. */ +extern time_t shell_start_time; + +/* The list of shell variables that the user has created at the global + scope, or that came from the environment. */ +VAR_CONTEXT *global_variables = (VAR_CONTEXT *)NULL; + +/* The current list of shell variables, including function scopes */ +VAR_CONTEXT *shell_variables = (VAR_CONTEXT *)NULL; + +/* The list of shell functions that the user has created, or that came from + the environment. */ +HASH_TABLE *shell_functions = (HASH_TABLE *)NULL; + +HASH_TABLE *invalid_env = (HASH_TABLE *)NULL; + +#if defined (DEBUGGER) +/* The table of shell function definitions that the user defined or that + came from the environment. */ +HASH_TABLE *shell_function_defs = (HASH_TABLE *)NULL; +#endif + +/* The current variable context. This is really a count of how deep into + executing functions we are. */ +int variable_context = 0; + +/* If non-zero, local variables inherit values and attributes from a variable + with the same name at a previous scope. */ +int localvar_inherit = 0; + +/* If non-zero, calling `unset' on local variables in previous scopes marks + them as invisible so lookups find them unset. This is the same behavior + as local variables in the current local scope. */ +int localvar_unset = 0; + +/* The set of shell assignments which are made only in the environment + for a single command. */ +HASH_TABLE *temporary_env = (HASH_TABLE *)NULL; + +/* Set to non-zero if an assignment error occurs while putting variables + into the temporary environment. */ +int tempenv_assign_error; + +/* 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 $$. */ +pid_t dollar_dollar_pid; + +/* Non-zero means that we have to remake EXPORT_ENV. */ +int array_needs_making = 1; + +/* The number of times BASH has been executed. This is set + by initialize_variables (). */ +int shell_level = 0; + +/* An array which is passed to commands as their environment. It is + manufactured from the union of the initial environment and the + shell variables that are marked for export. */ +char **export_env = (char **)NULL; +static int export_env_index; +static int export_env_size; + +#if defined (READLINE) +static int winsize_assignment; /* currently assigning to LINES or COLUMNS */ +#endif + +SHELL_VAR nameref_invalid_value; +static SHELL_VAR nameref_maxloop_value; + +static HASH_TABLE *last_table_searched; /* hash_lookup sets this */ +static VAR_CONTEXT *last_context_searched; + +/* Some forward declarations. */ +static void create_variable_tables __P((void)); + +static void set_machine_vars __P((void)); +static void set_home_var __P((void)); +static void set_shell_var __P((void)); +static char *get_bash_name __P((void)); +static void initialize_shell_level __P((void)); +static void uidset __P((void)); +#if defined (ARRAY_VARS) +static void make_vers_array __P((void)); +#endif + +static SHELL_VAR *null_assign __P((SHELL_VAR *, char *, arrayind_t, char *)); +#if defined (ARRAY_VARS) +static SHELL_VAR *null_array_assign __P((SHELL_VAR *, char *, arrayind_t, char *)); +#endif +static SHELL_VAR *get_self __P((SHELL_VAR *)); + +#if defined (ARRAY_VARS) +static SHELL_VAR *init_dynamic_array_var __P((char *, sh_var_value_func_t *, sh_var_assign_func_t *, int)); +static SHELL_VAR *init_dynamic_assoc_var __P((char *, sh_var_value_func_t *, sh_var_assign_func_t *, int)); +#endif + +static SHELL_VAR *assign_seconds __P((SHELL_VAR *, char *, arrayind_t, char *)); +static SHELL_VAR *get_seconds __P((SHELL_VAR *)); +static SHELL_VAR *init_seconds_var __P((void)); + +static int brand __P((void)); +static void sbrand __P((unsigned long)); /* set bash random number generator. */ +static void seedrand __P((void)); /* seed generator randomly */ +static SHELL_VAR *assign_random __P((SHELL_VAR *, char *, arrayind_t, char *)); +static SHELL_VAR *get_random __P((SHELL_VAR *)); + +static SHELL_VAR *assign_lineno __P((SHELL_VAR *, char *, arrayind_t, char *)); +static SHELL_VAR *get_lineno __P((SHELL_VAR *)); + +static SHELL_VAR *assign_subshell __P((SHELL_VAR *, char *, arrayind_t, char *)); +static SHELL_VAR *get_subshell __P((SHELL_VAR *)); + +static SHELL_VAR *get_epochseconds __P((SHELL_VAR *)); +static SHELL_VAR *get_epochrealtime __P((SHELL_VAR *)); + +static SHELL_VAR *get_bashpid __P((SHELL_VAR *)); + +#if defined (HISTORY) +static SHELL_VAR *get_histcmd __P((SHELL_VAR *)); +#endif + +#if defined (READLINE) +static SHELL_VAR *get_comp_wordbreaks __P((SHELL_VAR *)); +static SHELL_VAR *assign_comp_wordbreaks __P((SHELL_VAR *, char *, arrayind_t, char *)); +#endif + +#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS) +static SHELL_VAR *assign_dirstack __P((SHELL_VAR *, char *, arrayind_t, char *)); +static SHELL_VAR *get_dirstack __P((SHELL_VAR *)); +#endif + +#if defined (ARRAY_VARS) +static SHELL_VAR *get_groupset __P((SHELL_VAR *)); +# if defined (DEBUGGER) +static SHELL_VAR *get_bashargcv __P((SHELL_VAR *)); +# endif +static SHELL_VAR *build_hashcmd __P((SHELL_VAR *)); +static SHELL_VAR *get_hashcmd __P((SHELL_VAR *)); +static SHELL_VAR *assign_hashcmd __P((SHELL_VAR *, char *, arrayind_t, char *)); +# if defined (ALIAS) +static SHELL_VAR *build_aliasvar __P((SHELL_VAR *)); +static SHELL_VAR *get_aliasvar __P((SHELL_VAR *)); +static SHELL_VAR *assign_aliasvar __P((SHELL_VAR *, char *, arrayind_t, char *)); +# endif +#endif + +static SHELL_VAR *get_funcname __P((SHELL_VAR *)); +static SHELL_VAR *init_funcname_var __P((void)); + +static void initialize_dynamic_variables __P((void)); + +static SHELL_VAR *bind_invalid_envvar __P((const char *, char *, int)); + +static int var_sametype __P((SHELL_VAR *, SHELL_VAR *)); + +static SHELL_VAR *hash_lookup __P((const char *, HASH_TABLE *)); +static SHELL_VAR *new_shell_variable __P((const char *)); +static SHELL_VAR *make_new_variable __P((const char *, HASH_TABLE *)); +static SHELL_VAR *bind_variable_internal __P((const char *, char *, HASH_TABLE *, int, int)); + +static void dispose_variable_value __P((SHELL_VAR *)); +static void free_variable_hash_data __P((PTR_T)); + +static VARLIST *vlist_alloc __P((int)); +static VARLIST *vlist_realloc __P((VARLIST *, int)); +static void vlist_add __P((VARLIST *, SHELL_VAR *, int)); + +static void flatten __P((HASH_TABLE *, sh_var_map_func_t *, VARLIST *, int)); + +static int qsort_var_comp __P((SHELL_VAR **, SHELL_VAR **)); + +static SHELL_VAR **vapply __P((sh_var_map_func_t *)); +static SHELL_VAR **fapply __P((sh_var_map_func_t *)); + +static int visible_var __P((SHELL_VAR *)); +static int visible_and_exported __P((SHELL_VAR *)); +static int export_environment_candidate __P((SHELL_VAR *)); +static int local_and_exported __P((SHELL_VAR *)); +static int variable_in_context __P((SHELL_VAR *)); +#if defined (ARRAY_VARS) +static int visible_array_vars __P((SHELL_VAR *)); +#endif + +static SHELL_VAR *find_variable_internal __P((const char *, int)); + +static SHELL_VAR *find_nameref_at_context __P((SHELL_VAR *, VAR_CONTEXT *)); +static SHELL_VAR *find_variable_nameref_context __P((SHELL_VAR *, VAR_CONTEXT *, VAR_CONTEXT **)); +static SHELL_VAR *find_variable_last_nameref_context __P((SHELL_VAR *, VAR_CONTEXT *, VAR_CONTEXT **)); + +static SHELL_VAR *bind_tempenv_variable __P((const char *, char *)); +static void push_posix_temp_var __P((PTR_T)); +static void push_temp_var __P((PTR_T)); +static void propagate_temp_var __P((PTR_T)); +static void dispose_temporary_env __P((sh_free_func_t *)); + +static inline char *mk_env_string __P((const char *, const char *, int)); +static char **make_env_array_from_var_list __P((SHELL_VAR **)); +static char **make_var_export_array __P((VAR_CONTEXT *)); +static char **make_func_export_array __P((void)); +static void add_temp_array_to_env __P((char **, int, int)); + +static int n_shell_variables __P((void)); +static int set_context __P((SHELL_VAR *)); + +static void push_func_var __P((PTR_T)); +static void push_builtin_var __P((PTR_T)); +static void push_exported_var __P((PTR_T)); + +static inline void push_posix_tempvar_internal __P((SHELL_VAR *, int)); + +static inline int find_special_var __P((const char *)); + +static void +create_variable_tables () +{ + if (shell_variables == 0) + { + shell_variables = global_variables = new_var_context ((char *)NULL, 0); + shell_variables->scope = 0; + shell_variables->table = hash_create (VARIABLES_HASH_BUCKETS); + } + + if (shell_functions == 0) + shell_functions = hash_create (FUNCTIONS_HASH_BUCKETS); + +#if defined (DEBUGGER) + if (shell_function_defs == 0) + shell_function_defs = hash_create (FUNCTIONS_HASH_BUCKETS); +#endif +} + +/* Initialize the shell variables from the current environment. + If PRIVMODE is nonzero, don't import functions from ENV or + parse $SHELLOPTS. */ +void +initialize_shell_variables (env, privmode) + char **env; + int privmode; +{ + char *name, *string, *temp_string; + int c, char_index, string_index, string_length, ro; + SHELL_VAR *temp_var; + + create_variable_tables (); + + for (string_index = 0; env && (string = env[string_index++]); ) + { + char_index = 0; + name = string; + while ((c = *string++) && c != '=') + ; + if (string[-1] == '=') + char_index = string - name - 1; + + /* If there are weird things in the environment, like `=xxx' or a + string without an `=', just skip them. */ + if (char_index == 0) + continue; + + /* ASSERT(name[char_index] == '=') */ + name[char_index] = '\0'; + /* Now, name = env variable name, string = env variable value, and + char_index == strlen (name) */ + + temp_var = (SHELL_VAR *)NULL; + +#if defined (FUNCTION_IMPORT) + /* If exported function, define it now. Don't import functions from + the environment in privileged mode. */ + if (privmode == 0 && read_but_dont_execute == 0 && + STREQN (BASHFUNC_PREFIX, name, BASHFUNC_PREFLEN) && + STREQ (BASHFUNC_SUFFIX, name + char_index - BASHFUNC_SUFFLEN) && + STREQN ("() {", string, 4)) + { + size_t namelen; + char *tname; /* desired imported function name */ + + namelen = char_index - BASHFUNC_PREFLEN - BASHFUNC_SUFFLEN; + + tname = name + BASHFUNC_PREFLEN; /* start of func name */ + tname[namelen] = '\0'; /* now tname == func name */ + + string_length = strlen (string); + temp_string = (char *)xmalloc (namelen + string_length + 2); + + memcpy (temp_string, tname, namelen); + temp_string[namelen] = ' '; + memcpy (temp_string + namelen + 1, string, string_length + 1); + + /* Don't import function names that are invalid identifiers from the + environment in posix mode, though we still allow them to be defined as + shell variables. */ + if (absolute_program (tname) == 0 && (posixly_correct == 0 || legal_identifier (tname))) + parse_and_execute (temp_string, tname, SEVAL_NONINT|SEVAL_NOHIST|SEVAL_FUNCDEF|SEVAL_ONECMD); + else + free (temp_string); /* parse_and_execute does this */ + + if (temp_var = find_function (tname)) + { + VSETATTR (temp_var, (att_exported|att_imported)); + array_needs_making = 1; + } + else + { + if (temp_var = bind_invalid_envvar (name, string, 0)) + { + VSETATTR (temp_var, (att_exported | att_imported | att_invisible)); + array_needs_making = 1; + } + last_command_exit_value = 1; + report_error (_("error importing function definition for `%s'"), tname); + } + + /* Restore original suffix */ + tname[namelen] = BASHFUNC_SUFFIX[0]; + } + else +#endif /* FUNCTION_IMPORT */ +#if defined (ARRAY_VARS) +# if ARRAY_EXPORT + /* Array variables may not yet be exported. */ + if (*string == '(' && string[1] == '[' && string[strlen (string) - 1] == ')') + { + string_length = 1; + temp_string = extract_array_assignment_list (string, &string_length); + temp_var = assign_array_from_string (name, temp_string, 0); + FREE (temp_string); + VSETATTR (temp_var, (att_exported | att_imported)); + array_needs_making = 1; + } + else +# endif /* ARRAY_EXPORT */ +#endif + { + ro = 0; + /* If we processed a command-line option that caused SHELLOPTS to be + set, it may already be set (and read-only) by the time we process + the shell's environment. */ + if (/* posixly_correct &&*/ STREQ (name, "SHELLOPTS")) + { + temp_var = find_variable ("SHELLOPTS"); + ro = temp_var && readonly_p (temp_var); + if (temp_var) + VUNSETATTR (temp_var, att_readonly); + } + if (legal_identifier (name)) + { + temp_var = bind_variable (name, string, 0); + if (temp_var) + { + VSETATTR (temp_var, (att_exported | att_imported)); + if (ro) + VSETATTR (temp_var, att_readonly); + } + } + else + { + temp_var = bind_invalid_envvar (name, string, 0); + if (temp_var) + VSETATTR (temp_var, (att_exported | att_imported | att_invisible)); + } + if (temp_var) + array_needs_making = 1; + } + + name[char_index] = '='; + /* temp_var can be NULL if it was an exported function with a syntax + error (a different bug, but it still shouldn't dump core). */ + if (temp_var && function_p (temp_var) == 0) /* XXX not yet */ + { + CACHE_IMPORTSTR (temp_var, name); + } + } + + set_pwd (); + + /* Set up initial value of $_ */ + temp_var = set_if_not ("_", dollar_vars[0]); + + /* Remember this pid. */ + dollar_dollar_pid = 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); + temp_var = set_if_not ("TERM", "dumb"); + +#if defined (__QNX__) + /* set node id -- don't import it from the environment */ + { + char node_name[22]; +# if defined (__QNXNTO__) + netmgr_ndtostr(ND2S_LOCAL_STR, ND_LOCAL_NODE, node_name, sizeof(node_name)); +# else + qnx_nidtostr (getnid (), node_name, sizeof (node_name)); +# endif + temp_var = bind_variable ("NODE", node_name, 0); + if (temp_var) + set_auto_export (temp_var); + } +#endif + + /* set up the prompts. */ + if (interactive_shell) + { +#if defined (PROMPT_STRING_DECODE) + set_if_not ("PS1", primary_prompt); +#else + if (current_user.uid == -1) + get_current_user_info (); + set_if_not ("PS1", current_user.euid == 0 ? "# " : primary_prompt); +#endif + set_if_not ("PS2", secondary_prompt); + } + + if (current_user.euid == 0) + bind_variable ("PS4", "+ ", 0); + else + set_if_not ("PS4", "+ "); + + /* Don't allow IFS to be imported from the environment. */ + temp_var = bind_variable ("IFS", " \t\n", 0); + setifs (temp_var); + + /* Magic machine types. Pretty convenient. */ + set_machine_vars (); + + /* Default MAILCHECK for interactive shells. Defer the creation of a + default MAILPATH until the startup files are read, because MAIL + names a mail file if MAILPATH is not set, and we should provide a + default only if neither is set. */ + if (interactive_shell) + { + temp_var = set_if_not ("MAILCHECK", posixly_correct ? "600" : "60"); + VSETATTR (temp_var, att_integer); + } + + /* Do some things with shell level. */ + initialize_shell_level (); + + set_ppid (); + + /* Initialize the `getopts' stuff. */ + temp_var = bind_variable ("OPTIND", "1", 0); + VSETATTR (temp_var, att_integer); + getopts_reset (0); + bind_variable ("OPTERR", "1", 0); + sh_opterr = 1; + + if (login_shell == 1 && posixly_correct == 0) + set_home_var (); + + /* Get the full pathname to THIS shell, and set the BASH variable + to it. */ + name = get_bash_name (); + temp_var = bind_variable ("BASH", name, 0); + free (name); + + /* 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. */ + set_shell_var (); + + /* Make a variable called BASH_VERSION which contains the version info. */ + bind_variable ("BASH_VERSION", shell_version_string (), 0); +#if defined (ARRAY_VARS) + make_vers_array (); +#endif + + if (command_execution_string) + bind_variable ("BASH_EXECUTION_STRING", command_execution_string, 0); + + /* Find out if we're supposed to be in Posix.2 mode via an + environment variable. */ + temp_var = find_variable ("POSIXLY_CORRECT"); + if (!temp_var) + temp_var = find_variable ("POSIX_PEDANTIC"); + if (temp_var && imported_p (temp_var)) + sv_strict_posix (temp_var->name); + +#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) + { + name = bash_tilde_expand (posixly_correct ? "~/.sh_history" : "~/.bash_history", 0); + + set_if_not ("HISTFILE", name); + free (name); + } +#endif /* HISTORY */ + + /* Seed the random number generator. */ + seedrand (); + + /* Handle some "special" variables that we may have inherited from a + parent shell. */ + if (interactive_shell) + { + temp_var = find_variable ("IGNOREEOF"); + if (!temp_var) + temp_var = find_variable ("ignoreeof"); + if (temp_var && imported_p (temp_var)) + sv_ignoreeof (temp_var->name); + } + +#if defined (HISTORY) + if (interactive_shell && remember_on_history) + { + sv_history_control ("HISTCONTROL"); + sv_histignore ("HISTIGNORE"); + sv_histtimefmt ("HISTTIMEFORMAT"); + } +#endif /* HISTORY */ + +#if defined (READLINE) && defined (STRICT_POSIX) + /* POSIXLY_CORRECT will be 1 here if the shell was compiled + -DSTRICT_POSIX or if POSIXLY_CORRECT was supplied in the shell's + environment */ + if (interactive_shell && posixly_correct && no_line_editing == 0) + rl_prefer_env_winsize = 1; +#endif /* READLINE && STRICT_POSIX */ + + /* + * 24 October 2001 + * + * I'm tired of the arguing and bug reports. Bash now leaves SSH_CLIENT + * and SSH2_CLIENT alone. I'm going to rely on the shell_level check in + * isnetconn() to avoid running the startup files more often than wanted. + * That will, of course, only work if the user's login shell is bash, so + * I've made that behavior conditional on SSH_SOURCE_BASHRC being defined + * in config-top.h. + */ +#if 0 + temp_var = find_variable ("SSH_CLIENT"); + if (temp_var && imported_p (temp_var)) + { + VUNSETATTR (temp_var, att_exported); + array_needs_making = 1; + } + temp_var = find_variable ("SSH2_CLIENT"); + if (temp_var && imported_p (temp_var)) + { + VUNSETATTR (temp_var, att_exported); + array_needs_making = 1; + } +#endif + + /* Get the user's real and effective user ids. */ + uidset (); + + temp_var = find_variable ("BASH_XTRACEFD"); + if (temp_var && imported_p (temp_var)) + sv_xtracefd (temp_var->name); + + sv_shcompat ("BASH_COMPAT"); + + /* Allow FUNCNEST to be inherited from the environment. */ + sv_funcnest ("FUNCNEST"); + + /* Initialize the dynamic variables, and seed their values. */ + initialize_dynamic_variables (); +} + +/* **************************************************************** */ +/* */ +/* Setting values for special shell variables */ +/* */ +/* **************************************************************** */ + +static void +set_machine_vars () +{ + SHELL_VAR *temp_var; + + temp_var = set_if_not ("HOSTTYPE", HOSTTYPE); + temp_var = set_if_not ("OSTYPE", OSTYPE); + temp_var = set_if_not ("MACHTYPE", MACHTYPE); + + temp_var = set_if_not ("HOSTNAME", current_host_name); +} + +/* Set $HOME to the information in the password file if we didn't get + it from the environment. */ + +/* This function is not static so the tilde and readline libraries can + use it. */ +char * +sh_get_home_dir () +{ + if (current_user.home_dir == 0) + get_current_user_info (); + return current_user.home_dir; +} + +static void +set_home_var () +{ + SHELL_VAR *temp_var; + + temp_var = find_variable ("HOME"); + if (temp_var == 0) + temp_var = bind_variable ("HOME", sh_get_home_dir (), 0); +#if 0 + VSETATTR (temp_var, att_exported); +#endif +} + +/* Set $SHELL to the user's login shell if it is not already set. Call + get_current_user_info if we haven't already fetched the shell. */ +static void +set_shell_var () +{ + SHELL_VAR *temp_var; + + temp_var = find_variable ("SHELL"); + if (temp_var == 0) + { + if (current_user.shell == 0) + get_current_user_info (); + temp_var = bind_variable ("SHELL", current_user.shell, 0); + } +#if 0 + VSETATTR (temp_var, att_exported); +#endif +} + +static char * +get_bash_name () +{ + char *name; + + if ((login_shell == 1) && RELPATH(shell_name)) + { + if (current_user.shell == 0) + get_current_user_info (); + name = savestring (current_user.shell); + } + else if (ABSPATH(shell_name)) + name = savestring (shell_name); + else if (shell_name[0] == '.' && shell_name[1] == '/') + { + /* Fast path for common case. */ + char *cdir; + int len; + + cdir = get_string_value ("PWD"); + if (cdir) + { + len = strlen (cdir); + name = (char *)xmalloc (len + strlen (shell_name) + 1); + strcpy (name, cdir); + strcpy (name + len, shell_name + 1); + } + else + name = savestring (shell_name); + } + else + { + char *tname; + int s; + + tname = find_user_command (shell_name); + + if (tname == 0) + { + /* Try the current directory. If there is not an executable + there, just punt and use the login shell. */ + s = file_status (shell_name); + if (s & FS_EXECABLE) + { + tname = make_absolute (shell_name, get_string_value ("PWD")); + if (*shell_name == '.') + { + name = sh_canonpath (tname, PATH_CHECKDOTDOT|PATH_CHECKEXISTS); + if (name == 0) + name = tname; + else + free (tname); + } + else + name = tname; + } + else + { + if (current_user.shell == 0) + get_current_user_info (); + name = savestring (current_user.shell); + } + } + else + { + name = full_pathname (tname); + free (tname); + } + } + + return (name); +} + +void +adjust_shell_level (change) + int change; +{ + char new_level[5], *old_SHLVL; + intmax_t old_level; + SHELL_VAR *temp_var; + + old_SHLVL = get_string_value ("SHLVL"); + if (old_SHLVL == 0 || *old_SHLVL == '\0' || legal_number (old_SHLVL, &old_level) == 0) + old_level = 0; + + shell_level = old_level + change; + if (shell_level < 0) + shell_level = 0; + else if (shell_level >= 1000) + { + internal_warning (_("shell level (%d) too high, resetting to 1"), shell_level); + shell_level = 1; + } + + /* We don't need the full generality of itos here. */ + if (shell_level < 10) + { + new_level[0] = shell_level + '0'; + new_level[1] = '\0'; + } + else if (shell_level < 100) + { + new_level[0] = (shell_level / 10) + '0'; + new_level[1] = (shell_level % 10) + '0'; + new_level[2] = '\0'; + } + else if (shell_level < 1000) + { + new_level[0] = (shell_level / 100) + '0'; + old_level = shell_level % 100; + new_level[1] = (old_level / 10) + '0'; + new_level[2] = (old_level % 10) + '0'; + new_level[3] = '\0'; + } + + temp_var = bind_variable ("SHLVL", new_level, 0); + set_auto_export (temp_var); +} + +static void +initialize_shell_level () +{ + adjust_shell_level (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 getcwd () to fail on shell startup, + and in that case, PWD would be undefined. If this is an interactive + login shell, see if $HOME is the current working directory, and if + that's not the same string as $PWD, set PWD=$HOME. */ + +void +set_pwd () +{ + SHELL_VAR *temp_var, *home_var; + char *temp_string, *home_string, *current_dir; + + home_var = find_variable ("HOME"); + home_string = home_var ? value_cell (home_var) : (char *)NULL; + + temp_var = find_variable ("PWD"); + /* Follow posix rules for importing PWD */ + if (temp_var && imported_p (temp_var) && + (temp_string = value_cell (temp_var)) && + temp_string[0] == '/' && + same_file (temp_string, ".", (struct stat *)NULL, (struct stat *)NULL)) + { + current_dir = sh_canonpath (temp_string, PATH_CHECKDOTDOT|PATH_CHECKEXISTS); + if (current_dir == 0) + current_dir = get_working_directory ("shell_init"); + else + set_working_directory (current_dir); + if (posixly_correct && current_dir) + { + temp_var = bind_variable ("PWD", current_dir, 0); + set_auto_export (temp_var); + } + free (current_dir); + } + else if (home_string && interactive_shell && login_shell && + same_file (home_string, ".", (struct stat *)NULL, (struct stat *)NULL)) + { + set_working_directory (home_string); + temp_var = bind_variable ("PWD", home_string, 0); + set_auto_export (temp_var); + } + else + { + temp_string = get_working_directory ("shell-init"); + if (temp_string) + { + temp_var = bind_variable ("PWD", temp_string, 0); + set_auto_export (temp_var); + free (temp_string); + } + } + + /* According to the Single Unix Specification, v2, $OLDPWD is an + `environment variable' and therefore should be auto-exported. If we + don't find OLDPWD in the environment, or it doesn't name a directory, + make a dummy invisible variable for OLDPWD, and mark it as exported. */ + temp_var = find_variable ("OLDPWD"); +#if defined (OLDPWD_CHECK_DIRECTORY) + if (temp_var == 0 || value_cell (temp_var) == 0 || file_isdir (value_cell (temp_var)) == 0) +#else + if (temp_var == 0 || value_cell (temp_var) == 0) +#endif + { + temp_var = bind_variable ("OLDPWD", (char *)NULL, 0); + VSETATTR (temp_var, (att_exported | att_invisible)); + } +} + +/* Make a variable $PPID, which holds the pid of the shell's parent. */ +void +set_ppid () +{ + char namebuf[INT_STRLEN_BOUND(pid_t) + 1], *name; + SHELL_VAR *temp_var; + + name = inttostr (getppid (), namebuf, sizeof(namebuf)); + temp_var = find_variable ("PPID"); + if (temp_var) + VUNSETATTR (temp_var, (att_readonly | att_exported)); + temp_var = bind_variable ("PPID", name, 0); + VSETATTR (temp_var, (att_readonly | att_integer)); +} + +static void +uidset () +{ + char buff[INT_STRLEN_BOUND(uid_t) + 1], *b; + register SHELL_VAR *v; + + b = inttostr (current_user.uid, buff, sizeof (buff)); + v = find_variable ("UID"); + if (v == 0) + { + v = bind_variable ("UID", b, 0); + VSETATTR (v, (att_readonly | att_integer)); + } + + if (current_user.euid != current_user.uid) + b = inttostr (current_user.euid, buff, sizeof (buff)); + + v = find_variable ("EUID"); + if (v == 0) + { + v = bind_variable ("EUID", b, 0); + VSETATTR (v, (att_readonly | att_integer)); + } +} + +#if defined (ARRAY_VARS) +static void +make_vers_array () +{ + SHELL_VAR *vv; + ARRAY *av; + char *s, d[32], b[INT_STRLEN_BOUND(int) + 1]; + + unbind_variable_noref ("BASH_VERSINFO"); + + vv = make_new_array_variable ("BASH_VERSINFO"); + av = array_cell (vv); + strcpy (d, dist_version); + s = strchr (d, '.'); + if (s) + *s++ = '\0'; + array_insert (av, 0, d); + array_insert (av, 1, s); + s = inttostr (patch_level, b, sizeof (b)); + array_insert (av, 2, s); + s = inttostr (build_version, b, sizeof (b)); + array_insert (av, 3, s); + array_insert (av, 4, release_status); + array_insert (av, 5, MACHTYPE); + + VSETATTR (vv, att_readonly); +} +#endif /* ARRAY_VARS */ + +/* Set the environment variables $LINES and $COLUMNS in response to + a window size change. */ +void +sh_set_lines_and_columns (lines, cols) + int lines, cols; +{ + char val[INT_STRLEN_BOUND(int) + 1], *v; + +#if defined (READLINE) + /* If we are currently assigning to LINES or COLUMNS, don't do anything. */ + if (winsize_assignment) + return; +#endif + + v = inttostr (lines, val, sizeof (val)); + bind_variable ("LINES", v, 0); + + v = inttostr (cols, val, sizeof (val)); + bind_variable ("COLUMNS", v, 0); +} + +/* **************************************************************** */ +/* */ +/* Printing variables and values */ +/* */ +/* **************************************************************** */ + +/* Print LIST (a list of shell variables) to stdout in such a way that + they can be read back in. */ +void +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) == 0) + print_assignment (var); +} + +/* Print LIST (a list of shell functions) to stdout in such a way that + they can be read back in. */ +void +print_func_list (list) + register SHELL_VAR **list; +{ + register int i; + register SHELL_VAR *var; + + for (i = 0; list && (var = list[i]); i++) + { + printf ("%s ", var->name); + print_var_function (var); + printf ("\n"); + } +} + +/* 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. */ +void +print_assignment (var) + SHELL_VAR *var; +{ + if (var_isset (var) == 0) + return; + + if (function_p (var)) + { + printf ("%s", var->name); + print_var_function (var); + printf ("\n"); + } +#if defined (ARRAY_VARS) + else if (array_p (var)) + print_array_assignment (var, 0); + else if (assoc_p (var)) + print_assoc_assignment (var, 0); +#endif /* ARRAY_VARS */ + else + { + printf ("%s=", var->name); + print_var_value (var, 1); + printf ("\n"); + } +} + +/* Print the value cell of VAR, a shell variable. Do not print + the name, nor leading/trailing newline. If QUOTE is non-zero, + and the value contains shell metacharacters, quote the value + in such a way that it can be read back in. */ +void +print_var_value (var, quote) + SHELL_VAR *var; + int quote; +{ + char *t; + + if (var_isset (var) == 0) + return; + + if (quote && posixly_correct == 0 && ansic_shouldquote (value_cell (var))) + { + t = ansic_quote (value_cell (var), 0, (int *)0); + printf ("%s", t); + free (t); + } + else if (quote && sh_contains_shell_metas (value_cell (var))) + { + t = sh_single_quote (value_cell (var)); + printf ("%s", t); + free (t); + } + else + printf ("%s", value_cell (var)); +} + +/* Print the function cell of VAR, a shell variable. Do not + print the name, nor leading/trailing newline. */ +void +print_var_function (var) + SHELL_VAR *var; +{ + char *x; + + if (function_p (var) && var_isset (var)) + { + x = named_function_string ((char *)NULL, function_cell(var), FUNC_MULTILINE|FUNC_EXTERNAL); + printf ("%s", x); + } +} + +/* **************************************************************** */ +/* */ +/* Dynamic Variables */ +/* */ +/* **************************************************************** */ + +/* 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, if arrays are compiled into the shell, some of the functions in + arrayfunc.c, and dynamic_value, which is called from find_variable. + + assign_func is called from bind_variable_internal, if + bind_variable_internal discovers that the variable being assigned to + has such a function. The function is called as + SHELL_VAR *temp = (*(entry->assign_func)) (entry, value, ind) + and the (SHELL_VAR *)temp is returned as the value of bind_variable. It + is usually ENTRY (self). IND is an index for an array variable, and + unused otherwise. + + dynamic_value is called from find_variable_internal 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_internal, in the same style as + stupidly_hack_special_variables, but I wanted the changes as + localized as possible. */ + +#define INIT_DYNAMIC_VAR(var, val, gfunc, afunc) \ + do \ + { \ + v = bind_variable (var, (val), 0); \ + v->dynamic_value = gfunc; \ + v->assign_func = afunc; \ + } \ + while (0) + +#define INIT_DYNAMIC_ARRAY_VAR(var, gfunc, afunc) \ + do \ + { \ + v = make_new_array_variable (var); \ + v->dynamic_value = gfunc; \ + v->assign_func = afunc; \ + } \ + while (0) + +#define INIT_DYNAMIC_ASSOC_VAR(var, gfunc, afunc) \ + do \ + { \ + v = make_new_assoc_variable (var); \ + v->dynamic_value = gfunc; \ + v->assign_func = afunc; \ + } \ + while (0) + +static SHELL_VAR * +null_assign (self, value, unused, key) + SHELL_VAR *self; + char *value; + arrayind_t unused; + char *key; +{ + return (self); +} + +#if defined (ARRAY_VARS) +static SHELL_VAR * +null_array_assign (self, value, ind, key) + SHELL_VAR *self; + char *value; + arrayind_t ind; + char *key; +{ + return (self); +} +#endif + +/* Degenerate `dynamic_value' function; just returns what's passed without + manipulation. */ +static SHELL_VAR * +get_self (self) + SHELL_VAR *self; +{ + return (self); +} + +#if defined (ARRAY_VARS) +/* A generic dynamic array variable initializer. Initialize array variable + NAME with dynamic value function GETFUNC and assignment function SETFUNC. */ +static SHELL_VAR * +init_dynamic_array_var (name, getfunc, setfunc, attrs) + char *name; + sh_var_value_func_t *getfunc; + sh_var_assign_func_t *setfunc; + int attrs; +{ + SHELL_VAR *v; + + v = find_variable (name); + if (v) + return (v); + INIT_DYNAMIC_ARRAY_VAR (name, getfunc, setfunc); + if (attrs) + VSETATTR (v, attrs); + return v; +} + +static SHELL_VAR * +init_dynamic_assoc_var (name, getfunc, setfunc, attrs) + char *name; + sh_var_value_func_t *getfunc; + sh_var_assign_func_t *setfunc; + int attrs; +{ + SHELL_VAR *v; + + v = find_variable (name); + if (v) + return (v); + INIT_DYNAMIC_ASSOC_VAR (name, getfunc, setfunc); + if (attrs) + VSETATTR (v, attrs); + return v; +} +#endif + +/* 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 intmax_t seconds_value_assigned; + +static SHELL_VAR * +assign_seconds (self, value, unused, key) + SHELL_VAR *self; + char *value; + arrayind_t unused; + char *key; +{ + if (legal_number (value, &seconds_value_assigned) == 0) + seconds_value_assigned = 0; + shell_start_time = NOW; + return (self); +} + +static SHELL_VAR * +get_seconds (var) + SHELL_VAR *var; +{ + time_t time_since_start; + char *p; + + time_since_start = NOW - shell_start_time; + p = itos(seconds_value_assigned + time_since_start); + + FREE (value_cell (var)); + + VSETATTR (var, att_integer); + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +init_seconds_var () +{ + SHELL_VAR *v; + + v = find_variable ("SECONDS"); + if (v) + { + if (legal_number (value_cell(v), &seconds_value_assigned) == 0) + seconds_value_assigned = 0; + } + INIT_DYNAMIC_VAR ("SECONDS", (v ? value_cell (v) : (char *)NULL), get_seconds, assign_seconds); + return v; +} + +/* The random number seed. You can change this by setting RANDOM. */ +static u_bits32_t rseed = 1; +static int last_random_value; +static int seeded_subshell = 0; + +#define BASH_RANDOM_16 1 + +#if BASH_RANDOM_16 +# define BASH_RAND_MAX 32767 /* 0x7fff - 16 bits */ +#else +# define BASH_RAND_MAX 0x7fffffff /* 32 bits */ +#endif + +/* Returns a pseudo-random number between 0 and 32767. */ +static int +brand () +{ + /* Minimal Standard generator from + "Random number generators: good ones are hard to find", + Park and Miller, Communications of the ACM, vol. 31, no. 10, + October 1988, p. 1195. filtered through FreeBSD. + + x(n+1) = 16807 * x(n) mod (2**31 - 1). + + We split up the calculations to avoid overflow. + + h = rseed / q; l = x - h * q; t = a * l - h * r + m = 2147483647, a = 16807, q = 127773, r = 2836 + + There are lots of other combinations of constants to use; look at + https://www.gnu.org/software/gsl/manual/html_node/Other-random-number-generators.html#Other-random-number-generators */ + + bits32_t h, l, t; + + /* Can't seed with 0. */ + if (rseed == 0) + rseed = 123459876; + h = rseed / 127773; + l = rseed - (127773 * h); + t = 16807 * l - 2836 * h; + rseed = (t < 0) ? t + 0x7fffffff : t; + + return ((unsigned int)(rseed & BASH_RAND_MAX)); /* was % BASH_RAND_MAX+1 */ +} + +/* Set the random number generator seed to SEED. */ +static void +sbrand (seed) + unsigned long seed; +{ + rseed = seed; + last_random_value = 0; +} + +static void +seedrand () +{ + struct timeval tv; + SHELL_VAR *v; + + gettimeofday (&tv, NULL); +#if 0 + v = find_variable ("BASH_VERSION"); + sbrand (tv.tv_sec ^ tv.tv_usec ^ getpid () ^ ((u_bits32_t)&v & 0x7fffffff)); +#else + sbrand (tv.tv_sec ^ tv.tv_usec ^ getpid ()); +#endif +} + +static SHELL_VAR * +assign_random (self, value, unused, key) + SHELL_VAR *self; + char *value; + arrayind_t unused; + char *key; +{ + sbrand (strtoul (value, (char **)NULL, 10)); + if (subshell_environment) + seeded_subshell = getpid (); + return (self); +} + +int +get_random_number () +{ + int rv, pid; + + /* Reset for command and process substitution. */ + pid = getpid (); + if (subshell_environment && seeded_subshell != pid) + { + seedrand (); + seeded_subshell = pid; + } + + do + rv = brand (); + while (rv == last_random_value); + return rv; +} + +static SHELL_VAR * +get_random (var) + SHELL_VAR *var; +{ + int rv; + char *p; + + rv = get_random_number (); + last_random_value = rv; + p = itos (rv); + + FREE (value_cell (var)); + + VSETATTR (var, att_integer); + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +assign_lineno (var, value, unused, key) + SHELL_VAR *var; + char *value; + arrayind_t unused; + char *key; +{ + intmax_t new_value; + + if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0) + new_value = 0; + line_number = line_number_base = new_value; + return var; +} + +/* Function which returns the current line number. */ +static SHELL_VAR * +get_lineno (var) + SHELL_VAR *var; +{ + char *p; + int ln; + + ln = executing_line_number (); + p = itos (ln); + FREE (value_cell (var)); + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +assign_subshell (var, value, unused, key) + SHELL_VAR *var; + char *value; + arrayind_t unused; + char *key; +{ + intmax_t new_value; + + if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0) + new_value = 0; + subshell_level = new_value; + return var; +} + +static SHELL_VAR * +get_subshell (var) + SHELL_VAR *var; +{ + char *p; + + p = itos (subshell_level); + FREE (value_cell (var)); + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +get_epochseconds (var) + SHELL_VAR *var; +{ + intmax_t now; + char *p; + + now = NOW; + p = itos (now); + + FREE (value_cell (var)); + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +get_epochrealtime (var) + SHELL_VAR *var; +{ + char buf[32]; + char *p; + struct timeval tv; + + gettimeofday (&tv, NULL); + snprintf (buf, sizeof (buf), "%u%c%06u", (unsigned)tv.tv_sec, + locale_decpoint (), + (unsigned)tv.tv_usec); + + p = savestring (buf); + FREE (value_cell (var)); + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +get_bashpid (var) + SHELL_VAR *var; +{ + int pid; + char *p; + + pid = getpid (); + p = itos (pid); + + FREE (value_cell (var)); + VSETATTR (var, att_integer); /* XXX - was also att_readonly */ + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +get_bash_argv0 (var) + SHELL_VAR *var; +{ + char *p; + + p = savestring (dollar_vars[0]); + FREE (value_cell (var)); + var_setvalue (var, p); + return var; +} + +static char *static_shell_name = 0; + +static SHELL_VAR * +assign_bash_argv0 (var, value, unused, key) + SHELL_VAR *var; + char *value; + arrayind_t unused; + char *key; +{ + size_t vlen; + + if (value == 0) + return var; + + FREE (dollar_vars[0]); + dollar_vars[0] = savestring (value); + + /* Need these gyrations because shell_name isn't dynamically allocated */ + vlen = STRLEN (value); + static_shell_name = xrealloc (static_shell_name, vlen + 1); + strcpy (static_shell_name, value); + + shell_name = static_shell_name; + return var; +} + +static SHELL_VAR * +get_bash_command (var) + SHELL_VAR *var; +{ + char *p; + + if (the_printed_command_except_trap) + p = savestring (the_printed_command_except_trap); + else + { + p = (char *)xmalloc (1); + p[0] = '\0'; + } + FREE (value_cell (var)); + var_setvalue (var, p); + return (var); +} + +#if defined (HISTORY) +static SHELL_VAR * +get_histcmd (var) + SHELL_VAR *var; +{ + char *p; + + p = itos (history_number ()); + FREE (value_cell (var)); + var_setvalue (var, p); + return (var); +} +#endif + +#if defined (READLINE) +/* When this function returns, VAR->value points to malloced memory. */ +static SHELL_VAR * +get_comp_wordbreaks (var) + SHELL_VAR *var; +{ + /* If we don't have anything yet, assign a default value. */ + if (rl_completer_word_break_characters == 0 && bash_readline_initialized == 0) + enable_hostname_completion (perform_hostname_completion); + + FREE (value_cell (var)); + var_setvalue (var, savestring (rl_completer_word_break_characters)); + + return (var); +} + +/* When this function returns, rl_completer_word_break_characters points to + malloced memory. */ +static SHELL_VAR * +assign_comp_wordbreaks (self, value, unused, key) + SHELL_VAR *self; + char *value; + arrayind_t unused; + char *key; +{ + if (rl_completer_word_break_characters && + rl_completer_word_break_characters != rl_basic_word_break_characters) + free (rl_completer_word_break_characters); + + rl_completer_word_break_characters = savestring (value); + return self; +} +#endif /* READLINE */ + +#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS) +static SHELL_VAR * +assign_dirstack (self, value, ind, key) + SHELL_VAR *self; + char *value; + arrayind_t ind; + char *key; +{ + set_dirstack_element (ind, 1, value); + return self; +} + +static SHELL_VAR * +get_dirstack (self) + SHELL_VAR *self; +{ + ARRAY *a; + WORD_LIST *l; + + l = get_directory_stack (0); + a = array_from_word_list (l); + array_dispose (array_cell (self)); + dispose_words (l); + var_setarray (self, a); + return self; +} +#endif /* PUSHD AND POPD && ARRAY_VARS */ + +#if defined (ARRAY_VARS) +/* We don't want to initialize the group set with a call to getgroups() + unless we're asked to, but we only want to do it once. */ +static SHELL_VAR * +get_groupset (self) + SHELL_VAR *self; +{ + register int i; + int ng; + ARRAY *a; + static char **group_set = (char **)NULL; + + if (group_set == 0) + { + group_set = get_group_list (&ng); + a = array_cell (self); + for (i = 0; i < ng; i++) + array_insert (a, i, group_set[i]); + } + return (self); +} + +# if defined (DEBUGGER) +static SHELL_VAR * +get_bashargcv (self) + SHELL_VAR *self; +{ + static int self_semaphore = 0; + + /* Backwards compatibility: if we refer to BASH_ARGV or BASH_ARGC at the + top level without enabling debug mode, and we don't have an instance + of the variable set, initialize the arg arrays. + This will already have been done if debugging_mode != 0. */ + if (self_semaphore == 0 && variable_context == 0 && debugging_mode == 0) /* don't do it for shell functions */ + { + self_semaphore = 1; + init_bash_argv (); + self_semaphore = 0; + } + return self; +} +# endif + +static SHELL_VAR * +build_hashcmd (self) + SHELL_VAR *self; +{ + HASH_TABLE *h; + int i; + char *k, *v; + BUCKET_CONTENTS *item; + + h = assoc_cell (self); + if (h) + assoc_dispose (h); + + if (hashed_filenames == 0 || HASH_ENTRIES (hashed_filenames) == 0) + { + var_setvalue (self, (char *)NULL); + return self; + } + + h = assoc_create (hashed_filenames->nbuckets); + for (i = 0; i < hashed_filenames->nbuckets; i++) + { + for (item = hash_items (i, hashed_filenames); item; item = item->next) + { + k = savestring (item->key); + v = pathdata(item)->path; + assoc_insert (h, k, v); + } + } + + var_setvalue (self, (char *)h); + return self; +} + +static SHELL_VAR * +get_hashcmd (self) + SHELL_VAR *self; +{ + build_hashcmd (self); + return (self); +} + +static SHELL_VAR * +assign_hashcmd (self, value, ind, key) + SHELL_VAR *self; + char *value; + arrayind_t ind; + char *key; +{ +#if defined (RESTRICTED_SHELL) + char *full_path; + + if (restricted) + { + if (strchr (value, '/')) + { + sh_restricted (value); + return (SHELL_VAR *)NULL; + } + /* If we are changing the hash table in a restricted shell, make sure the + target pathname can be found using a $PATH search. */ + full_path = find_user_command (value); + if (full_path == 0 || *full_path == 0 || executable_file (full_path) == 0) + { + sh_notfound (value); + free (full_path); + return ((SHELL_VAR *)NULL); + } + free (full_path); + } +#endif + phash_insert (key, value, 0, 0); + return (build_hashcmd (self)); +} + +#if defined (ALIAS) +static SHELL_VAR * +build_aliasvar (self) + SHELL_VAR *self; +{ + HASH_TABLE *h; + int i; + char *k, *v; + BUCKET_CONTENTS *item; + + h = assoc_cell (self); + if (h) + assoc_dispose (h); + + if (aliases == 0 || HASH_ENTRIES (aliases) == 0) + { + var_setvalue (self, (char *)NULL); + return self; + } + + h = assoc_create (aliases->nbuckets); + for (i = 0; i < aliases->nbuckets; i++) + { + for (item = hash_items (i, aliases); item; item = item->next) + { + k = savestring (item->key); + v = ((alias_t *)(item->data))->value; + assoc_insert (h, k, v); + } + } + + var_setvalue (self, (char *)h); + return self; +} + +static SHELL_VAR * +get_aliasvar (self) + SHELL_VAR *self; +{ + build_aliasvar (self); + return (self); +} + +static SHELL_VAR * +assign_aliasvar (self, value, ind, key) + SHELL_VAR *self; + char *value; + arrayind_t ind; + char *key; +{ + if (legal_alias_name (key, 0) == 0) + { + report_error (_("`%s': invalid alias name"), key); + return (self); + } + add_alias (key, value); + return (build_aliasvar (self)); +} +#endif /* ALIAS */ + +#endif /* ARRAY_VARS */ + +/* If ARRAY_VARS is not defined, this just returns the name of any + currently-executing function. If we have arrays, it's a call stack. */ +static SHELL_VAR * +get_funcname (self) + SHELL_VAR *self; +{ +#if ! defined (ARRAY_VARS) + char *t; + if (variable_context && this_shell_function) + { + FREE (value_cell (self)); + t = savestring (this_shell_function->name); + var_setvalue (self, t); + } +#endif + return (self); +} + +void +make_funcname_visible (on_or_off) + int on_or_off; +{ + SHELL_VAR *v; + + v = find_variable ("FUNCNAME"); + if (v == 0 || v->dynamic_value == 0) + return; + + if (on_or_off) + VUNSETATTR (v, att_invisible); + else + VSETATTR (v, att_invisible); +} + +static SHELL_VAR * +init_funcname_var () +{ + SHELL_VAR *v; + + v = find_variable ("FUNCNAME"); + if (v) + return v; +#if defined (ARRAY_VARS) + INIT_DYNAMIC_ARRAY_VAR ("FUNCNAME", get_funcname, null_array_assign); +#else + INIT_DYNAMIC_VAR ("FUNCNAME", (char *)NULL, get_funcname, null_assign); +#endif + VSETATTR (v, att_invisible|att_noassign); + return v; +} + +static void +initialize_dynamic_variables () +{ + SHELL_VAR *v; + + v = init_seconds_var (); + + INIT_DYNAMIC_VAR ("BASH_ARGV0", (char *)NULL, get_bash_argv0, assign_bash_argv0); + + INIT_DYNAMIC_VAR ("BASH_COMMAND", (char *)NULL, get_bash_command, (sh_var_assign_func_t *)NULL); + INIT_DYNAMIC_VAR ("BASH_SUBSHELL", (char *)NULL, get_subshell, assign_subshell); + + INIT_DYNAMIC_VAR ("RANDOM", (char *)NULL, get_random, assign_random); + VSETATTR (v, att_integer); + INIT_DYNAMIC_VAR ("LINENO", (char *)NULL, get_lineno, assign_lineno); + VSETATTR (v, att_integer|att_regenerate); + + INIT_DYNAMIC_VAR ("BASHPID", (char *)NULL, get_bashpid, null_assign); + VSETATTR (v, att_integer); + + INIT_DYNAMIC_VAR ("EPOCHSECONDS", (char *)NULL, get_epochseconds, null_assign); + VSETATTR (v, att_regenerate); + INIT_DYNAMIC_VAR ("EPOCHREALTIME", (char *)NULL, get_epochrealtime, null_assign); + VSETATTR (v, att_regenerate); + +#if defined (HISTORY) + INIT_DYNAMIC_VAR ("HISTCMD", (char *)NULL, get_histcmd, (sh_var_assign_func_t *)NULL); + VSETATTR (v, att_integer); +#endif + +#if defined (READLINE) + INIT_DYNAMIC_VAR ("COMP_WORDBREAKS", (char *)NULL, get_comp_wordbreaks, assign_comp_wordbreaks); +#endif + +#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS) + v = init_dynamic_array_var ("DIRSTACK", get_dirstack, assign_dirstack, 0); +#endif /* PUSHD_AND_POPD && ARRAY_VARS */ + +#if defined (ARRAY_VARS) + v = init_dynamic_array_var ("GROUPS", get_groupset, null_array_assign, att_noassign); + +# if defined (DEBUGGER) + v = init_dynamic_array_var ("BASH_ARGC", get_bashargcv, null_array_assign, att_noassign|att_nounset); + v = init_dynamic_array_var ("BASH_ARGV", get_bashargcv, null_array_assign, att_noassign|att_nounset); +# endif /* DEBUGGER */ + v = init_dynamic_array_var ("BASH_SOURCE", get_self, null_array_assign, att_noassign|att_nounset); + v = init_dynamic_array_var ("BASH_LINENO", get_self, null_array_assign, att_noassign|att_nounset); + + v = init_dynamic_assoc_var ("BASH_CMDS", get_hashcmd, assign_hashcmd, att_nofree); +# if defined (ALIAS) + v = init_dynamic_assoc_var ("BASH_ALIASES", get_aliasvar, assign_aliasvar, att_nofree); +# endif +#endif + + v = init_funcname_var (); +} + +/* **************************************************************** */ +/* */ +/* Retrieving variables and values */ +/* */ +/* **************************************************************** */ + +/* 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). */ + +static SHELL_VAR * +hash_lookup (name, hashed_vars) + const char *name; + HASH_TABLE *hashed_vars; +{ + BUCKET_CONTENTS *bucket; + + bucket = hash_search (name, hashed_vars, 0); + /* If we find the name in HASHED_VARS, set LAST_TABLE_SEARCHED to that + table. */ + if (bucket) + last_table_searched = hashed_vars; + return (bucket ? (SHELL_VAR *)bucket->data : (SHELL_VAR *)NULL); +} + +SHELL_VAR * +var_lookup (name, vcontext) + const char *name; + VAR_CONTEXT *vcontext; +{ + VAR_CONTEXT *vc; + SHELL_VAR *v; + + v = (SHELL_VAR *)NULL; + for (vc = vcontext; vc; vc = vc->down) + if (v = hash_lookup (name, vc->table)) + break; + + return v; +} + +/* Look up the variable entry named NAME. If SEARCH_TEMPENV is non-zero, + then also search the temporarily built list of exported variables. + The lookup order is: + temporary_env + shell_variables list +*/ + +SHELL_VAR * +find_variable_internal (name, flags) + const char *name; + int flags; +{ + SHELL_VAR *var; + int search_tempenv, force_tempenv; + VAR_CONTEXT *vc; + + var = (SHELL_VAR *)NULL; + + force_tempenv = (flags & FV_FORCETEMPENV); + + /* 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". */ + search_tempenv = force_tempenv || (expanding_redir == 0 && subshell_environment); + + if (search_tempenv && temporary_env) + var = hash_lookup (name, temporary_env); + + if (var == 0) + { + if ((flags & FV_SKIPINVISIBLE) == 0) + var = var_lookup (name, shell_variables); + else + { + /* essentially var_lookup expanded inline so we can check for + att_invisible */ + for (vc = shell_variables; vc; vc = vc->down) + { + var = hash_lookup (name, vc->table); + if (var && invisible_p (var)) + var = 0; + if (var) + break; + } + } + } + + if (var == 0) + return ((SHELL_VAR *)NULL); + + return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var); +} + +/* Look up and resolve the chain of nameref variables starting at V all the + way to NULL or non-nameref. */ +SHELL_VAR * +find_variable_nameref (v) + SHELL_VAR *v; +{ + int level, flags; + char *newname; + SHELL_VAR *orig, *oldv; + + level = 0; + orig = v; + while (v && nameref_p (v)) + { + level++; + if (level > NAMEREF_MAX) + return ((SHELL_VAR *)0); /* error message here? */ + newname = nameref_cell (v); + if (newname == 0 || *newname == '\0') + return ((SHELL_VAR *)0); + oldv = v; + flags = 0; + if (expanding_redir == 0 && (assigning_in_environment || executing_builtin)) + flags |= FV_FORCETEMPENV; + /* We don't handle array subscripts here. */ + v = find_variable_internal (newname, flags); + if (v == orig || v == oldv) + { + internal_warning (_("%s: circular name reference"), orig->name); +#if 1 + /* XXX - provisional change - circular refs go to + global scope for resolution, without namerefs. */ + if (variable_context && v->context) + return (find_global_variable_noref (v->name)); + else +#endif + return ((SHELL_VAR *)0); + } + } + return v; +} + +/* Resolve the chain of nameref variables for NAME. XXX - could change later */ +SHELL_VAR * +find_variable_last_nameref (name, vflags) + const char *name; + int vflags; +{ + SHELL_VAR *v, *nv; + char *newname; + int level, flags; + + nv = v = find_variable_noref (name); + level = 0; + while (v && nameref_p (v)) + { + level++; + if (level > NAMEREF_MAX) + return ((SHELL_VAR *)0); /* error message here? */ + newname = nameref_cell (v); + if (newname == 0 || *newname == '\0') + return ((vflags && invisible_p (v)) ? v : (SHELL_VAR *)0); + nv = v; + flags = 0; + if (expanding_redir == 0 && (assigning_in_environment || executing_builtin)) + flags |= FV_FORCETEMPENV; + /* We don't accommodate array subscripts here. */ + v = find_variable_internal (newname, flags); + } + return nv; +} + +/* Resolve the chain of nameref variables for NAME. XXX - could change later */ +SHELL_VAR * +find_global_variable_last_nameref (name, vflags) + const char *name; + int vflags; +{ + SHELL_VAR *v, *nv; + char *newname; + int level; + + nv = v = find_global_variable_noref (name); + level = 0; + while (v && nameref_p (v)) + { + level++; + if (level > NAMEREF_MAX) + return ((SHELL_VAR *)0); /* error message here? */ + newname = nameref_cell (v); + if (newname == 0 || *newname == '\0') + return ((vflags && invisible_p (v)) ? v : (SHELL_VAR *)0); + nv = v; + /* We don't accommodate array subscripts here. */ + v = find_global_variable_noref (newname); + } + return nv; +} + +static SHELL_VAR * +find_nameref_at_context (v, vc) + SHELL_VAR *v; + VAR_CONTEXT *vc; +{ + SHELL_VAR *nv, *nv2; + char *newname; + int level; + + nv = v; + level = 1; + while (nv && nameref_p (nv)) + { + level++; + if (level > NAMEREF_MAX) + return (&nameref_maxloop_value); + newname = nameref_cell (nv); + if (newname == 0 || *newname == '\0') + return ((SHELL_VAR *)NULL); + nv2 = hash_lookup (newname, vc->table); + if (nv2 == 0) + break; + nv = nv2; + } + return nv; +} + +/* Do nameref resolution from the VC, which is the local context for some + function or builtin, `up' the chain to the global variables context. If + NVCP is not NULL, return the variable context where we finally ended the + nameref resolution (so the bind_variable_internal can use the correct + variable context and hash table). */ +static SHELL_VAR * +find_variable_nameref_context (v, vc, nvcp) + SHELL_VAR *v; + VAR_CONTEXT *vc; + VAR_CONTEXT **nvcp; +{ + SHELL_VAR *nv, *nv2; + VAR_CONTEXT *nvc; + + /* Look starting at the current context all the way `up' */ + for (nv = v, nvc = vc; nvc; nvc = nvc->down) + { + nv2 = find_nameref_at_context (nv, nvc); + if (nv2 == &nameref_maxloop_value) + return (nv2); /* XXX */ + if (nv2 == 0) + continue; + nv = nv2; + if (*nvcp) + *nvcp = nvc; + if (nameref_p (nv) == 0) + break; + } + return (nameref_p (nv) ? (SHELL_VAR *)NULL : nv); +} + +/* Do nameref resolution from the VC, which is the local context for some + function or builtin, `up' the chain to the global variables context. If + NVCP is not NULL, return the variable context where we finally ended the + nameref resolution (so the bind_variable_internal can use the correct + variable context and hash table). */ +static SHELL_VAR * +find_variable_last_nameref_context (v, vc, nvcp) + SHELL_VAR *v; + VAR_CONTEXT *vc; + VAR_CONTEXT **nvcp; +{ + SHELL_VAR *nv, *nv2; + VAR_CONTEXT *nvc; + + /* Look starting at the current context all the way `up' */ + for (nv = v, nvc = vc; nvc; nvc = nvc->down) + { + nv2 = find_nameref_at_context (nv, nvc); + if (nv2 == &nameref_maxloop_value) + return (nv2); /* XXX */ + if (nv2 == 0) + continue; + nv = nv2; + if (*nvcp) + *nvcp = nvc; + } + return (nameref_p (nv) ? nv : (SHELL_VAR *)NULL); +} + +SHELL_VAR * +find_variable_nameref_for_create (name, flags) + const char *name; + int flags; +{ + SHELL_VAR *var; + + /* See if we have a nameref pointing to a variable that hasn't been + created yet. */ + var = find_variable_last_nameref (name, 1); + if ((flags&1) && var && nameref_p (var) && invisible_p (var)) + { + internal_warning (_("%s: removing nameref attribute"), name); + VUNSETATTR (var, att_nameref); + } + if (var && nameref_p (var)) + { + if (legal_identifier (nameref_cell (var)) == 0) + { + sh_invalidid (nameref_cell (var) ? nameref_cell (var) : ""); + return ((SHELL_VAR *)INVALID_NAMEREF_VALUE); + } + } + return (var); +} + +SHELL_VAR * +find_variable_nameref_for_assignment (name, flags) + const char *name; + int flags; +{ + SHELL_VAR *var; + + /* See if we have a nameref pointing to a variable that hasn't been + created yet. */ + var = find_variable_last_nameref (name, 1); + if (var && nameref_p (var) && invisible_p (var)) /* XXX - flags */ + { + internal_warning (_("%s: removing nameref attribute"), name); + VUNSETATTR (var, att_nameref); + } + if (var && nameref_p (var)) + { + if (valid_nameref_value (nameref_cell (var), 1) == 0) + { + sh_invalidid (nameref_cell (var) ? nameref_cell (var) : ""); + return ((SHELL_VAR *)INVALID_NAMEREF_VALUE); + } + } + return (var); +} + +/* If find_variable (name) returns NULL, check that it's not a nameref + referencing a variable that doesn't exist. If it is, return the new + name. If not, return the original name. Kind of like the previous + function, but dealing strictly with names. This takes assignment flags + so it can deal with the various assignment modes used by `declare'. */ +char * +nameref_transform_name (name, flags) + char *name; + int flags; +{ + SHELL_VAR *v; + char *newname; + + v = 0; + if (flags & ASS_MKLOCAL) + { + v = find_variable_last_nameref (name, 1); + /* If we're making local variables, only follow namerefs that point to + non-existant variables at the same variable context. */ + if (v && v->context != variable_context) + v = 0; + } + else if (flags & ASS_MKGLOBAL) + v = (flags & ASS_CHKLOCAL) ? find_variable_last_nameref (name, 1) + : find_global_variable_last_nameref (name, 1); + if (v && nameref_p (v) && valid_nameref_value (nameref_cell (v), 1)) + return nameref_cell (v); + return name; +} + +/* Find a variable, forcing a search of the temporary environment first */ +SHELL_VAR * +find_variable_tempenv (name) + const char *name; +{ + SHELL_VAR *var; + + var = find_variable_internal (name, FV_FORCETEMPENV); + if (var && nameref_p (var)) + var = find_variable_nameref (var); + return (var); +} + +/* Find a variable, not forcing a search of the temporary environment first */ +SHELL_VAR * +find_variable_notempenv (name) + const char *name; +{ + SHELL_VAR *var; + + var = find_variable_internal (name, 0); + if (var && nameref_p (var)) + var = find_variable_nameref (var); + return (var); +} + +SHELL_VAR * +find_global_variable (name) + const char *name; +{ + SHELL_VAR *var; + + var = var_lookup (name, global_variables); + if (var && nameref_p (var)) + var = find_variable_nameref (var); + + if (var == 0) + return ((SHELL_VAR *)NULL); + + return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var); +} + +SHELL_VAR * +find_global_variable_noref (name) + const char *name; +{ + SHELL_VAR *var; + + var = var_lookup (name, global_variables); + + if (var == 0) + return ((SHELL_VAR *)NULL); + + return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var); +} + +SHELL_VAR * +find_shell_variable (name) + const char *name; +{ + SHELL_VAR *var; + + var = var_lookup (name, shell_variables); + if (var && nameref_p (var)) + var = find_variable_nameref (var); + + if (var == 0) + return ((SHELL_VAR *)NULL); + + return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var); +} + +/* Look up the variable entry named NAME. Returns the entry or NULL. */ +SHELL_VAR * +find_variable (name) + const char *name; +{ + SHELL_VAR *v; + int flags; + + last_table_searched = 0; + flags = 0; + if (expanding_redir == 0 && (assigning_in_environment || executing_builtin)) + flags |= FV_FORCETEMPENV; + v = find_variable_internal (name, flags); + if (v && nameref_p (v)) + v = find_variable_nameref (v); + return v; +} + +/* Find the first instance of NAME in the variable context chain; return first + one found without att_invisible set; return 0 if no non-invisible instances + found. */ +SHELL_VAR * +find_variable_no_invisible (name) + const char *name; +{ + SHELL_VAR *v; + int flags; + + last_table_searched = 0; + flags = FV_SKIPINVISIBLE; + if (expanding_redir == 0 && (assigning_in_environment || executing_builtin)) + flags |= FV_FORCETEMPENV; + v = find_variable_internal (name, flags); + if (v && nameref_p (v)) + v = find_variable_nameref (v); + return v; +} + +/* Find the first instance of NAME in the variable context chain; return first + one found even if att_invisible set. */ +SHELL_VAR * +find_variable_for_assignment (name) + const char *name; +{ + SHELL_VAR *v; + int flags; + + last_table_searched = 0; + flags = 0; + if (expanding_redir == 0 && (assigning_in_environment || executing_builtin)) + flags |= FV_FORCETEMPENV; + v = find_variable_internal (name, flags); + if (v && nameref_p (v)) + v = find_variable_nameref (v); + return v; +} + +SHELL_VAR * +find_variable_noref (name) + const char *name; +{ + SHELL_VAR *v; + int flags; + + flags = 0; + if (expanding_redir == 0 && (assigning_in_environment || executing_builtin)) + flags |= FV_FORCETEMPENV; + v = find_variable_internal (name, flags); + return v; +} + +/* Look up the function entry whose name matches STRING. + Returns the entry or NULL. */ +SHELL_VAR * +find_function (name) + const char *name; +{ + return (hash_lookup (name, shell_functions)); +} + +/* Find the function definition for the shell function named NAME. Returns + the entry or NULL. */ +FUNCTION_DEF * +find_function_def (name) + const char *name; +{ +#if defined (DEBUGGER) + return ((FUNCTION_DEF *)hash_lookup (name, shell_function_defs)); +#else + return ((FUNCTION_DEF *)0); +#endif +} + +/* Return the value of VAR. VAR is assumed to have been the result of a + lookup without any subscript, if arrays are compiled into the shell. */ +char * +get_variable_value (var) + SHELL_VAR *var; +{ + if (var == 0) + return ((char *)NULL); +#if defined (ARRAY_VARS) + else if (array_p (var)) + return (array_reference (array_cell (var), 0)); + else if (assoc_p (var)) + return (assoc_reference (assoc_cell (var), "0")); +#endif + else + return (value_cell (var)); +} + +/* Return the string value of a variable. Return NULL if the variable + doesn't exist. Don't cons a new string. This is a potential memory + leak if the variable is found in the temporary environment, but doesn't + leak in practice. Since functions and variables have separate name + spaces, returns NULL if var_name is a shell function only. */ +char * +get_string_value (var_name) + const char *var_name; +{ + SHELL_VAR *var; + + var = find_variable (var_name); + return ((var) ? get_variable_value (var) : (char *)NULL); +} + +/* This is present for use by the tilde and readline libraries. */ +char * +sh_get_env_value (v) + const char *v; +{ + return get_string_value (v); +} + +/* **************************************************************** */ +/* */ +/* Creating and setting variables */ +/* */ +/* **************************************************************** */ + +static int +var_sametype (v1, v2) + SHELL_VAR *v1; + SHELL_VAR *v2; +{ + if (v1 == 0 || v2 == 0) + return 0; +#if defined (ARRAY_VARS) + else if (assoc_p (v1) && assoc_p (v2)) + return 1; + else if (array_p (v1) && array_p (v2)) + return 1; + else if (array_p (v1) || array_p (v2)) + return 0; + else if (assoc_p (v1) || assoc_p (v2)) + return 0; +#endif + else + return 1; +} + +int +validate_inherited_value (var, type) + SHELL_VAR *var; + int type; +{ +#if defined (ARRAY_VARS) + if (type == att_array && assoc_p (var)) + return 0; + else if (type == att_assoc && array_p (var)) + return 0; + else +#endif + return 1; /* should we run convert_var_to_array here or let the caller? */ +} + +/* Set NAME to VALUE if NAME has no value. */ +SHELL_VAR * +set_if_not (name, value) + char *name, *value; +{ + SHELL_VAR *v; + + if (shell_variables == 0) + create_variable_tables (); + + v = find_variable (name); + if (v == 0) + v = bind_variable_internal (name, value, global_variables->table, HASH_NOSRCH, 0); + return (v); +} + +/* Create a local variable referenced by NAME. */ +SHELL_VAR * +make_local_variable (name, flags) + const char *name; + int flags; +{ + SHELL_VAR *new_var, *old_var, *old_ref; + VAR_CONTEXT *vc; + int was_tmpvar; + char *old_value; + + /* We don't want to follow the nameref chain when making local variables; we + just want to create them. */ + old_ref = find_variable_noref (name); + if (old_ref && nameref_p (old_ref) == 0) + old_ref = 0; + /* local foo; local foo; is a no-op. */ + old_var = find_variable (name); + if (old_ref == 0 && old_var && local_p (old_var) && old_var->context == variable_context) + return (old_var); + + /* local -n foo; local -n foo; is a no-op. */ + if (old_ref && local_p (old_ref) && old_ref->context == variable_context) + return (old_ref); + + /* From here on, we want to use the refvar, not the variable it references */ + if (old_ref) + old_var = old_ref; + + was_tmpvar = old_var && tempvar_p (old_var); + /* If we're making a local variable in a shell function, the temporary env + has already been merged into the function's variable context stack. We + can assume that a temporary var in the same context appears in the same + VAR_CONTEXT and can safely be returned without creating a new variable + (which results in duplicate names in the same VAR_CONTEXT->table */ + /* We can't just test tmpvar_p because variables in the temporary env given + to a shell function appear in the function's local variable VAR_CONTEXT + but retain their tempvar attribute. We want temporary variables that are + found in temporary_env, hence the test for last_table_searched, which is + set in hash_lookup and only (so far) checked here. */ + if (was_tmpvar && old_var->context == variable_context && last_table_searched != temporary_env) + { + VUNSETATTR (old_var, att_invisible); /* XXX */ +#if 0 /* TAG:bash-5.1 */ + /* We still want to flag this variable as local, though, and set things + up so that it gets treated as a local variable. */ + new_var = old_var; + /* Since we found the variable in a temporary environment, this will + succeed. */ + for (vc = shell_variables; vc; vc = vc->down) + if (vc_isfuncenv (vc) && vc->scope == variable_context) + break; + goto set_local_var_flags; +#endif + return (old_var); + } + + /* If we want to change to "inherit the old variable's value" semantics, + here is where to save the old value. */ + old_value = was_tmpvar ? value_cell (old_var) : (char *)NULL; + + for (vc = shell_variables; vc; vc = vc->down) + if (vc_isfuncenv (vc) && vc->scope == variable_context) + break; + + if (vc == 0) + { + internal_error (_("make_local_variable: no function context at current scope")); + return ((SHELL_VAR *)NULL); + } + else if (vc->table == 0) + vc->table = hash_create (TEMPENV_HASH_BUCKETS); + + /* Since this is called only from the local/declare/typeset code, we can + call builtin_error here without worry (of course, it will also work + for anything that sets this_command_name). Variables with the `noassign' + attribute may not be made local. The test against old_var's context + level is to disallow local copies of readonly global variables (since I + believe that this could be a security hole). Readonly copies of calling + function local variables are OK. */ + if (old_var && (noassign_p (old_var) || + (readonly_p (old_var) && old_var->context == 0))) + { + if (readonly_p (old_var)) + sh_readonly (name); + else if (noassign_p (old_var)) + builtin_error (_("%s: variable may not be assigned value"), name); +#if 0 + /* Let noassign variables through with a warning */ + if (readonly_p (old_var)) +#endif + return ((SHELL_VAR *)NULL); + } + + if (old_var == 0) + new_var = make_new_variable (name, vc->table); + else + { + new_var = make_new_variable (name, vc->table); + + /* If we found this variable in one of the temporary environments, + inherit its value. Watch to see if this causes problems with + things like `x=4 local x'. XXX - see above for temporary env + variables with the same context level as variable_context */ + /* XXX - we should only do this if the variable is not an array. */ + /* If we want to change the local variable semantics to "inherit + the old variable's value" here is where to set it. And we would + need to use copy_variable (currently unused) to do it for all + possible variable values. */ + if (was_tmpvar) + var_setvalue (new_var, savestring (old_value)); + else if (localvar_inherit || (flags & MKLOC_INHERIT)) + { + /* This may not make sense for nameref variables that are shadowing + variables with the same name, but we don't know that yet. */ +#if defined (ARRAY_VARS) + if (assoc_p (old_var)) + var_setassoc (new_var, assoc_copy (assoc_cell (old_var))); + else if (array_p (old_var)) + var_setarray (new_var, array_copy (array_cell (old_var))); + else if (value_cell (old_var)) +#else + if (value_cell (old_var)) +#endif + var_setvalue (new_var, savestring (value_cell (old_var))); + else + var_setvalue (new_var, (char *)NULL); + } + + if (localvar_inherit || (flags & MKLOC_INHERIT)) + { + /* It doesn't make sense to inherit the nameref attribute */ + new_var->attributes = old_var->attributes & ~att_nameref; + new_var->dynamic_value = old_var->dynamic_value; + new_var->assign_func = old_var->assign_func; + } + else + /* We inherit the export attribute, but no others. */ + new_var->attributes = exported_p (old_var) ? att_exported : 0; + } + +set_local_var_flags: + vc->flags |= VC_HASLOCAL; + + new_var->context = variable_context; + VSETATTR (new_var, att_local); + + if (ifsname (name)) + setifs (new_var); + + /* value_cell will be 0 if localvar_inherit == 0 or there was no old variable + with the same name or the old variable was invisible */ + if (was_tmpvar == 0 && no_invisible_vars == 0 && value_cell (new_var) == 0) + VSETATTR (new_var, att_invisible); /* XXX */ + return (new_var); +} + +/* Create a new shell variable with name NAME. */ +static SHELL_VAR * +new_shell_variable (name) + const char *name; +{ + SHELL_VAR *entry; + + entry = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR)); + + entry->name = savestring (name); + var_setvalue (entry, (char *)NULL); + CLEAR_EXPORTSTR (entry); + + entry->dynamic_value = (sh_var_value_func_t *)NULL; + entry->assign_func = (sh_var_assign_func_t *)NULL; + + entry->attributes = 0; + + /* Always assume variables are to be made at toplevel! + make_local_variable has the responsibility of changing the + variable context. */ + entry->context = 0; + + return (entry); +} + +/* Create a new shell variable with name NAME and add it to the hash table + TABLE. */ +static SHELL_VAR * +make_new_variable (name, table) + const char *name; + HASH_TABLE *table; +{ + SHELL_VAR *entry; + BUCKET_CONTENTS *elt; + + entry = new_shell_variable (name); + + /* Make sure we have a shell_variables hash table to add to. */ + if (shell_variables == 0) + create_variable_tables (); + + elt = hash_insert (savestring (name), table, HASH_NOSRCH); + elt->data = (PTR_T)entry; + + return entry; +} + +#if defined (ARRAY_VARS) +SHELL_VAR * +make_new_array_variable (name) + char *name; +{ + SHELL_VAR *entry; + ARRAY *array; + + entry = make_new_variable (name, global_variables->table); + array = array_create (); + + var_setarray (entry, array); + VSETATTR (entry, att_array); + return entry; +} + +SHELL_VAR * +make_local_array_variable (name, assoc_ok) + char *name; + int assoc_ok; +{ + SHELL_VAR *var; + ARRAY *array; + + var = make_local_variable (name, 0); /* XXX for now */ + /* If ASSOC_OK is non-zero, assume that we are ok with letting an assoc + variable return to the caller without converting it. The caller will + either flag an error or do the conversion itself. */ + if (var == 0 || array_p (var) || (assoc_ok && assoc_p (var))) + return var; + + /* Validate any value we inherited from a variable instance at a previous + scope and disard anything that's invalid. */ + if (localvar_inherit && assoc_p (var)) + { + internal_warning ("%s: cannot inherit value from incompatible type", name); + VUNSETATTR (var, att_assoc); + dispose_variable_value (var); + array = array_create (); + var_setarray (var, array); + } + else if (localvar_inherit) + var = convert_var_to_array (var); /* XXX */ + else + { + dispose_variable_value (var); + array = array_create (); + var_setarray (var, array); + } + + VSETATTR (var, att_array); + return var; +} + +SHELL_VAR * +make_new_assoc_variable (name) + char *name; +{ + SHELL_VAR *entry; + HASH_TABLE *hash; + + entry = make_new_variable (name, global_variables->table); + hash = assoc_create (0); + + var_setassoc (entry, hash); + VSETATTR (entry, att_assoc); + return entry; +} + +SHELL_VAR * +make_local_assoc_variable (name, array_ok) + char *name; + int array_ok; +{ + SHELL_VAR *var; + HASH_TABLE *hash; + + var = make_local_variable (name, 0); /* XXX for now */ + /* If ARRAY_OK is non-zero, assume that we are ok with letting an array + variable return to the caller without converting it. The caller will + either flag an error or do the conversion itself. */ + if (var == 0 || assoc_p (var) || (array_ok && array_p (var))) + return var; + + /* Validate any value we inherited from a variable instance at a previous + scope and disard anything that's invalid. */ + if (localvar_inherit && array_p (var)) + { + internal_warning ("%s: cannot inherit value from incompatible type", name); + VUNSETATTR (var, att_array); + dispose_variable_value (var); + hash = assoc_create (0); + var_setassoc (var, hash); + } + else if (localvar_inherit) + var = convert_var_to_assoc (var); /* XXX */ + else + { + dispose_variable_value (var); + hash = assoc_create (0); + var_setassoc (var, hash); + } + + VSETATTR (var, att_assoc); + return var; +} +#endif + +char * +make_variable_value (var, value, flags) + SHELL_VAR *var; + char *value; + int flags; +{ + char *retval, *oval; + intmax_t lval, rval; + int expok, olen, op; + + /* 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. Callers that find it inconvenient to do this can set + the ASS_NOEVAL flag. For the special case of arithmetic expression + evaluation, the caller can set ASS_NOLONGJMP to avoid jumping out to + top_level. */ + if ((flags & ASS_NOEVAL) == 0 && integer_p (var)) + { + if (flags & ASS_APPEND) + { + oval = value_cell (var); + lval = evalexp (oval, 0, &expok); /* ksh93 seems to do this */ + if (expok == 0) + { + if (flags & ASS_NOLONGJMP) + goto make_value; + else + { + top_level_cleanup (); + jump_to_top_level (DISCARD); + } + } + } + rval = evalexp (value, 0, &expok); + if (expok == 0) + { + if (flags & ASS_NOLONGJMP) + goto make_value; + else + { + top_level_cleanup (); + jump_to_top_level (DISCARD); + } + } + /* This can be fooled if the variable's value changes while evaluating + `rval'. We can change it if we move the evaluation of lval to here. */ + if (flags & ASS_APPEND) + rval += lval; + retval = itos (rval); + } +#if defined (CASEMOD_ATTRS) + else if ((flags & ASS_NOEVAL) == 0 && (capcase_p (var) || uppercase_p (var) || lowercase_p (var))) + { + if (flags & ASS_APPEND) + { + oval = get_variable_value (var); + if (oval == 0) /* paranoia */ + oval = ""; + olen = STRLEN (oval); + retval = (char *)xmalloc (olen + (value ? STRLEN (value) : 0) + 1); + strcpy (retval, oval); + if (value) + strcpy (retval+olen, value); + } + else if (*value) + retval = savestring (value); + else + { + retval = (char *)xmalloc (1); + retval[0] = '\0'; + } + op = capcase_p (var) ? CASE_CAPITALIZE + : (uppercase_p (var) ? CASE_UPPER : CASE_LOWER); + oval = sh_modcase (retval, (char *)0, op); + free (retval); + retval = oval; + } +#endif /* CASEMOD_ATTRS */ + else if (value) + { +make_value: + if (flags & ASS_APPEND) + { + oval = get_variable_value (var); + if (oval == 0) /* paranoia */ + oval = ""; + olen = STRLEN (oval); + retval = (char *)xmalloc (olen + (value ? STRLEN (value) : 0) + 1); + strcpy (retval, oval); + if (value) + strcpy (retval+olen, value); + } + else if (*value) + retval = savestring (value); + else + { + retval = (char *)xmalloc (1); + retval[0] = '\0'; + } + } + else + retval = (char *)NULL; + + return retval; +} + +/* If we can optimize appending to string variables, say so */ +static int +can_optimize_assignment (entry, value, aflags) + SHELL_VAR *entry; + char *value; + int aflags; +{ + if ((aflags & ASS_APPEND) == 0) + return 0; +#if defined (ARRAY_VARS) + if (array_p (entry) || assoc_p (entry)) + return 0; +#endif + if (integer_p (entry) || uppercase_p (entry) || lowercase_p (entry) || capcase_p (entry)) + return 0; + if (readonly_p (entry) || noassign_p (entry)) + return 0; + return 1; +} + +/* right now we optimize appends to string variables */ +static SHELL_VAR * +optimized_assignment (entry, value, aflags) + SHELL_VAR *entry; + char *value; + int aflags; +{ + size_t len, vlen; + char *v, *new; + + v = value_cell (entry); + len = STRLEN (v); + vlen = STRLEN (value); + + new = (char *)xrealloc (v, len + vlen + 8); /* for now */ + if (vlen == 1) + { + new[len] = *value; + new[len+1] = '\0'; + } + else + strcpy (new + len, value); + var_setvalue (entry, new); + return entry; +} + +/* Bind a variable NAME to VALUE in the HASH_TABLE TABLE, which may be the + temporary environment (but usually is not). HFLAGS controls how NAME + is looked up in TABLE; AFLAGS controls how VALUE is assigned */ +static SHELL_VAR * +bind_variable_internal (name, value, table, hflags, aflags) + const char *name; + char *value; + HASH_TABLE *table; + int hflags, aflags; +{ + char *newval, *tname; + SHELL_VAR *entry, *tentry; + + entry = (hflags & HASH_NOSRCH) ? (SHELL_VAR *)NULL : hash_lookup (name, table); + /* Follow the nameref chain here if this is the global variables table */ + if (entry && nameref_p (entry) && (invisible_p (entry) == 0) && table == global_variables->table) + { + entry = find_global_variable (entry->name); + /* Let's see if we have a nameref referencing a variable that hasn't yet + been created. */ + if (entry == 0) + entry = find_variable_last_nameref (name, 0); /* XXX */ + if (entry == 0) /* just in case */ + return (entry); + } + + /* The first clause handles `declare -n ref; ref=x;' or `declare -n ref; + declare -n ref' */ + if (entry && invisible_p (entry) && nameref_p (entry)) + { + if ((aflags & ASS_FORCE) == 0 && value && valid_nameref_value (value, 0) == 0) + { + sh_invalidid (value); + return ((SHELL_VAR *)NULL); + } + goto assign_value; + } + else if (entry && nameref_p (entry)) + { + newval = nameref_cell (entry); /* XXX - newval can't be NULL here */ + if (valid_nameref_value (newval, 0) == 0) + { + sh_invalidid (newval); + return ((SHELL_VAR *)NULL); + } +#if defined (ARRAY_VARS) + /* declare -n foo=x[2] ; foo=bar */ + if (valid_array_reference (newval, 0)) + { + tname = array_variable_name (newval, 0, (char **)0, (int *)0); + if (tname && (tentry = find_variable_noref (tname)) && nameref_p (tentry)) + { + /* nameref variables can't be arrays */ + internal_warning (_("%s: removing nameref attribute"), name_cell (tentry)); + FREE (value_cell (tentry)); /* XXX - bash-4.3 compat */ + var_setvalue (tentry, (char *)NULL); + VUNSETATTR (tentry, att_nameref); + } + free (tname); + /* XXX - should it be aflags? */ + entry = assign_array_element (newval, make_variable_value (entry, value, aflags), aflags|ASS_NAMEREF); + if (entry == 0) + return entry; + } + else +#endif + { + entry = make_new_variable (newval, table); + var_setvalue (entry, make_variable_value (entry, value, aflags)); + } + } + else if (entry == 0) + { + entry = make_new_variable (name, table); + var_setvalue (entry, make_variable_value (entry, value, aflags)); /* XXX */ + } + else if (entry->assign_func) /* array vars have assign functions now */ + { + INVALIDATE_EXPORTSTR (entry); + newval = (aflags & ASS_APPEND) ? make_variable_value (entry, value, aflags) : value; + if (assoc_p (entry)) + entry = (*(entry->assign_func)) (entry, newval, -1, savestring ("0")); + else if (array_p (entry)) + entry = (*(entry->assign_func)) (entry, newval, 0, 0); + else + entry = (*(entry->assign_func)) (entry, newval, -1, 0); + if (newval != value) + free (newval); + return (entry); + } + else + { +assign_value: + if ((readonly_p (entry) && (aflags & ASS_FORCE) == 0) || noassign_p (entry)) + { + if (readonly_p (entry)) + err_readonly (name_cell (entry)); + return (entry); + } + + /* Variables which are bound are visible. */ + VUNSETATTR (entry, att_invisible); + + /* If we can optimize the assignment, do so and return. Right now, we + optimize appends to string variables. */ + if (can_optimize_assignment (entry, value, aflags)) + { + INVALIDATE_EXPORTSTR (entry); + optimized_assignment (entry, value, aflags); + + if (mark_modified_vars) + VSETATTR (entry, att_exported); + + if (exported_p (entry)) + array_needs_making = 1; + + return (entry); + } + +#if defined (ARRAY_VARS) + if (assoc_p (entry) || array_p (entry)) + newval = make_array_variable_value (entry, 0, "0", value, aflags); + else +#endif + newval = make_variable_value (entry, value, aflags); /* XXX */ + + /* Invalidate any cached export string */ + INVALIDATE_EXPORTSTR (entry); + +#if defined (ARRAY_VARS) + /* XXX -- this bears looking at again -- XXX */ + /* If an existing array variable x is being assigned to with x=b or + `read x' or something of that nature, silently convert it to + x[0]=b or `read x[0]'. */ + if (assoc_p (entry)) + { + assoc_insert (assoc_cell (entry), savestring ("0"), newval); + free (newval); + } + else if (array_p (entry)) + { + array_insert (array_cell (entry), 0, newval); + free (newval); + } + else +#endif + { + FREE (value_cell (entry)); + var_setvalue (entry, newval); + } + } + + if (mark_modified_vars) + VSETATTR (entry, att_exported); + + if (exported_p (entry)) + array_needs_making = 1; + + return (entry); +} + +/* Bind a variable NAME to VALUE. This conses up the name + and value strings. If we have a temporary environment, we bind there + first, then we bind into shell_variables. */ + +SHELL_VAR * +bind_variable (name, value, flags) + const char *name; + char *value; + int flags; +{ + SHELL_VAR *v, *nv; + VAR_CONTEXT *vc, *nvc; + + if (shell_variables == 0) + create_variable_tables (); + + /* If we have a temporary environment, look there first for the variable, + and, if found, modify the value there before modifying it in the + shell_variables table. This allows sourced scripts to modify values + given to them in a temporary environment while modifying the variable + value that the caller sees. */ + if (temporary_env && value) /* XXX - can value be null here? */ + bind_tempenv_variable (name, value); + + /* XXX -- handle local variables here. */ + for (vc = shell_variables; vc; vc = vc->down) + { + if (vc_isfuncenv (vc) || vc_isbltnenv (vc)) + { + v = hash_lookup (name, vc->table); + nvc = vc; + if (v && nameref_p (v)) + { + /* This starts at the context where we found the nameref. If we + want to start the name resolution over again at the original + context, this is where we need to change it */ + nv = find_variable_nameref_context (v, vc, &nvc); + if (nv == 0) + { + nv = find_variable_last_nameref_context (v, vc, &nvc); + if (nv && nameref_p (nv)) + { + /* If this nameref variable doesn't have a value yet, + set the value. Otherwise, assign using the value as + normal. */ + if (nameref_cell (nv) == 0) + return (bind_variable_internal (nv->name, value, nvc->table, 0, flags)); +#if defined (ARRAY_VARS) + else if (valid_array_reference (nameref_cell (nv), 0)) + return (assign_array_element (nameref_cell (nv), value, flags)); + else +#endif + return (bind_variable_internal (nameref_cell (nv), value, nvc->table, 0, flags)); + } + else if (nv == &nameref_maxloop_value) + { + internal_warning (_("%s: circular name reference"), v->name); +#if 1 + /* TAG:bash-5.1 */ + return (bind_global_variable (v->name, value, flags)); +#else + v = 0; /* backwards compat */ +#endif + } + else + v = nv; + } + else if (nv == &nameref_maxloop_value) + { + internal_warning (_("%s: circular name reference"), v->name); +#if 1 + /* TAG:bash-5.1 */ + return (bind_global_variable (v->name, value, flags)); +#else + v = 0; /* backwards compat */ +#endif + } + else + v = nv; + } + if (v) + return (bind_variable_internal (v->name, value, nvc->table, 0, flags)); + } + } + /* bind_variable_internal will handle nameref resolution in this case */ + return (bind_variable_internal (name, value, global_variables->table, 0, flags)); +} + +SHELL_VAR * +bind_global_variable (name, value, flags) + const char *name; + char *value; + int flags; +{ + if (shell_variables == 0) + create_variable_tables (); + + /* bind_variable_internal will handle nameref resolution in this case */ + return (bind_variable_internal (name, value, global_variables->table, 0, flags)); +} + +static SHELL_VAR * +bind_invalid_envvar (name, value, flags) + const char *name; + char *value; + int flags; +{ + if (invalid_env == 0) + invalid_env = hash_create (64); /* XXX */ + return (bind_variable_internal (name, value, invalid_env, HASH_NOSRCH, flags)); +} + +/* Make VAR, a simple shell variable, have value VALUE. Once assigned a + value, variables are no longer invisible. This is a duplicate of part + of the internals of bind_variable. If the variable is exported, or + all modified variables should be exported, mark the variable for export + and note that the export environment needs to be recreated. */ +SHELL_VAR * +bind_variable_value (var, value, aflags) + SHELL_VAR *var; + char *value; + int aflags; +{ + char *t; + int invis; + + invis = invisible_p (var); + VUNSETATTR (var, att_invisible); + + if (var->assign_func) + { + /* If we're appending, we need the old value, so use + make_variable_value */ + t = (aflags & ASS_APPEND) ? make_variable_value (var, value, aflags) : value; + (*(var->assign_func)) (var, t, -1, 0); + if (t != value && t) + free (t); + } + else + { + t = make_variable_value (var, value, aflags); + if ((aflags & (ASS_NAMEREF|ASS_FORCE)) == ASS_NAMEREF && check_selfref (name_cell (var), t, 0)) + { + if (variable_context) + internal_warning (_("%s: circular name reference"), name_cell (var)); + else + { + internal_error (_("%s: nameref variable self references not allowed"), name_cell (var)); + free (t); + if (invis) + VSETATTR (var, att_invisible); /* XXX */ + return ((SHELL_VAR *)NULL); + } + } + if ((aflags & ASS_NAMEREF) && (valid_nameref_value (t, 0) == 0)) + { + free (t); + if (invis) + VSETATTR (var, att_invisible); /* XXX */ + return ((SHELL_VAR *)NULL); + } + FREE (value_cell (var)); + var_setvalue (var, t); + } + + INVALIDATE_EXPORTSTR (var); + + if (mark_modified_vars) + VSETATTR (var, att_exported); + + if (exported_p (var)) + array_needs_making = 1; + + return (var); +} + +/* Bind/create a shell variable with the name LHS to the RHS. + This creates or modifies a variable such that it is an integer. + + This used to be in expr.c, but it is here so that all of the + variable binding stuff is localized. Since we don't want any + recursive evaluation from bind_variable() (possible without this code, + since bind_variable() calls the evaluator for variables with the integer + attribute set), we temporarily turn off the integer attribute for each + variable we set here, then turn it back on after binding as necessary. */ + +SHELL_VAR * +bind_int_variable (lhs, rhs, flags) + char *lhs, *rhs; + int flags; +{ + register SHELL_VAR *v; + int isint, isarr, implicitarray; + + isint = isarr = implicitarray = 0; +#if defined (ARRAY_VARS) + if (valid_array_reference (lhs, (flags & ASS_NOEXPAND) != 0)) + { + isarr = 1; + v = array_variable_part (lhs, (flags & ASS_NOEXPAND) != 0, (char **)0, (int *)0); + } + else if (legal_identifier (lhs) == 0) + { + sh_invalidid (lhs); + return ((SHELL_VAR *)NULL); + } + else +#endif + v = find_variable (lhs); + + if (v) + { + isint = integer_p (v); + VUNSETATTR (v, att_integer); +#if defined (ARRAY_VARS) + if (array_p (v) && isarr == 0) + implicitarray = 1; +#endif + } + +#if defined (ARRAY_VARS) + if (isarr) + v = assign_array_element (lhs, rhs, flags); + else if (implicitarray) + v = bind_array_variable (lhs, 0, rhs, 0); /* XXX - check on flags */ + else +#endif + v = bind_variable (lhs, rhs, 0); /* why not use bind_variable_value? */ + + if (v) + { + if (isint) + VSETATTR (v, att_integer); + VUNSETATTR (v, att_invisible); + } + + if (v && nameref_p (v)) + internal_warning (_("%s: assigning integer to name reference"), lhs); + + return (v); +} + +SHELL_VAR * +bind_var_to_int (var, val) + char *var; + intmax_t val; +{ + char ibuf[INT_STRLEN_BOUND (intmax_t) + 1], *p; + + p = fmtulong (val, 10, ibuf, sizeof (ibuf), 0); + return (bind_int_variable (var, p, 0)); +} + +/* 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) + const char *name; + COMMAND *value; +{ + SHELL_VAR *entry; + + entry = find_function (name); + if (entry == 0) + { + BUCKET_CONTENTS *elt; + + elt = hash_insert (savestring (name), shell_functions, HASH_NOSRCH); + entry = new_shell_variable (name); + elt->data = (PTR_T)entry; + } + else + INVALIDATE_EXPORTSTR (entry); + + if (var_isset (entry)) + dispose_command (function_cell (entry)); + + if (value) + var_setfunc (entry, copy_command (value)); + else + var_setfunc (entry, 0); + + VSETATTR (entry, att_function); + + if (mark_modified_vars) + VSETATTR (entry, att_exported); + + VUNSETATTR (entry, att_invisible); /* Just to be sure */ + + if (exported_p (entry)) + array_needs_making = 1; + +#if defined (PROGRAMMABLE_COMPLETION) + set_itemlist_dirty (&it_functions); +#endif + + return (entry); +} + +#if defined (DEBUGGER) +/* Bind a function definition, which includes source file and line number + information in addition to the command, into the FUNCTION_DEF hash table. + If (FLAGS & 1), overwrite any existing definition. If FLAGS == 0, leave + any existing definition alone. */ +void +bind_function_def (name, value, flags) + const char *name; + FUNCTION_DEF *value; + int flags; +{ + FUNCTION_DEF *entry; + BUCKET_CONTENTS *elt; + COMMAND *cmd; + + entry = find_function_def (name); + if (entry && (flags & 1)) + { + dispose_function_def_contents (entry); + entry = copy_function_def_contents (value, entry); + } + else if (entry) + return; + else + { + cmd = value->command; + value->command = 0; + entry = copy_function_def (value); + value->command = cmd; + + elt = hash_insert (savestring (name), shell_function_defs, HASH_NOSRCH); + elt->data = (PTR_T *)entry; + } +} +#endif /* DEBUGGER */ + +/* Add STRING, which is of the form foo=bar, to the temporary environment + HASH_TABLE (temporary_env). The functions in execute_cmd.c are + responsible for moving the main temporary env to one of the other + temporary environments. The expansion code in subst.c calls this. */ +int +assign_in_env (word, flags) + WORD_DESC *word; + int flags; +{ + int offset, aflags; + char *name, *temp, *value, *newname; + SHELL_VAR *var; + const char *string; + + string = word->word; + + aflags = 0; + offset = assignment (string, 0); + newname = name = savestring (string); + value = (char *)NULL; + + if (name[offset] == '=') + { + name[offset] = 0; + + /* don't ignore the `+' when assigning temporary environment */ + if (name[offset - 1] == '+') + { + name[offset - 1] = '\0'; + aflags |= ASS_APPEND; + } + + if (legal_identifier (name) == 0) + { + sh_invalidid (name); + return (0); + } + + var = find_variable (name); + if (var == 0) + { + var = find_variable_last_nameref (name, 1); + /* If we're assigning a value to a nameref variable in the temp + environment, and the value of the nameref is valid for assignment, + but the variable does not already exist, assign to the nameref + target and add the target to the temporary environment. This is + what ksh93 does */ + /* We use 2 in the call to valid_nameref_value because we don't want + to allow array references here at all (newname will be used to + create a variable directly below) */ + if (var && nameref_p (var) && valid_nameref_value (nameref_cell (var), 2)) + { + newname = nameref_cell (var); + var = 0; /* don't use it for append */ + } + } + else + newname = name_cell (var); /* no-op if not nameref */ + + if (var && (readonly_p (var) || noassign_p (var))) + { + if (readonly_p (var)) + err_readonly (name); + free (name); + return (0); + } + temp = name + offset + 1; + + value = expand_assignment_string_to_string (temp, 0); + + if (var && (aflags & ASS_APPEND)) + { + if (value == 0) + { + value = (char *)xmalloc (1); /* like do_assignment_internal */ + value[0] = '\0'; + } + temp = make_variable_value (var, value, aflags); + FREE (value); + value = temp; + } + } + + if (temporary_env == 0) + temporary_env = hash_create (TEMPENV_HASH_BUCKETS); + + var = hash_lookup (newname, temporary_env); + if (var == 0) + var = make_new_variable (newname, temporary_env); + else + FREE (value_cell (var)); + + if (value == 0) + { + value = (char *)xmalloc (1); /* see above */ + value[0] = '\0'; + } + + var_setvalue (var, value); + var->attributes |= (att_exported|att_tempvar); + var->context = variable_context; /* XXX */ + + INVALIDATE_EXPORTSTR (var); + var->exportstr = mk_env_string (newname, value, 0); + + array_needs_making = 1; + + if (flags) + stupidly_hack_special_variables (newname); + + if (echo_command_at_execute) + /* The Korn shell prints the `+ ' in front of assignment statements, + so we do too. */ + xtrace_print_assignment (name, value, 0, 1); + + free (name); + return 1; +} + +/* **************************************************************** */ +/* */ +/* Copying variables */ +/* */ +/* **************************************************************** */ + +#ifdef INCLUDE_UNUSED +/* 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)) + var_setfunc (copy, copy_command (function_cell (var))); +#if defined (ARRAY_VARS) + else if (array_p (var)) + var_setarray (copy, array_copy (array_cell (var))); + else if (assoc_p (var)) + var_setassoc (copy, assoc_copy (assoc_cell (var))); +#endif + else if (nameref_cell (var)) /* XXX - nameref */ + var_setref (copy, savestring (nameref_cell (var))); + else if (value_cell (var)) /* XXX - nameref */ + var_setvalue (copy, savestring (value_cell (var))); + else + var_setvalue (copy, (char *)NULL); + + copy->dynamic_value = var->dynamic_value; + copy->assign_func = var->assign_func; + + copy->exportstr = COPY_EXPORTSTR (var); + + copy->context = var->context; + } + return (copy); +} +#endif + +/* **************************************************************** */ +/* */ +/* Deleting and unsetting variables */ +/* */ +/* **************************************************************** */ + +/* Dispose of the information attached to VAR. */ +static void +dispose_variable_value (var) + SHELL_VAR *var; +{ + if (function_p (var)) + dispose_command (function_cell (var)); +#if defined (ARRAY_VARS) + else if (array_p (var)) + array_dispose (array_cell (var)); + else if (assoc_p (var)) + assoc_dispose (assoc_cell (var)); +#endif + else if (nameref_p (var)) + FREE (nameref_cell (var)); + else + FREE (value_cell (var)); +} + +void +dispose_variable (var) + SHELL_VAR *var; +{ + if (var == 0) + return; + + if (nofree_p (var) == 0) + dispose_variable_value (var); + + FREE_EXPORTSTR (var); + + free (var->name); + + if (exported_p (var)) + array_needs_making = 1; + + free (var); +} + +/* Unset the shell variable referenced by NAME. Unsetting a nameref variable + unsets the variable it resolves to but leaves the nameref alone. */ +int +unbind_variable (name) + const char *name; +{ + SHELL_VAR *v, *nv; + int r; + + v = var_lookup (name, shell_variables); + nv = (v && nameref_p (v)) ? find_variable_nameref (v) : (SHELL_VAR *)NULL; + + r = nv ? makunbound (nv->name, shell_variables) : makunbound (name, shell_variables); + return r; +} + +/* Unbind NAME, where NAME is assumed to be a nameref variable */ +int +unbind_nameref (name) + const char *name; +{ + SHELL_VAR *v; + + v = var_lookup (name, shell_variables); + if (v && nameref_p (v)) + return makunbound (name, shell_variables); + return 0; +} + +/* Unbind the first instance of NAME, whether it's a nameref or not */ +int +unbind_variable_noref (name) + const char *name; +{ + SHELL_VAR *v; + + v = var_lookup (name, shell_variables); + if (v) + return makunbound (name, shell_variables); + return 0; +} + +int +check_unbind_variable (name) + const char *name; +{ + SHELL_VAR *v; + + v = find_variable (name); + if (v && readonly_p (v)) + { + internal_error (_("%s: cannot unset: readonly %s"), name, "variable"); + return -1; + } + return (unbind_variable (name)); +} + +/* Unset the shell function named NAME. */ +int +unbind_func (name) + const char *name; +{ + BUCKET_CONTENTS *elt; + SHELL_VAR *func; + + elt = hash_remove (name, shell_functions, 0); + + if (elt == 0) + return -1; + +#if defined (PROGRAMMABLE_COMPLETION) + set_itemlist_dirty (&it_functions); +#endif + + func = (SHELL_VAR *)elt->data; + if (func) + { + if (exported_p (func)) + array_needs_making++; + dispose_variable (func); + } + + free (elt->key); + free (elt); + + return 0; +} + +#if defined (DEBUGGER) +int +unbind_function_def (name) + const char *name; +{ + BUCKET_CONTENTS *elt; + FUNCTION_DEF *funcdef; + + elt = hash_remove (name, shell_function_defs, 0); + + if (elt == 0) + return -1; + + funcdef = (FUNCTION_DEF *)elt->data; + if (funcdef) + dispose_function_def (funcdef); + + free (elt->key); + free (elt); + + return 0; +} +#endif /* DEBUGGER */ + +int +delete_var (name, vc) + const char *name; + VAR_CONTEXT *vc; +{ + BUCKET_CONTENTS *elt; + SHELL_VAR *old_var; + VAR_CONTEXT *v; + + for (elt = (BUCKET_CONTENTS *)NULL, v = vc; v; v = v->down) + if (elt = hash_remove (name, v->table, 0)) + break; + + if (elt == 0) + return (-1); + + old_var = (SHELL_VAR *)elt->data; + free (elt->key); + free (elt); + + dispose_variable (old_var); + 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. */ +int +makunbound (name, vc) + const char *name; + VAR_CONTEXT *vc; +{ + BUCKET_CONTENTS *elt, *new_elt; + SHELL_VAR *old_var; + VAR_CONTEXT *v; + char *t; + + for (elt = (BUCKET_CONTENTS *)NULL, v = vc; v; v = v->down) + if (elt = hash_remove (name, v->table, 0)) + break; + + if (elt == 0) + return (-1); + + old_var = (SHELL_VAR *)elt->data; + + if (old_var && exported_p (old_var)) + array_needs_making++; + + /* If we're unsetting a local variable and we're still executing inside + the function, just mark the variable as invisible. The function + eventually called by pop_var_context() will clean it up later. This + must be done so that if the variable is subsequently assigned a new + value inside the function, the `local' attribute is still present. + We also need to add it back into the correct hash table. */ + if (old_var && local_p (old_var) && + (old_var->context == variable_context || (localvar_unset && old_var->context < variable_context))) + { + if (nofree_p (old_var)) + var_setvalue (old_var, (char *)NULL); +#if defined (ARRAY_VARS) + else if (array_p (old_var)) + array_dispose (array_cell (old_var)); + else if (assoc_p (old_var)) + assoc_dispose (assoc_cell (old_var)); +#endif + else if (nameref_p (old_var)) + FREE (nameref_cell (old_var)); + else + FREE (value_cell (old_var)); + /* Reset the attributes. Preserve the export attribute if the variable + came from a temporary environment. Make sure it stays local, and + make it invisible. */ + old_var->attributes = (exported_p (old_var) && tempvar_p (old_var)) ? att_exported : 0; + VSETATTR (old_var, att_local); + VSETATTR (old_var, att_invisible); + var_setvalue (old_var, (char *)NULL); + INVALIDATE_EXPORTSTR (old_var); + + new_elt = hash_insert (savestring (old_var->name), v->table, 0); + new_elt->data = (PTR_T)old_var; + stupidly_hack_special_variables (old_var->name); + + free (elt->key); + free (elt); + return (0); + } + + /* 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); +} + +/* Get rid of all of the variables in the current context. */ +void +kill_all_local_variables () +{ + VAR_CONTEXT *vc; + + for (vc = shell_variables; vc; vc = vc->down) + if (vc_isfuncenv (vc) && vc->scope == variable_context) + break; + if (vc == 0) + return; /* XXX */ + + if (vc->table && vc_haslocals (vc)) + { + delete_all_variables (vc->table); + hash_dispose (vc->table); + } + vc->table = (HASH_TABLE *)NULL; +} + +static void +free_variable_hash_data (data) + PTR_T data; +{ + SHELL_VAR *var; + + var = (SHELL_VAR *)data; + dispose_variable (var); +} + +/* Delete the entire contents of the hash table. */ +void +delete_all_variables (hashed_vars) + HASH_TABLE *hashed_vars; +{ + hash_flush (hashed_vars, free_variable_hash_data); +} + +/* **************************************************************** */ +/* */ +/* Setting variable attributes */ +/* */ +/* **************************************************************** */ + +#define FIND_OR_MAKE_VARIABLE(name, entry) \ + do \ + { \ + entry = find_variable (name); \ + if (!entry) \ + { \ + entry = bind_variable (name, "", 0); \ + if (!no_invisible_vars && entry) entry->attributes |= att_invisible; \ + } \ + } \ + while (0) + +/* Make the variable associated with NAME be readonly. + If NAME does not exist yet, create it. */ +void +set_var_read_only (name) + char *name; +{ + SHELL_VAR *entry; + + FIND_OR_MAKE_VARIABLE (name, entry); + VSETATTR (entry, att_readonly); +} + +#ifdef INCLUDE_UNUSED +/* Make the function associated with NAME be readonly. + If NAME does not exist, we just punt, like auto_export code below. */ +void +set_func_read_only (name) + const char *name; +{ + SHELL_VAR *entry; + + entry = find_function (name); + if (entry) + VSETATTR (entry, att_readonly); +} + +/* Make the variable associated with NAME be auto-exported. + If NAME does not exist yet, create it. */ +void +set_var_auto_export (name) + char *name; +{ + SHELL_VAR *entry; + + FIND_OR_MAKE_VARIABLE (name, entry); + set_auto_export (entry); +} + +/* Make the function associated with NAME be auto-exported. */ +void +set_func_auto_export (name) + const char *name; +{ + SHELL_VAR *entry; + + entry = find_function (name); + if (entry) + set_auto_export (entry); +} +#endif + +/* **************************************************************** */ +/* */ +/* Creating lists of variables */ +/* */ +/* **************************************************************** */ + +static VARLIST * +vlist_alloc (nentries) + int nentries; +{ + VARLIST *vlist; + + vlist = (VARLIST *)xmalloc (sizeof (VARLIST)); + vlist->list = (SHELL_VAR **)xmalloc ((nentries + 1) * sizeof (SHELL_VAR *)); + vlist->list_size = nentries; + vlist->list_len = 0; + vlist->list[0] = (SHELL_VAR *)NULL; + + return vlist; +} + +static VARLIST * +vlist_realloc (vlist, n) + VARLIST *vlist; + int n; +{ + if (vlist == 0) + return (vlist = vlist_alloc (n)); + if (n > vlist->list_size) + { + vlist->list_size = n; + vlist->list = (SHELL_VAR **)xrealloc (vlist->list, (vlist->list_size + 1) * sizeof (SHELL_VAR *)); + } + return vlist; +} + +static void +vlist_add (vlist, var, flags) + VARLIST *vlist; + SHELL_VAR *var; + int flags; +{ + register int i; + + for (i = 0; i < vlist->list_len; i++) + if (STREQ (var->name, vlist->list[i]->name)) + break; + if (i < vlist->list_len) + return; + + if (i >= vlist->list_size) + vlist = vlist_realloc (vlist, vlist->list_size + 16); + + vlist->list[vlist->list_len++] = var; + vlist->list[vlist->list_len] = (SHELL_VAR *)NULL; +} + +/* Map FUNCTION over the variables in VAR_HASH_TABLE. Return an array of the + variables for which FUNCTION returns a non-zero value. A NULL value + for FUNCTION means to use all variables. */ +SHELL_VAR ** +map_over (function, vc) + sh_var_map_func_t *function; + VAR_CONTEXT *vc; +{ + VAR_CONTEXT *v; + VARLIST *vlist; + SHELL_VAR **ret; + int nentries; + + for (nentries = 0, v = vc; v; v = v->down) + nentries += HASH_ENTRIES (v->table); + + if (nentries == 0) + return (SHELL_VAR **)NULL; + + vlist = vlist_alloc (nentries); + + for (v = vc; v; v = v->down) + flatten (v->table, function, vlist, 0); + + ret = vlist->list; + free (vlist); + return ret; +} + +SHELL_VAR ** +map_over_funcs (function) + sh_var_map_func_t *function; +{ + VARLIST *vlist; + SHELL_VAR **ret; + + if (shell_functions == 0 || HASH_ENTRIES (shell_functions) == 0) + return ((SHELL_VAR **)NULL); + + vlist = vlist_alloc (HASH_ENTRIES (shell_functions)); + + flatten (shell_functions, function, vlist, 0); + + ret = vlist->list; + free (vlist); + return ret; +} + +/* Flatten VAR_HASH_TABLE, applying FUNC to each member and adding those + elements for which FUNC succeeds to VLIST->list. FLAGS is reserved + for future use. Only unique names are added to VLIST. If FUNC is + NULL, each variable in VAR_HASH_TABLE is added to VLIST. If VLIST is + NULL, FUNC is applied to each SHELL_VAR in VAR_HASH_TABLE. If VLIST + and FUNC are both NULL, nothing happens. */ +static void +flatten (var_hash_table, func, vlist, flags) + HASH_TABLE *var_hash_table; + sh_var_map_func_t *func; + VARLIST *vlist; + int flags; +{ + register int i; + register BUCKET_CONTENTS *tlist; + int r; + SHELL_VAR *var; + + if (var_hash_table == 0 || (HASH_ENTRIES (var_hash_table) == 0) || (vlist == 0 && func == 0)) + return; + + for (i = 0; i < var_hash_table->nbuckets; i++) + { + for (tlist = hash_items (i, var_hash_table); tlist; tlist = tlist->next) + { + var = (SHELL_VAR *)tlist->data; + + r = func ? (*func) (var) : 1; + if (r && vlist) + vlist_add (vlist, var, flags); + } + } +} + +void +sort_variables (array) + SHELL_VAR **array; +{ + qsort (array, strvec_len ((char **)array), sizeof (SHELL_VAR *), (QSFUNC *)qsort_var_comp); +} + +static 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); +} + +/* Apply FUNC to each variable in SHELL_VARIABLES, adding each one for + which FUNC succeeds to an array of SHELL_VAR *s. Returns the array. */ +static SHELL_VAR ** +vapply (func) + sh_var_map_func_t *func; +{ + SHELL_VAR **list; + + list = map_over (func, shell_variables); + if (list /* && posixly_correct */) + sort_variables (list); + return (list); +} + +/* Apply FUNC to each variable in SHELL_FUNCTIONS, adding each one for + which FUNC succeeds to an array of SHELL_VAR *s. Returns the array. */ +static SHELL_VAR ** +fapply (func) + sh_var_map_func_t *func; +{ + SHELL_VAR **list; + + list = map_over_funcs (func); + if (list /* && posixly_correct */) + sort_variables (list); + return (list); +} + +/* Create a NULL terminated array of all the shell variables. */ +SHELL_VAR ** +all_shell_variables () +{ + return (vapply ((sh_var_map_func_t *)NULL)); +} + +/* Create a NULL terminated array of all the shell functions. */ +SHELL_VAR ** +all_shell_functions () +{ + return (fapply ((sh_var_map_func_t *)NULL)); +} + +static int +visible_var (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0); +} + +SHELL_VAR ** +all_visible_functions () +{ + return (fapply (visible_var)); +} + +SHELL_VAR ** +all_visible_variables () +{ + return (vapply (visible_var)); +} + +/* Return non-zero if the variable VAR is visible and exported. Array + variables cannot be exported. */ +static int +visible_and_exported (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0 && exported_p (var)); +} + +/* Candidate variables for the export environment are either valid variables + with the export attribute or invalid variables inherited from the initial + environment and simply passed through. */ +static int +export_environment_candidate (var) + SHELL_VAR *var; +{ + return (exported_p (var) && (invisible_p (var) == 0 || imported_p (var))); +} + +/* Return non-zero if VAR is a local variable in the current context and + is exported. */ +static int +local_and_exported (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0 && local_p (var) && var->context == variable_context && exported_p (var)); +} + +SHELL_VAR ** +all_exported_variables () +{ + return (vapply (visible_and_exported)); +} + +SHELL_VAR ** +local_exported_variables () +{ + return (vapply (local_and_exported)); +} + +static int +variable_in_context (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0 && local_p (var) && var->context == variable_context); +} + +SHELL_VAR ** +all_local_variables () +{ + VARLIST *vlist; + SHELL_VAR **ret; + VAR_CONTEXT *vc; + + vc = shell_variables; + for (vc = shell_variables; vc; vc = vc->down) + if (vc_isfuncenv (vc) && vc->scope == variable_context) + break; + + if (vc == 0) + { + internal_error (_("all_local_variables: no function context at current scope")); + return (SHELL_VAR **)NULL; + } + if (vc->table == 0 || HASH_ENTRIES (vc->table) == 0 || vc_haslocals (vc) == 0) + return (SHELL_VAR **)NULL; + + vlist = vlist_alloc (HASH_ENTRIES (vc->table)); + + flatten (vc->table, variable_in_context, vlist, 0); + + ret = vlist->list; + free (vlist); + if (ret) + sort_variables (ret); + return ret; +} + +#if defined (ARRAY_VARS) +/* Return non-zero if the variable VAR is visible and an array. */ +static int +visible_array_vars (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0 && (array_p (var) || assoc_p (var))); +} + +SHELL_VAR ** +all_array_variables () +{ + return (vapply (visible_array_vars)); +} +#endif /* ARRAY_VARS */ + +char ** +all_variables_matching_prefix (prefix) + const char *prefix; +{ + SHELL_VAR **varlist; + char **rlist; + int vind, rind, plen; + + plen = STRLEN (prefix); + varlist = all_visible_variables (); + for (vind = 0; varlist && varlist[vind]; vind++) + ; + if (varlist == 0 || vind == 0) + return ((char **)NULL); + rlist = strvec_create (vind + 1); + for (vind = rind = 0; varlist[vind]; vind++) + { + if (plen == 0 || STREQN (prefix, varlist[vind]->name, plen)) + rlist[rind++] = savestring (varlist[vind]->name); + } + rlist[rind] = (char *)0; + free (varlist); + + return rlist; +} + +/* **************************************************************** */ +/* */ +/* Managing temporary variable scopes */ +/* */ +/* **************************************************************** */ + +/* Make variable NAME have VALUE in the temporary environment. */ +static SHELL_VAR * +bind_tempenv_variable (name, value) + const char *name; + char *value; +{ + SHELL_VAR *var; + + var = temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL; + + if (var) + { + FREE (value_cell (var)); + var_setvalue (var, savestring (value)); + INVALIDATE_EXPORTSTR (var); + } + + return (var); +} + +/* Find a variable in the temporary environment that is named NAME. + Return the SHELL_VAR *, or NULL if not found. */ +SHELL_VAR * +find_tempenv_variable (name) + const char *name; +{ + return (temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL); +} + +char **tempvar_list; +int tvlist_ind; + +/* Take a variable from an assignment statement preceding a posix special + builtin (including `return') and create a global variable from it. This + is called from merge_temporary_env, which is only called when in posix + mode. */ +static void +push_posix_temp_var (data) + PTR_T data; +{ + SHELL_VAR *var, *v; + HASH_TABLE *binding_table; + + var = (SHELL_VAR *)data; + + binding_table = global_variables->table; + if (binding_table == 0) + binding_table = global_variables->table = hash_create (VARIABLES_HASH_BUCKETS); + + v = bind_variable_internal (var->name, value_cell (var), binding_table, 0, ASS_FORCE|ASS_NOLONGJMP); + + /* global variables are no longer temporary and don't need propagating. */ + var->attributes &= ~(att_tempvar|att_propagate); + if (v) + v->attributes |= var->attributes; + + if (find_special_var (var->name) >= 0) + tempvar_list[tvlist_ind++] = savestring (var->name); + + dispose_variable (var); +} + +/* Push the variable described by (SHELL_VAR *)DATA down to the next + variable context from the temporary environment. This can be called + from one context: + 1. propagate_temp_var: which is called to propagate variables in + assignments like `var=value declare -x var' to the surrounding + scope. + + In this case, the variable should have the att_propagate flag set and + we can create variables in the current scope. +*/ +static void +push_temp_var (data) + PTR_T data; +{ + SHELL_VAR *var, *v; + HASH_TABLE *binding_table; + + var = (SHELL_VAR *)data; + + binding_table = shell_variables->table; + if (binding_table == 0) + { + if (shell_variables == global_variables) + /* shouldn't happen */ + binding_table = shell_variables->table = global_variables->table = hash_create (VARIABLES_HASH_BUCKETS); + else + binding_table = shell_variables->table = hash_create (TEMPENV_HASH_BUCKETS); + } + + v = bind_variable_internal (var->name, value_cell (var), binding_table, 0, ASS_FORCE|ASS_NOLONGJMP); + + /* XXX - should we set the context here? It shouldn't matter because of how + assign_in_env works, but we do it anyway. */ + if (v) + v->context = shell_variables->scope; + + if (binding_table == global_variables->table) /* XXX */ + var->attributes &= ~(att_tempvar|att_propagate); + else + { + var->attributes |= att_propagate; + if (binding_table == shell_variables->table) + shell_variables->flags |= VC_HASTMPVAR; + } + if (v) + v->attributes |= var->attributes; + + if (find_special_var (var->name) >= 0) + tempvar_list[tvlist_ind++] = savestring (var->name); + + dispose_variable (var); +} + +/* Take a variable described by DATA and push it to the surrounding scope if + the PROPAGATE attribute is set. That gets set by push_temp_var if we are + taking a variable like `var=value declare -x var' and propagating it to + the enclosing scope. */ +static void +propagate_temp_var (data) + PTR_T data; +{ + SHELL_VAR *var; + + var = (SHELL_VAR *)data; + if (tempvar_p (var) && (var->attributes & att_propagate)) + push_temp_var (data); + else + { + if (find_special_var (var->name) >= 0) + tempvar_list[tvlist_ind++] = savestring (var->name); + dispose_variable (var); + } +} + +/* Free the storage used in the hash table for temporary + environment variables. PUSHF is a function to be called + to free each hash table entry. It takes care of pushing variables + to previous scopes if appropriate. PUSHF stores names of variables + that require special handling (e.g., IFS) on tempvar_list, so this + function can call stupidly_hack_special_variables on all the + variables in the list when the temporary hash table is destroyed. */ +static void +dispose_temporary_env (pushf) + sh_free_func_t *pushf; +{ + int i; + + tempvar_list = strvec_create (HASH_ENTRIES (temporary_env) + 1); + tempvar_list[tvlist_ind = 0] = 0; + + hash_flush (temporary_env, pushf); + hash_dispose (temporary_env); + temporary_env = (HASH_TABLE *)NULL; + + tempvar_list[tvlist_ind] = 0; + + array_needs_making = 1; + + for (i = 0; i < tvlist_ind; i++) + stupidly_hack_special_variables (tempvar_list[i]); + + strvec_dispose (tempvar_list); + tempvar_list = 0; + tvlist_ind = 0; +} + +void +dispose_used_env_vars () +{ + if (temporary_env) + { + dispose_temporary_env (propagate_temp_var); + maybe_make_export_env (); + } +} + +/* Take all of the shell variables in the temporary environment HASH_TABLE + and make shell variables from them at the current variable context. + Right now, this is only called in Posix mode to implement the historical + accident of creating global variables from assignment statements preceding + special builtins, but we check in case this acquires another caller later. */ +void +merge_temporary_env () +{ + if (temporary_env) + dispose_temporary_env (posixly_correct ? push_posix_temp_var : push_temp_var); +} + +void +flush_temporary_env () +{ + if (temporary_env) + { + hash_flush (temporary_env, free_variable_hash_data); + hash_dispose (temporary_env); + temporary_env = (HASH_TABLE *)NULL; + } +} + +/* **************************************************************** */ +/* */ +/* Creating and manipulating the environment */ +/* */ +/* **************************************************************** */ + +static inline char * +mk_env_string (name, value, isfunc) + const char *name, *value; + int isfunc; +{ + size_t name_len, value_len; + char *p, *q, *t; + + name_len = strlen (name); + value_len = STRLEN (value); + + /* If we are exporting a shell function, construct the encoded function + name. */ + if (isfunc && value) + { + p = (char *)xmalloc (BASHFUNC_PREFLEN + name_len + BASHFUNC_SUFFLEN + value_len + 2); + q = p; + memcpy (q, BASHFUNC_PREFIX, BASHFUNC_PREFLEN); + q += BASHFUNC_PREFLEN; + memcpy (q, name, name_len); + q += name_len; + memcpy (q, BASHFUNC_SUFFIX, BASHFUNC_SUFFLEN); + q += BASHFUNC_SUFFLEN; + } + else + { + p = (char *)xmalloc (2 + name_len + value_len); + memcpy (p, name, name_len); + q = p + name_len; + } + + q[0] = '='; + if (value && *value) + { + if (isfunc) + { + t = dequote_escapes (value); + value_len = STRLEN (t); + memcpy (q + 1, t, value_len + 1); + free (t); + } + else + memcpy (q + 1, value, value_len + 1); + } + else + q[1] = '\0'; + + return (p); +} + +#ifdef DEBUG +/* Debugging */ +static int +valid_exportstr (v) + SHELL_VAR *v; +{ + char *s; + + s = v->exportstr; + if (s == 0) + { + internal_error (_("%s has null exportstr"), v->name); + return (0); + } + if (legal_variable_starter ((unsigned char)*s) == 0) + { + internal_error (_("invalid character %d in exportstr for %s"), *s, v->name); + return (0); + } + for (s = v->exportstr + 1; s && *s; s++) + { + if (*s == '=') + break; + if (legal_variable_char ((unsigned char)*s) == 0) + { + internal_error (_("invalid character %d in exportstr for %s"), *s, v->name); + return (0); + } + } + if (*s != '=') + { + internal_error (_("no `=' in exportstr for %s"), v->name); + return (0); + } + return (1); +} +#endif + +static char ** +make_env_array_from_var_list (vars) + SHELL_VAR **vars; +{ + register int i, list_index; + register SHELL_VAR *var; + char **list, *value; + + list = strvec_create ((1 + strvec_len ((char **)vars))); + +#define USE_EXPORTSTR (value == var->exportstr) + + for (i = 0, list_index = 0; var = vars[i]; i++) + { +#if defined (__CYGWIN__) + /* We don't use the exportstr stuff on Cygwin at all. */ + INVALIDATE_EXPORTSTR (var); +#endif + + /* If the value is generated dynamically, generate it here. */ + if (regen_p (var) && var->dynamic_value) + { + var = (*(var->dynamic_value)) (var); + INVALIDATE_EXPORTSTR (var); + } + + if (var->exportstr) + value = var->exportstr; + else if (function_p (var)) + value = named_function_string ((char *)NULL, function_cell (var), 0); +#if defined (ARRAY_VARS) + else if (array_p (var)) +# if ARRAY_EXPORT + value = array_to_assign (array_cell (var), 0); +# else + continue; /* XXX array vars cannot yet be exported */ +# endif /* ARRAY_EXPORT */ + else if (assoc_p (var)) +# if 0 + value = assoc_to_assign (assoc_cell (var), 0); +# else + continue; /* XXX associative array vars cannot yet be exported */ +# endif +#endif + else + value = value_cell (var); + + if (value) + { + /* Gee, I'd like to get away with not using savestring() if we're + using the cached exportstr... */ + list[list_index] = USE_EXPORTSTR ? savestring (value) + : mk_env_string (var->name, value, function_p (var)); + + if (USE_EXPORTSTR == 0) + SAVE_EXPORTSTR (var, list[list_index]); + + list_index++; +#undef USE_EXPORTSTR + +#if 0 /* not yet */ +#if defined (ARRAY_VARS) + if (array_p (var) || assoc_p (var)) + free (value); +#endif +#endif + } + } + + list[list_index] = (char *)NULL; + return (list); +} + +/* Make an array of assignment statements from the hash table + HASHED_VARS which contains SHELL_VARs. Only visible, exported + variables are eligible. */ +static char ** +make_var_export_array (vcxt) + VAR_CONTEXT *vcxt; +{ + char **list; + SHELL_VAR **vars; + +#if 0 + vars = map_over (visible_and_exported, vcxt); +#else + vars = map_over (export_environment_candidate, vcxt); +#endif + + if (vars == 0) + return (char **)NULL; + + list = make_env_array_from_var_list (vars); + + free (vars); + return (list); +} + +static char ** +make_func_export_array () +{ + char **list; + SHELL_VAR **vars; + + vars = map_over_funcs (visible_and_exported); + if (vars == 0) + return (char **)NULL; + + list = make_env_array_from_var_list (vars); + + free (vars); + return (list); +} + +/* Add ENVSTR to the end of the exported environment, EXPORT_ENV. */ +#define add_to_export_env(envstr,do_alloc) \ +do \ + { \ + if (export_env_index >= (export_env_size - 1)) \ + { \ + export_env_size += 16; \ + export_env = strvec_resize (export_env, export_env_size); \ + environ = export_env; \ + } \ + export_env[export_env_index++] = (do_alloc) ? savestring (envstr) : envstr; \ + export_env[export_env_index] = (char *)NULL; \ + } while (0) + +/* Add ASSIGN to EXPORT_ENV, or supercede a previous assignment in the + array with the same left-hand side. Return the new EXPORT_ENV. */ +char ** +add_or_supercede_exported_var (assign, do_alloc) + char *assign; + int do_alloc; +{ + register int i; + int equal_offset; + + equal_offset = assignment (assign, 0); + if (equal_offset == 0) + return (export_env); + + /* If this is a function, then only supersede the function definition. + We do this by including the `=() {' in the comparison, like + initialize_shell_variables does. */ + if (assign[equal_offset + 1] == '(' && + strncmp (assign + equal_offset + 2, ") {", 3) == 0) /* } */ + equal_offset += 4; + + for (i = 0; i < export_env_index; i++) + { + if (STREQN (assign, export_env[i], equal_offset + 1)) + { + free (export_env[i]); + export_env[i] = do_alloc ? savestring (assign) : assign; + return (export_env); + } + } + add_to_export_env (assign, do_alloc); + return (export_env); +} + +static void +add_temp_array_to_env (temp_array, do_alloc, do_supercede) + char **temp_array; + int do_alloc, do_supercede; +{ + register int i; + + if (temp_array == 0) + return; + + for (i = 0; temp_array[i]; i++) + { + if (do_supercede) + export_env = add_or_supercede_exported_var (temp_array[i], do_alloc); + else + add_to_export_env (temp_array[i], do_alloc); + } + + free (temp_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++'. + + The order to add to the array is: + temporary_env + list of var contexts whose head is shell_variables + shell_functions + + This is the shell variable lookup order. We add only new variable + names at each step, which allows local variables and variables in + the temporary environments to shadow variables in the global (or + any previous) scope. +*/ + +static int +n_shell_variables () +{ + VAR_CONTEXT *vc; + int n; + + for (n = 0, vc = shell_variables; vc; vc = vc->down) + n += HASH_ENTRIES (vc->table); + return n; +} + +int +chkexport (name) + char *name; +{ + SHELL_VAR *v; + + v = find_variable (name); + if (v && exported_p (v)) + { + array_needs_making = 1; + maybe_make_export_env (); + return 1; + } + return 0; +} + +void +maybe_make_export_env () +{ + register char **temp_array; + int new_size; + VAR_CONTEXT *tcxt, *icxt; + + if (array_needs_making) + { + if (export_env) + strvec_flush (export_env); + + /* Make a guess based on how many shell variables and functions we + have. Since there will always be array variables, and array + variables are not (yet) exported, this will always be big enough + for the exported variables and functions. */ + new_size = n_shell_variables () + HASH_ENTRIES (shell_functions) + 1 + + HASH_ENTRIES (temporary_env) + HASH_ENTRIES (invalid_env); + if (new_size > export_env_size) + { + export_env_size = new_size; + export_env = strvec_resize (export_env, export_env_size); + environ = export_env; + } + export_env[export_env_index = 0] = (char *)NULL; + + /* Make a dummy variable context from the temporary_env, stick it on + the front of shell_variables, call make_var_export_array on the + whole thing to flatten it, and convert the list of SHELL_VAR *s + to the form needed by the environment. */ + if (temporary_env) + { + tcxt = new_var_context ((char *)NULL, 0); + tcxt->table = temporary_env; + tcxt->down = shell_variables; + } + else + tcxt = shell_variables; + + if (invalid_env) + { + icxt = new_var_context ((char *)NULL, 0); + icxt->table = invalid_env; + icxt->down = tcxt; + } + else + icxt = tcxt; + + temp_array = make_var_export_array (icxt); + if (temp_array) + add_temp_array_to_env (temp_array, 0, 0); + + if (icxt != tcxt) + free (icxt); + + if (tcxt != shell_variables) + free (tcxt); + +#if defined (RESTRICTED_SHELL) + /* Restricted shells may not export shell functions. */ + temp_array = restricted ? (char **)0 : make_func_export_array (); +#else + temp_array = make_func_export_array (); +#endif + if (temp_array) + add_temp_array_to_env (temp_array, 0, 0); + + array_needs_making = 0; + } +} + +/* This is an efficiency hack. PWD and OLDPWD are auto-exported, so + we will need to remake the exported environment every time we + change directories. `_' is always put into the environment for + every external command, so without special treatment it will always + cause the environment to be remade. + + If there is no other reason to make the exported environment, we can + just update the variables in place and mark the exported environment + as no longer needing a remake. */ +void +update_export_env_inplace (env_prefix, preflen, value) + char *env_prefix; + int preflen; + char *value; +{ + char *evar; + + evar = (char *)xmalloc (STRLEN (value) + preflen + 1); + strcpy (evar, env_prefix); + if (value) + strcpy (evar + preflen, value); + export_env = add_or_supercede_exported_var (evar, 0); +} + +/* We always put _ in the environment as the name of this command. */ +void +put_command_name_into_env (command_name) + char *command_name; +{ + update_export_env_inplace ("_=", 2, command_name); +} + +/* **************************************************************** */ +/* */ +/* Managing variable contexts */ +/* */ +/* **************************************************************** */ + +/* Allocate and return a new variable context with NAME and FLAGS. + NAME can be NULL. */ + +VAR_CONTEXT * +new_var_context (name, flags) + char *name; + int flags; +{ + VAR_CONTEXT *vc; + + vc = (VAR_CONTEXT *)xmalloc (sizeof (VAR_CONTEXT)); + vc->name = name ? savestring (name) : (char *)NULL; + vc->scope = variable_context; + vc->flags = flags; + + vc->up = vc->down = (VAR_CONTEXT *)NULL; + vc->table = (HASH_TABLE *)NULL; + + return vc; +} + +/* Free a variable context and its data, including the hash table. Dispose + all of the variables. */ +void +dispose_var_context (vc) + VAR_CONTEXT *vc; +{ + FREE (vc->name); + + if (vc->table) + { + delete_all_variables (vc->table); + hash_dispose (vc->table); + } + + free (vc); +} + +/* Set VAR's scope level to the current variable context. */ +static int +set_context (var) + SHELL_VAR *var; +{ + return (var->context = variable_context); +} + +/* Make a new variable context with NAME and FLAGS and a HASH_TABLE of + temporary variables, and push it onto shell_variables. This is + for shell functions. */ +VAR_CONTEXT * +push_var_context (name, flags, tempvars) + char *name; + int flags; + HASH_TABLE *tempvars; +{ + VAR_CONTEXT *vc; + int posix_func_behavior; + + /* As of IEEE Std 1003.1-2017, assignment statements preceding shell + functions no longer behave like assignment statements preceding + special builtins, and do not persist in the current shell environment. + This is austin group interp #654, though nobody implements it yet. */ +#if 0 /* XXX - TAG: bash-5.1 */ + posix_func_behavior = 0; +#else + posix_func_behavior = posixly_correct; +#endif + + vc = new_var_context (name, flags); + /* Posix interp 1009, temporary assignments preceding function calls modify + the current environment *before* the command is executed. */ + if (posix_func_behavior && (flags & VC_FUNCENV) && tempvars == temporary_env) + merge_temporary_env (); + else if (tempvars) + { + vc->table = tempvars; + /* Have to do this because the temp environment was created before + variable_context was incremented. */ + flatten (tempvars, set_context, (VARLIST *)NULL, 0); + vc->flags |= VC_HASTMPVAR; + } + vc->down = shell_variables; + shell_variables->up = vc; + + return (shell_variables = vc); +} + +/* This can be called from one of two code paths: + 1. pop_scope, which implements the posix rules for propagating variable + assignments preceding special builtins to the surrounding scope + (push_builtin_var); + 2. pop_var_context, which is called from pop_context and implements the + posix rules for propagating variable assignments preceding function + calls to the surrounding scope (push_func_var). + + It takes variables out of a temporary environment hash table. We take the + variable in data. +*/ + +static inline void +push_posix_tempvar_internal (var, isbltin) + SHELL_VAR *var; + int isbltin; +{ + SHELL_VAR *v; + int posix_var_behavior; + + /* As of IEEE Std 1003.1-2017, assignment statements preceding shell + functions no longer behave like assignment statements preceding + special builtins, and do not persist in the current shell environment. + This is austin group interp #654, though nobody implements it yet. */ +#if 0 /* XXX - TAG: bash-5.1 */ + posix_var_behavior = posixly_correct && isbltin; +#else + posix_var_behavior = posixly_correct; +#endif + + if (local_p (var) && STREQ (var->name, "-")) + set_current_options (value_cell (var)); + else if (tempvar_p (var) && (posix_var_behavior || (var->attributes & att_propagate))) + { + /* Make sure we have a hash table to store the variable in while it is + being propagated down to the global variables table. Create one if + we have to */ + if ((vc_isfuncenv (shell_variables) || vc_istempenv (shell_variables)) && shell_variables->table == 0) + shell_variables->table = hash_create (VARIABLES_HASH_BUCKETS); + v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0, 0); + /* XXX - should we set v->context here? */ + if (v) + v->context = shell_variables->scope; +#if defined (ARRAY_VARS) + if (v && (array_p (var) || assoc_p (var))) + { + FREE (value_cell (v)); + if (array_p (var)) + var_setarray (v, array_copy (array_cell (var))); + else + var_setassoc (v, assoc_copy (assoc_cell (var))); + } +#endif + if (shell_variables == global_variables) + var->attributes &= ~(att_tempvar|att_propagate); + else + shell_variables->flags |= VC_HASTMPVAR; + if (v) + v->attributes |= var->attributes; + } + else + stupidly_hack_special_variables (var->name); /* XXX */ + + dispose_variable (var); +} + +static void +push_func_var (data) + PTR_T data; +{ + SHELL_VAR *var; + + var = (SHELL_VAR *)data; + push_posix_tempvar_internal (var, 0); +} + +static void +push_builtin_var (data) + PTR_T data; +{ + SHELL_VAR *var; + + var = (SHELL_VAR *)data; + push_posix_tempvar_internal (var, 1); +} + +/* Pop the top context off of VCXT and dispose of it, returning the rest of + the stack. */ +void +pop_var_context () +{ + VAR_CONTEXT *ret, *vcxt; + + vcxt = shell_variables; + if (vc_isfuncenv (vcxt) == 0) + { + internal_error (_("pop_var_context: head of shell_variables not a function context")); + return; + } + + if (ret = vcxt->down) + { + ret->up = (VAR_CONTEXT *)NULL; + shell_variables = ret; + if (vcxt->table) + hash_flush (vcxt->table, push_func_var); + dispose_var_context (vcxt); + } + else + internal_error (_("pop_var_context: no global_variables context")); +} + +/* Delete the HASH_TABLEs for all variable contexts beginning at VCXT, and + all of the VAR_CONTEXTs except GLOBAL_VARIABLES. */ +void +delete_all_contexts (vcxt) + VAR_CONTEXT *vcxt; +{ + VAR_CONTEXT *v, *t; + + for (v = vcxt; v != global_variables; v = t) + { + t = v->down; + dispose_var_context (v); + } + + delete_all_variables (global_variables->table); + shell_variables = global_variables; +} + +/* **************************************************************** */ +/* */ +/* Pushing and Popping temporary variable scopes */ +/* */ +/* **************************************************************** */ + +VAR_CONTEXT * +push_scope (flags, tmpvars) + int flags; + HASH_TABLE *tmpvars; +{ + return (push_var_context ((char *)NULL, flags, tmpvars)); +} + +static void +push_exported_var (data) + PTR_T data; +{ + SHELL_VAR *var, *v; + + var = (SHELL_VAR *)data; + + /* If a temp var had its export attribute set, or it's marked to be + propagated, bind it in the previous scope before disposing it. */ + /* XXX - This isn't exactly right, because all tempenv variables have the + export attribute set. */ + if (tempvar_p (var) && exported_p (var) && (var->attributes & att_propagate)) + { + var->attributes &= ~att_tempvar; /* XXX */ + v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0, 0); + if (shell_variables == global_variables) + var->attributes &= ~att_propagate; + if (v) + { + v->attributes |= var->attributes; + v->context = shell_variables->scope; + } + } + else + stupidly_hack_special_variables (var->name); /* XXX */ + + dispose_variable (var); +} + +/* This is called to propagate variables in the temporary environment of a + special builtin (if IS_SPECIAL != 0) or exported variables that are the + result of a builtin like `source' or `command' that can operate on the + variables in its temporary environment. In the first case, we call + push_builtin_var, which does the right thing. */ +void +pop_scope (is_special) + int is_special; +{ + VAR_CONTEXT *vcxt, *ret; + int is_bltinenv; + + vcxt = shell_variables; + if (vc_istempscope (vcxt) == 0) + { + internal_error (_("pop_scope: head of shell_variables not a temporary environment scope")); + return; + } + is_bltinenv = vc_isbltnenv (vcxt); /* XXX - for later */ + + ret = vcxt->down; + if (ret) + ret->up = (VAR_CONTEXT *)NULL; + + shell_variables = ret; + + /* Now we can take care of merging variables in VCXT into set of scopes + whose head is RET (shell_variables). */ + FREE (vcxt->name); + if (vcxt->table) + { + if (is_special) + hash_flush (vcxt->table, push_builtin_var); + else + hash_flush (vcxt->table, push_exported_var); + hash_dispose (vcxt->table); + } + free (vcxt); + + sv_ifs ("IFS"); /* XXX here for now */ +} + +/* **************************************************************** */ +/* */ +/* Pushing and Popping function contexts */ +/* */ +/* **************************************************************** */ + +struct saved_dollar_vars { + char **first_ten; + WORD_LIST *rest; +}; + +static struct saved_dollar_vars *dollar_arg_stack = (struct saved_dollar_vars *)NULL; +static int dollar_arg_stack_slots; +static int dollar_arg_stack_index; + +/* Functions to manipulate dollar_vars array. Need to keep these in sync with + whatever remember_args() does. */ +static char ** +save_dollar_vars () +{ + char **ret; + int i; + + ret = strvec_create (10); + for (i = 1; i < 10; i++) + { + ret[i] = dollar_vars[i]; + dollar_vars[i] = (char *)NULL; + } + return ret; +} + +static void +restore_dollar_vars (args) + char **args; +{ + int i; + + for (i = 1; i < 10; i++) + dollar_vars[i] = args[i]; +} + +static void +free_dollar_vars () +{ + int i; + + for (i = 1; i < 10; i++) + { + FREE (dollar_vars[i]); + dollar_vars[i] = (char *)NULL; + } +} + +static void +free_saved_dollar_vars (args) + char **args; +{ + int i; + + for (i = 1; i < 10; i++) + FREE (args[i]); +} + +/* XXX - should always be followed by remember_args () */ +void +push_context (name, is_subshell, tempvars) + char *name; /* function name */ + int is_subshell; + HASH_TABLE *tempvars; +{ + if (is_subshell == 0) + push_dollar_vars (); + variable_context++; + push_var_context (name, VC_FUNCENV, tempvars); +} + +/* Only called when subshell == 0, so we don't need to check, and can + unconditionally pop the dollar vars off the stack. */ +void +pop_context () +{ + pop_dollar_vars (); + variable_context--; + pop_var_context (); + + sv_ifs ("IFS"); /* XXX here for now */ +} + +/* Save the existing positional parameters on a stack. */ +void +push_dollar_vars () +{ + if (dollar_arg_stack_index + 2 > dollar_arg_stack_slots) + { + dollar_arg_stack = (struct saved_dollar_vars *) + xrealloc (dollar_arg_stack, (dollar_arg_stack_slots += 10) + * sizeof (struct saved_dollar_vars)); + } + + dollar_arg_stack[dollar_arg_stack_index].first_ten = save_dollar_vars (); + dollar_arg_stack[dollar_arg_stack_index++].rest = rest_of_args; + rest_of_args = (WORD_LIST *)NULL; + + dollar_arg_stack[dollar_arg_stack_index].first_ten = (char **)NULL; + dollar_arg_stack[dollar_arg_stack_index].rest = (WORD_LIST *)NULL; +} + +/* Restore the positional parameters from our stack. */ +void +pop_dollar_vars () +{ + if (dollar_arg_stack == 0 || dollar_arg_stack_index == 0) + return; + + /* Do what remember_args (xxx, 1) would have done. */ + free_dollar_vars (); + dispose_words (rest_of_args); + + rest_of_args = dollar_arg_stack[--dollar_arg_stack_index].rest; + restore_dollar_vars (dollar_arg_stack[dollar_arg_stack_index].first_ten); + free (dollar_arg_stack[dollar_arg_stack_index].first_ten); + + dollar_arg_stack[dollar_arg_stack_index].first_ten = (char **)NULL; + dollar_arg_stack[dollar_arg_stack_index].rest = (WORD_LIST *)NULL; + + set_dollar_vars_unchanged (); + invalidate_cached_quoted_dollar_at (); +} + +void +dispose_saved_dollar_vars () +{ + if (dollar_arg_stack == 0 || dollar_arg_stack_index == 0) + return; + + dispose_words (dollar_arg_stack[--dollar_arg_stack_index].rest); + free_saved_dollar_vars (dollar_arg_stack[dollar_arg_stack_index].first_ten); + free (dollar_arg_stack[dollar_arg_stack_index].first_ten); + + dollar_arg_stack[dollar_arg_stack_index].first_ten = (char **)NULL; + dollar_arg_stack[dollar_arg_stack_index].rest = (WORD_LIST *)NULL; +} + +/* Initialize BASH_ARGV and BASH_ARGC after turning on extdebug after the + shell is initialized */ +void +init_bash_argv () +{ + if (bash_argv_initialized == 0) + { + save_bash_argv (); + bash_argv_initialized = 1; + } +} + +void +save_bash_argv () +{ + WORD_LIST *list; + + list = list_rest_of_args (); + push_args (list); + dispose_words (list); +} + +/* Manipulate the special BASH_ARGV and BASH_ARGC variables. */ + +void +push_args (list) + WORD_LIST *list; +{ +#if defined (ARRAY_VARS) && defined (DEBUGGER) + SHELL_VAR *bash_argv_v, *bash_argc_v; + ARRAY *bash_argv_a, *bash_argc_a; + WORD_LIST *l; + arrayind_t i; + char *t; + + GET_ARRAY_FROM_VAR ("BASH_ARGV", bash_argv_v, bash_argv_a); + GET_ARRAY_FROM_VAR ("BASH_ARGC", bash_argc_v, bash_argc_a); + + for (l = list, i = 0; l; l = l->next, i++) + array_push (bash_argv_a, l->word->word); + + t = itos (i); + array_push (bash_argc_a, t); + free (t); +#endif /* ARRAY_VARS && DEBUGGER */ +} + +/* Remove arguments from BASH_ARGV array. Pop top element off BASH_ARGC + array and use that value as the count of elements to remove from + BASH_ARGV. */ +void +pop_args () +{ +#if defined (ARRAY_VARS) && defined (DEBUGGER) + SHELL_VAR *bash_argv_v, *bash_argc_v; + ARRAY *bash_argv_a, *bash_argc_a; + ARRAY_ELEMENT *ce; + intmax_t i; + + GET_ARRAY_FROM_VAR ("BASH_ARGV", bash_argv_v, bash_argv_a); + GET_ARRAY_FROM_VAR ("BASH_ARGC", bash_argc_v, bash_argc_a); + + ce = array_shift (bash_argc_a, 1, 0); + if (ce == 0 || legal_number (element_value (ce), &i) == 0) + i = 0; + + for ( ; i > 0; i--) + array_pop (bash_argv_a); + array_dispose_element (ce); +#endif /* ARRAY_VARS && DEBUGGER */ +} + +/************************************************* + * * + * Functions to manage special variables * + * * + *************************************************/ + +/* Extern declarations for variables this code has to manage. */ + +/* An alist of name.function for each special variable. Most of the + functions don't do much, and in fact, this would be faster with a + switch statement, but by the end of this file, I am sick of switch + statements. */ + +#define SET_INT_VAR(name, intvar) intvar = find_variable (name) != 0 + +/* This table will be sorted with qsort() the first time it's accessed. */ +struct name_and_function { + char *name; + sh_sv_func_t *function; +}; + +static struct name_and_function special_vars[] = { + { "BASH_COMPAT", sv_shcompat }, + { "BASH_XTRACEFD", sv_xtracefd }, + +#if defined (JOB_CONTROL) + { "CHILD_MAX", sv_childmax }, +#endif + +#if defined (READLINE) +# if defined (STRICT_POSIX) + { "COLUMNS", sv_winsize }, +# endif + { "COMP_WORDBREAKS", sv_comp_wordbreaks }, +#endif + + { "EXECIGNORE", sv_execignore }, + + { "FUNCNEST", sv_funcnest }, + + { "GLOBIGNORE", sv_globignore }, + +#if defined (HISTORY) + { "HISTCONTROL", sv_history_control }, + { "HISTFILESIZE", sv_histsize }, + { "HISTIGNORE", sv_histignore }, + { "HISTSIZE", sv_histsize }, + { "HISTTIMEFORMAT", sv_histtimefmt }, +#endif + +#if defined (__CYGWIN__) + { "HOME", sv_home }, +#endif + +#if defined (READLINE) + { "HOSTFILE", sv_hostfile }, +#endif + + { "IFS", sv_ifs }, + { "IGNOREEOF", sv_ignoreeof }, + + { "LANG", sv_locale }, + { "LC_ALL", sv_locale }, + { "LC_COLLATE", sv_locale }, + { "LC_CTYPE", sv_locale }, + { "LC_MESSAGES", sv_locale }, + { "LC_NUMERIC", sv_locale }, + { "LC_TIME", sv_locale }, + +#if defined (READLINE) && defined (STRICT_POSIX) + { "LINES", sv_winsize }, +#endif + + { "MAIL", sv_mail }, + { "MAILCHECK", sv_mail }, + { "MAILPATH", sv_mail }, + + { "OPTERR", sv_opterr }, + { "OPTIND", sv_optind }, + + { "PATH", sv_path }, + { "POSIXLY_CORRECT", sv_strict_posix }, + +#if defined (READLINE) + { "TERM", sv_terminal }, + { "TERMCAP", sv_terminal }, + { "TERMINFO", sv_terminal }, +#endif /* READLINE */ + + { "TEXTDOMAIN", sv_locale }, + { "TEXTDOMAINDIR", sv_locale }, + +#if defined (HAVE_TZSET) + { "TZ", sv_tz }, +#endif + +#if defined (HISTORY) && defined (BANG_HISTORY) + { "histchars", sv_histchars }, +#endif /* HISTORY && BANG_HISTORY */ + + { "ignoreeof", sv_ignoreeof }, + + { (char *)0, (sh_sv_func_t *)0 } +}; + +#define N_SPECIAL_VARS (sizeof (special_vars) / sizeof (special_vars[0]) - 1) + +static int +sv_compare (sv1, sv2) + struct name_and_function *sv1, *sv2; +{ + int r; + + if ((r = sv1->name[0] - sv2->name[0]) == 0) + r = strcmp (sv1->name, sv2->name); + return r; +} + +static inline int +find_special_var (name) + const char *name; +{ + register int i, r; + + for (i = 0; special_vars[i].name; i++) + { + r = special_vars[i].name[0] - name[0]; + if (r == 0) + r = strcmp (special_vars[i].name, name); + if (r == 0) + return i; + else if (r > 0) + /* Can't match any of rest of elements in sorted list. Take this out + if it causes problems in certain environments. */ + break; + } + return -1; +} + +/* The variable in NAME has just had its state changed. Check to see if it + is one of the special ones where something special happens. */ +void +stupidly_hack_special_variables (name) + char *name; +{ + static int sv_sorted = 0; + int i; + + if (sv_sorted == 0) /* shouldn't need, but it's fairly cheap. */ + { + qsort (special_vars, N_SPECIAL_VARS, sizeof (special_vars[0]), + (QSFUNC *)sv_compare); + sv_sorted = 1; + } + + i = find_special_var (name); + if (i != -1) + (*(special_vars[i].function)) (name); +} + +/* Special variables that need hooks to be run when they are unset as part + of shell reinitialization should have their sv_ functions run here. */ +void +reinit_special_variables () +{ +#if defined (READLINE) + sv_comp_wordbreaks ("COMP_WORDBREAKS"); +#endif + sv_globignore ("GLOBIGNORE"); + sv_opterr ("OPTERR"); +} + +void +sv_ifs (name) + char *name; +{ + SHELL_VAR *v; + + v = find_variable ("IFS"); + setifs (v); +} + +/* What to do just after the PATH variable has changed. */ +void +sv_path (name) + char *name; +{ + /* hash -r */ + phash_flush (); +} + +/* What to do just after one of the MAILxxxx variables has changed. NAME + is the name of the variable. This is called with NAME set to one of + MAIL, MAILCHECK, or MAILPATH. */ +void +sv_mail (name) + char *name; +{ + /* If the time interval for checking the files has changed, then + reset the mail timer. Otherwise, one of the pathname vars + to the users mailbox has changed, so rebuild the array of + filenames. */ + if (name[4] == 'C') /* if (strcmp (name, "MAILCHECK") == 0) */ + reset_mail_timer (); + else + { + free_mail_files (); + remember_mail_dates (); + } +} + +void +sv_funcnest (name) + char *name; +{ + SHELL_VAR *v; + intmax_t num; + + v = find_variable (name); + if (v == 0) + funcnest_max = 0; + else if (legal_number (value_cell (v), &num) == 0) + funcnest_max = 0; + else + funcnest_max = num; +} + +/* What to do when EXECIGNORE changes. */ +void +sv_execignore (name) + char *name; +{ + setup_exec_ignore (name); +} + +/* What to do when GLOBIGNORE changes. */ +void +sv_globignore (name) + char *name; +{ + if (privileged_mode == 0) + setup_glob_ignore (name); +} + +#if defined (READLINE) +void +sv_comp_wordbreaks (name) + char *name; +{ + SHELL_VAR *sv; + + sv = find_variable (name); + if (sv == 0) + reset_completer_word_break_chars (); +} + +/* What to do just after one of the TERMxxx variables has changed. + If we are an interactive shell, then try to reset the terminal + information in readline. */ +void +sv_terminal (name) + char *name; +{ + if (interactive_shell && no_line_editing == 0) + rl_reset_terminal (get_string_value ("TERM")); +} + +void +sv_hostfile (name) + char *name; +{ + SHELL_VAR *v; + + v = find_variable (name); + if (v == 0) + clear_hostname_list (); + else + hostname_list_initialized = 0; +} + +#if defined (STRICT_POSIX) +/* In strict posix mode, we allow assignments to LINES and COLUMNS (and values + found in the initial environment) to override the terminal size reported by + the kernel. */ +void +sv_winsize (name) + char *name; +{ + SHELL_VAR *v; + intmax_t xd; + int d; + + if (posixly_correct == 0 || interactive_shell == 0 || no_line_editing) + return; + + v = find_variable (name); + if (v == 0 || var_isset (v) == 0) + rl_reset_screen_size (); + else + { + if (legal_number (value_cell (v), &xd) == 0) + return; + winsize_assignment = 1; + d = xd; /* truncate */ + if (name[0] == 'L') /* LINES */ + rl_set_screen_size (d, -1); + else /* COLUMNS */ + rl_set_screen_size (-1, d); + winsize_assignment = 0; + } +} +#endif /* STRICT_POSIX */ +#endif /* READLINE */ + +/* Update the value of HOME in the export environment so tilde expansion will + work on cygwin. */ +#if defined (__CYGWIN__) +sv_home (name) + char *name; +{ + array_needs_making = 1; + maybe_make_export_env (); +} +#endif + +#if defined (HISTORY) +/* What to do after the HISTSIZE or HISTFILESIZE variables change. + If there is a value for this HISTSIZE (and it is numeric), then stifle + the history. Otherwise, if there is NO value for this variable, + unstifle the history. If name is HISTFILESIZE, and its value is + numeric, truncate the history file to hold no more than that many + lines. */ +void +sv_histsize (name) + char *name; +{ + char *temp; + intmax_t num; + int hmax; + + temp = get_string_value (name); + + if (temp && *temp) + { + if (legal_number (temp, &num)) + { + hmax = num; + if (hmax < 0 && name[4] == 'S') + unstifle_history (); /* unstifle history if HISTSIZE < 0 */ + else if (name[4] == 'S') + { + stifle_history (hmax); + hmax = where_history (); + if (history_lines_this_session > hmax) + history_lines_this_session = hmax; + } + else if (hmax >= 0) /* truncate HISTFILE if HISTFILESIZE >= 0 */ + { + history_truncate_file (get_string_value ("HISTFILE"), hmax); + /* If we just shrank the history file to fewer lines than we've + already read, make sure we adjust our idea of how many lines + we have read from the file. */ + if (hmax < history_lines_in_file) + history_lines_in_file = hmax; + } + } + } + else if (name[4] == 'S') + unstifle_history (); +} + +/* What to do after the HISTIGNORE variable changes. */ +void +sv_histignore (name) + char *name; +{ + setup_history_ignore (name); +} + +/* What to do after the HISTCONTROL variable changes. */ +void +sv_history_control (name) + char *name; +{ + char *temp; + char *val; + int tptr; + + history_control = 0; + temp = get_string_value (name); + + if (temp == 0 || *temp == 0) + return; + + tptr = 0; + while (val = extract_colon_unit (temp, &tptr)) + { + if (STREQ (val, "ignorespace")) + history_control |= HC_IGNSPACE; + else if (STREQ (val, "ignoredups")) + history_control |= HC_IGNDUPS; + else if (STREQ (val, "ignoreboth")) + history_control |= HC_IGNBOTH; + else if (STREQ (val, "erasedups")) + history_control |= HC_ERASEDUPS; + + free (val); + } +} + +#if defined (BANG_HISTORY) +/* Setting/unsetting of the history expansion character. */ +void +sv_histchars (name) + char *name; +{ + char *temp; + + temp = get_string_value (name); + if (temp) + { + history_expansion_char = *temp; + if (temp[0] && temp[1]) + { + history_subst_char = temp[1]; + if (temp[2]) + history_comment_char = temp[2]; + } + } + else + { + history_expansion_char = '!'; + history_subst_char = '^'; + history_comment_char = '#'; + } +} +#endif /* BANG_HISTORY */ + +void +sv_histtimefmt (name) + char *name; +{ + SHELL_VAR *v; + + if (v = find_variable (name)) + { + if (history_comment_char == 0) + history_comment_char = '#'; + } + history_write_timestamps = (v != 0); +} +#endif /* HISTORY */ + +#if defined (HAVE_TZSET) +void +sv_tz (name) + char *name; +{ + if (chkexport (name)) + tzset (); +} +#endif + +/* If the variable exists, then the value of it can be the number + of times we actually ignore the EOF. The default is small, + (smaller than csh, anyway). */ +void +sv_ignoreeof (name) + char *name; +{ + SHELL_VAR *tmp_var; + char *temp; + + eof_encountered = 0; + + tmp_var = find_variable (name); + ignoreeof = tmp_var && var_isset (tmp_var); + temp = tmp_var ? value_cell (tmp_var) : (char *)NULL; + if (temp) + eof_encountered_limit = (*temp && all_digits (temp)) ? atoi (temp) : 10; + set_shellopts (); /* make sure `ignoreeof' is/is not in $SHELLOPTS */ +} + +void +sv_optind (name) + char *name; +{ + SHELL_VAR *var; + char *tt; + int s; + + var = find_variable ("OPTIND"); + tt = var ? get_variable_value (var) : (char *)NULL; + + /* Assume that if var->context < variable_context and variable_context > 0 + then we are restoring the variables's previous state while returning + from a function. */ + if (tt && *tt) + { + s = atoi (tt); + + /* According to POSIX, setting OPTIND=1 resets the internal state + of getopt (). */ + if (s < 0 || s == 1) + s = 0; + } + else + s = 0; + getopts_reset (s); +} + +void +sv_opterr (name) + char *name; +{ + char *tt; + + tt = get_string_value ("OPTERR"); + sh_opterr = (tt && *tt) ? atoi (tt) : 1; +} + +void +sv_strict_posix (name) + char *name; +{ + SHELL_VAR *var; + + var = find_variable (name); + posixly_correct = var && var_isset (var); + posix_initialize (posixly_correct); +#if defined (READLINE) + if (interactive_shell) + posix_readline_initialize (posixly_correct); +#endif /* READLINE */ + set_shellopts (); /* make sure `posix' is/is not in $SHELLOPTS */ +} + +void +sv_locale (name) + char *name; +{ + char *v; + int r; + + v = get_string_value (name); + if (name[0] == 'L' && name[1] == 'A') /* LANG */ + r = set_lang (name, v); + else + r = set_locale_var (name, v); /* LC_*, TEXTDOMAIN* */ + +#if 1 + if (r == 0 && posixly_correct) + last_command_exit_value = 1; +#endif +} + +#if defined (ARRAY_VARS) +void +set_pipestatus_array (ps, nproc) + int *ps; + int nproc; +{ + SHELL_VAR *v; + ARRAY *a; + ARRAY_ELEMENT *ae; + register int i; + char *t, tbuf[INT_STRLEN_BOUND(int) + 1]; + + v = find_variable ("PIPESTATUS"); + if (v == 0) + v = make_new_array_variable ("PIPESTATUS"); + if (array_p (v) == 0) + return; /* Do nothing if not an array variable. */ + a = array_cell (v); + + if (a == 0 || array_num_elements (a) == 0) + { + for (i = 0; i < nproc; i++) /* was ps[i] != -1, not i < nproc */ + { + t = inttostr (ps[i], tbuf, sizeof (tbuf)); + array_insert (a, i, t); + } + return; + } + + /* Fast case */ + if (array_num_elements (a) == nproc && nproc == 1) + { + ae = element_forw (a->head); + free (element_value (ae)); + set_element_value (ae, itos (ps[0])); + } + else if (array_num_elements (a) <= nproc) + { + /* modify in array_num_elements members in place, then add */ + ae = a->head; + for (i = 0; i < array_num_elements (a); i++) + { + ae = element_forw (ae); + free (element_value (ae)); + set_element_value (ae, itos (ps[i])); + } + /* add any more */ + for ( ; i < nproc; i++) + { + t = inttostr (ps[i], tbuf, sizeof (tbuf)); + array_insert (a, i, t); + } + } + else + { + /* deleting elements. it's faster to rebuild the array. */ + array_flush (a); + for (i = 0; ps[i] != -1; i++) + { + t = inttostr (ps[i], tbuf, sizeof (tbuf)); + array_insert (a, i, t); + } + } +} + +ARRAY * +save_pipestatus_array () +{ + SHELL_VAR *v; + ARRAY *a; + + v = find_variable ("PIPESTATUS"); + if (v == 0 || array_p (v) == 0 || array_cell (v) == 0) + return ((ARRAY *)NULL); + + a = array_copy (array_cell (v)); + + return a; +} + +void +restore_pipestatus_array (a) + ARRAY *a; +{ + SHELL_VAR *v; + ARRAY *a2; + + v = find_variable ("PIPESTATUS"); + /* XXX - should we still assign even if existing value is NULL? */ + if (v == 0 || array_p (v) == 0 || array_cell (v) == 0) + return; + + a2 = array_cell (v); + var_setarray (v, a); + + array_dispose (a2); +} +#endif + +void +set_pipestatus_from_exit (s) + int s; +{ +#if defined (ARRAY_VARS) + static int v[2] = { 0, -1 }; + + v[0] = s; + set_pipestatus_array (v, 1); +#endif +} + +void +sv_xtracefd (name) + char *name; +{ + SHELL_VAR *v; + char *t, *e; + int fd; + FILE *fp; + + v = find_variable (name); + if (v == 0) + { + xtrace_reset (); + return; + } + + t = value_cell (v); + if (t == 0 || *t == 0) + xtrace_reset (); + else + { + fd = (int)strtol (t, &e, 10); + if (e != t && *e == '\0' && sh_validfd (fd)) + { + fp = fdopen (fd, "w"); + if (fp == 0) + internal_error (_("%s: %s: cannot open as FILE"), name, value_cell (v)); + else + xtrace_set (fd, fp); + } + else + internal_error (_("%s: %s: invalid value for trace file descriptor"), name, value_cell (v)); + } +} + +#define MIN_COMPAT_LEVEL 31 + +void +sv_shcompat (name) + char *name; +{ + SHELL_VAR *v; + char *val; + int tens, ones, compatval; + + v = find_variable (name); + if (v == 0) + { + shell_compatibility_level = DEFAULT_COMPAT_LEVEL; + set_compatibility_opts (); + return; + } + val = value_cell (v); + if (val == 0 || *val == '\0') + { + shell_compatibility_level = DEFAULT_COMPAT_LEVEL; + set_compatibility_opts (); + return; + } + /* Handle decimal-like compatibility version specifications: 4.2 */ + if (ISDIGIT (val[0]) && val[1] == '.' && ISDIGIT (val[2]) && val[3] == 0) + { + tens = val[0] - '0'; + ones = val[2] - '0'; + compatval = tens*10 + ones; + } + /* Handle integer-like compatibility version specifications: 42 */ + else if (ISDIGIT (val[0]) && ISDIGIT (val[1]) && val[2] == 0) + { + tens = val[0] - '0'; + ones = val[1] - '0'; + compatval = tens*10 + ones; + } + else + { +compat_error: + internal_error (_("%s: %s: compatibility value out of range"), name, val); + shell_compatibility_level = DEFAULT_COMPAT_LEVEL; + set_compatibility_opts (); + return; + } + + if (compatval < MIN_COMPAT_LEVEL || compatval > DEFAULT_COMPAT_LEVEL) + goto compat_error; + + shell_compatibility_level = compatval; + set_compatibility_opts (); +} + +#if defined (JOB_CONTROL) +void +sv_childmax (name) + char *name; +{ + char *tt; + int s; + + tt = get_string_value (name); + s = (tt && *tt) ? atoi (tt) : 0; + set_maxchild (s); +} +#endif |