4810 lines
		
	
	
	
		
			116 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			4810 lines
		
	
	
	
		
			116 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* variables.c -- Functions for hacking shell variables. */
 | |
| 
 | |
| /* Copyright (C) 1987-2010 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 "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 "builtins/getopt.h"
 | |
| #include "builtins/common.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 TEMPENV_HASH_BUCKETS	4	/* must be power of two */
 | |
| 
 | |
| #define ifsname(s)	((s)[0] == 'I' && (s)[1] == 'F' && (s)[2] == 'S' && (s)[3] == '\0')
 | |
| 
 | |
| extern char **environ;
 | |
| 
 | |
| /* Variables used here and defined in other files. */
 | |
| extern int posixly_correct;
 | |
| extern int line_number, line_number_base;
 | |
| extern int subshell_environment, indirection_level, subshell_level;
 | |
| extern int build_version, patch_level;
 | |
| extern int expanding_redir;
 | |
| extern char *dist_version, *release_status;
 | |
| extern char *shell_name;
 | |
| extern char *primary_prompt, *secondary_prompt;
 | |
| extern char *current_host_name;
 | |
| extern sh_builtin_func_t *this_shell_builtin;
 | |
| extern SHELL_VAR *this_shell_function;
 | |
| extern char *the_printed_command_except_trap;
 | |
| extern char *this_command_name;
 | |
| extern char *command_execution_string;
 | |
| extern time_t shell_start_time;
 | |
| extern int assigning_in_environment;
 | |
| extern int executing_builtin;
 | |
| extern int funcnest_max;
 | |
| 
 | |
| #if defined (READLINE)
 | |
| extern int no_line_editing;
 | |
| extern int perform_hostname_completion;
 | |
| #endif
 | |
| 
 | |
| /* 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;
 | |
| 
 | |
| #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;
 | |
| 
 | |
| /* 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
 | |
| 
 | |
| /* 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_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 *));
 | |
| 
 | |
| 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 *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 *bind_tempenv_variable __P((const char *, char *));
 | |
| 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 *));
 | |
| 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_exported_var __P((PTR_T));
 | |
| 
 | |
| 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 (0);
 | |
|     }
 | |
| 
 | |
|   if (shell_functions == 0)
 | |
|     shell_functions = hash_create (0);
 | |
| 
 | |
| #if defined (DEBUGGER)
 | |
|   if (shell_function_defs == 0)
 | |
|     shell_function_defs = hash_create (0);
 | |
| #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;
 | |
|   SHELL_VAR *temp_var;
 | |
| 
 | |
|   create_variable_tables ();
 | |
| 
 | |
|   for (string_index = 0; 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 exported function, define it now.  Don't import functions from
 | |
| 	 the environment in privileged mode. */
 | |
|       if (privmode == 0 && read_but_dont_execute == 0 && STREQN ("() {", string, 4))
 | |
| 	{
 | |
| 	  string_length = strlen (string);
 | |
| 	  temp_string = (char *)xmalloc (3 + string_length + char_index);
 | |
| 
 | |
| 	  strcpy (temp_string, name);
 | |
| 	  temp_string[char_index] = ' ';
 | |
| 	  strcpy (temp_string + char_index + 1, string);
 | |
| 
 | |
| 	  parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST);
 | |
| 
 | |
| 	  /* Ancient backwards compatibility.  Old versions of bash exported
 | |
| 	     functions like name()=() {...} */
 | |
| 	  if (name[char_index - 1] == ')' && name[char_index - 2] == '(')
 | |
| 	    name[char_index - 2] = '\0';
 | |
| 
 | |
| 	  if (temp_var = find_function (name))
 | |
| 	    {
 | |
| 	      VSETATTR (temp_var, (att_exported|att_imported));
 | |
| 	      array_needs_making = 1;
 | |
| 	    }
 | |
| 	  else
 | |
| 	    report_error (_("error importing function definition for `%s'"), name);
 | |
| 
 | |
| 	  /* ( */
 | |
| 	  if (name[char_index - 1] == ')' && name[char_index - 2] == '\0')
 | |
| 	    name[char_index - 2] = '(';		/* ) */
 | |
| 	}
 | |
| #if defined (ARRAY_VARS)
 | |
| #  if 0
 | |
|       /* Array variables may not yet be exported. */
 | |
|       else 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);
 | |
| 	  FREE (temp_string);
 | |
| 	  VSETATTR (temp_var, (att_exported | att_imported));
 | |
| 	  array_needs_making = 1;
 | |
| 	}
 | |
| #  endif
 | |
| #endif
 | |
| #if 0
 | |
|       else if (legal_identifier (name))
 | |
| #else
 | |
|       else
 | |
| #endif
 | |
| 	{
 | |
| 	  temp_var = bind_variable (name, string, 0);
 | |
| 	  if (temp_var)
 | |
| 	    {
 | |
| 	      if (legal_identifier (name))
 | |
| 		VSETATTR (temp_var, (att_exported | att_imported));
 | |
| 	      else
 | |
| 		VSETATTR (temp_var, (att_exported | att_imported | att_invisible));
 | |
| 	      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);
 | |
| #if 0
 | |
|   set_auto_export (temp_var);	/* XXX */
 | |
| #endif
 | |
| 
 | |
|   temp_var = set_if_not ("TERM", "dumb");
 | |
| #if 0
 | |
|   set_auto_export (temp_var);	/* XXX */
 | |
| #endif
 | |
| 
 | |
| #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);
 | |
|     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);
 | |
|     }
 | |
|   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);
 | |
| 
 | |
| #if 0
 | |
|       set_if_not ("HISTSIZE", "500");
 | |
|       sv_histsize ("HISTSIZE");
 | |
| #endif
 | |
|     }
 | |
| #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 only be 1 here if the shell was compiled
 | |
|      -DSTRICT_POSIX */
 | |
|   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);
 | |
| 
 | |
|   /* 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;
 | |
| 
 | |
|   home_var = find_variable ("HOME");
 | |
|   home_string = home_var ? value_cell (home_var) : (char *)NULL;
 | |
| 
 | |
|   temp_var = find_variable ("PWD");
 | |
|   if (temp_var && imported_p (temp_var) &&
 | |
|       (temp_string = value_cell (temp_var)) &&
 | |
|       same_file (temp_string, ".", (struct stat *)NULL, (struct stat *)NULL))
 | |
|     set_working_directory (temp_string);
 | |
|   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.
 | |
|      Make a dummy invisible variable for OLDPWD, and mark it as exported. */
 | |
|   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 ("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.  Intialize 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 unsigned long rseed = 1;
 | |
| static int last_random_value;
 | |
| static int seeded_subshell = 0;
 | |
| 
 | |
| /* A linear congruential random number generator based on the example
 | |
|    one in the ANSI C standard.  This one isn't very good, but a more
 | |
|    complicated one is overkill. */
 | |
| 
 | |
| /* Returns a pseudo-random number between 0 and 32767. */
 | |
| static int
 | |
| brand ()
 | |
| {
 | |
|   /* 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 */
 | |
|   long h, l;
 | |
| 
 | |
|   /* Can't seed with 0. */
 | |
|   if (rseed == 0)
 | |
|     rseed = 123459876;
 | |
|   h = rseed / 127773;
 | |
|   l = rseed % 127773;
 | |
|   rseed = 16807 * l - 2836 * h;
 | |
| #if 0
 | |
|   if (rseed < 0)
 | |
|     rseed += 0x7fffffff;
 | |
| #endif
 | |
|   return ((unsigned int)(rseed & 32767));	/* was % 32768 */
 | |
| }
 | |
| 
 | |
| /* 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;
 | |
| 
 | |
|   gettimeofday (&tv, NULL);
 | |
|   sbrand (tv.tv_sec ^ tv.tv_usec ^ getpid ());
 | |
| }
 | |
| 
 | |
| 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_bashpid (var)
 | |
|      SHELL_VAR *var;
 | |
| {
 | |
|   int pid;
 | |
|   char *p;
 | |
| 
 | |
|   pid = getpid ();
 | |
|   p = itos (pid);
 | |
| 
 | |
|   FREE (value_cell (var));
 | |
|   VSETATTR (var, att_integer|att_readonly);
 | |
|   var_setvalue (var, p);
 | |
|   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);
 | |
| }
 | |
| 
 | |
| 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;
 | |
| {
 | |
|   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;
 | |
| {
 | |
|   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_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);
 | |
| 
 | |
|   INIT_DYNAMIC_VAR ("BASHPID", (char *)NULL, get_bashpid, null_assign);
 | |
|   VSETATTR (v, att_integer|att_readonly);
 | |
| 
 | |
| #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_self, null_array_assign, att_noassign|att_nounset);
 | |
|   v = init_dynamic_array_var ("BASH_ARGV", get_self, 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);
 | |
|   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, force_tempenv)
 | |
|      const char *name;
 | |
|      int force_tempenv;
 | |
| {
 | |
|   SHELL_VAR *var;
 | |
|   int search_tempenv;
 | |
| 
 | |
|   var = (SHELL_VAR *)NULL;
 | |
| 
 | |
|   /* If explicitly requested, first look in the temporary environment for
 | |
|      the variable.  This allows constructs such as "foo=x eval 'echo $foo'"
 | |
|      to get the `exported' value of $foo.  This happens if we are executing
 | |
|      a function or builtin, or if we are looking up a variable in a
 | |
|      "subshell environment". */
 | |
|   search_tempenv = force_tempenv || (expanding_redir == 0 && subshell_environment);
 | |
| 
 | |
|   if (search_tempenv && temporary_env)		
 | |
|     var = hash_lookup (name, temporary_env);
 | |
| 
 | |
|   if (var == 0)
 | |
|     var = var_lookup (name, shell_variables);
 | |
| 
 | |
|   if (var == 0)
 | |
|     return ((SHELL_VAR *)NULL);
 | |
| 
 | |
|   return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var);
 | |
| }
 | |
| 
 | |
| SHELL_VAR *
 | |
| find_global_variable (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);
 | |
| }
 | |
| 
 | |
| /* Look up the variable entry named NAME.  Returns the entry or NULL. */
 | |
| SHELL_VAR *
 | |
| find_variable (name)
 | |
|      const char *name;
 | |
| {
 | |
|   return (find_variable_internal (name, (expanding_redir == 0 && (assigning_in_environment || executing_builtin))));
 | |
| }
 | |
| 
 | |
| /* 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.  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		    */
 | |
| /*								    */
 | |
| /* **************************************************************** */
 | |
| 
 | |
| /* 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)
 | |
|      const char *name;
 | |
| {
 | |
|   SHELL_VAR *new_var, *old_var;
 | |
|   VAR_CONTEXT *vc;
 | |
|   int was_tmpvar;
 | |
|   char *tmp_value;
 | |
| 
 | |
|   /* local foo; local foo;  is a no-op. */
 | |
|   old_var = find_variable (name);
 | |
|   if (old_var && local_p (old_var) && old_var->context == variable_context)
 | |
|     {
 | |
|       VUNSETATTR (old_var, att_invisible);
 | |
|       return (old_var);
 | |
|     }
 | |
| 
 | |
|   was_tmpvar = old_var && tempvar_p (old_var);
 | |
|   if (was_tmpvar)
 | |
|     tmp_value = value_cell (old_var);
 | |
| 
 | |
|   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);
 | |
|       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'. */
 | |
|       if (was_tmpvar)
 | |
| 	var_setvalue (new_var, savestring (tmp_value));
 | |
| 
 | |
|       new_var->attributes = exported_p (old_var) ? att_exported : 0;
 | |
|     }
 | |
| 
 | |
|   vc->flags |= VC_HASLOCAL;
 | |
| 
 | |
|   new_var->context = variable_context;
 | |
|   VSETATTR (new_var, att_local);
 | |
| 
 | |
|   if (ifsname (name))
 | |
|     setifs (new_var);
 | |
| 
 | |
|   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 responsibilty 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)
 | |
|      char *name;
 | |
| {
 | |
|   SHELL_VAR *var;
 | |
|   ARRAY *array;
 | |
| 
 | |
|   var = make_local_variable (name);
 | |
|   if (var == 0 || array_p (var))
 | |
|     return var;
 | |
| 
 | |
|   array = array_create ();
 | |
| 
 | |
|   dispose_variable_value (var);
 | |
|   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)
 | |
|      char *name;
 | |
| {
 | |
|   SHELL_VAR *var;
 | |
|   HASH_TABLE *hash;
 | |
| 
 | |
|   var = make_local_variable (name);
 | |
|   if (var == 0 || assoc_p (var))
 | |
|     return var;
 | |
| 
 | |
|   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. */
 | |
|   if (integer_p (var))
 | |
|     {
 | |
|       if (flags & ASS_APPEND)
 | |
| 	{
 | |
| 	  oval = value_cell (var);
 | |
| 	  lval = evalexp (oval, &expok);	/* ksh93 seems to do this */
 | |
| 	  if (expok == 0)
 | |
| 	    {
 | |
| 	      top_level_cleanup ();
 | |
| 	      jump_to_top_level (DISCARD);
 | |
| 	    }
 | |
| 	}
 | |
|       rval = evalexp (value, &expok);
 | |
|       if (expok == 0)
 | |
| 	{
 | |
| 	  top_level_cleanup ();
 | |
| 	  jump_to_top_level (DISCARD);
 | |
| 	}
 | |
|       if (flags & ASS_APPEND)
 | |
| 	rval += lval;
 | |
|       retval = itos (rval);
 | |
|     }
 | |
| #if defined (CASEMOD_ATTRS)
 | |
|   else if (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)
 | |
|     {
 | |
|       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;
 | |
| }
 | |
| 
 | |
| /* Bind a variable NAME to VALUE in the HASH_TABLE TABLE, which may be the
 | |
|    temporary environment (but usually is not). */
 | |
| static SHELL_VAR *
 | |
| bind_variable_internal (name, value, table, hflags, aflags)
 | |
|      const char *name;
 | |
|      char *value;
 | |
|      HASH_TABLE *table;
 | |
|      int hflags, aflags;
 | |
| {
 | |
|   char *newval;
 | |
|   SHELL_VAR *entry;
 | |
| 
 | |
|   entry = (hflags & HASH_NOSRCH) ? (SHELL_VAR *)NULL : hash_lookup (name, table);
 | |
| 
 | |
|   if (entry == 0)
 | |
|     {
 | |
|       entry = make_new_variable (name, table);
 | |
|       var_setvalue (entry, make_variable_value (entry, value, 0)); /* 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
 | |
|     {
 | |
|       if (readonly_p (entry) || noassign_p (entry))
 | |
| 	{
 | |
| 	  if (readonly_p (entry))
 | |
| 	    err_readonly (name);
 | |
| 	  return (entry);
 | |
| 	}
 | |
| 
 | |
|       /* Variables which are bound are visible. */
 | |
|       VUNSETATTR (entry, att_invisible);
 | |
| 
 | |
|       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 (array_p (entry))
 | |
| 	{
 | |
| 	  array_insert (array_cell (entry), 0, newval);
 | |
| 	  free (newval);
 | |
| 	}
 | |
|       else if (assoc_p (entry))
 | |
| 	{
 | |
| 	  assoc_insert (assoc_cell (entry), savestring ("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;
 | |
|   VAR_CONTEXT *vc;
 | |
| 
 | |
|   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)
 | |
|     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);
 | |
| 	  if (v)
 | |
| 	    return (bind_variable_internal (name, value, vc->table, 0, flags));
 | |
| 	}
 | |
|     }
 | |
|   return (bind_variable_internal (name, value, global_variables->table, 0, 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;
 | |
| 
 | |
|   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);
 | |
|       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)
 | |
|      char *lhs, *rhs;
 | |
| {
 | |
|   register SHELL_VAR *v;
 | |
|   int isint, isarr;
 | |
| 
 | |
|   isint = isarr = 0;
 | |
| #if defined (ARRAY_VARS)
 | |
|   if (valid_array_reference (lhs))
 | |
|     {
 | |
|       isarr = 1;
 | |
|       v = array_variable_part (lhs, (char **)0, (int *)0);
 | |
|     }
 | |
|   else
 | |
| #endif
 | |
|     v = find_variable (lhs);
 | |
| 
 | |
|   if (v)
 | |
|     {
 | |
|       isint = integer_p (v);
 | |
|       VUNSETATTR (v, att_integer);
 | |
|     }
 | |
| 
 | |
| #if defined (ARRAY_VARS)
 | |
|   if (isarr)
 | |
|     v = assign_array_element (lhs, rhs, 0);
 | |
|   else
 | |
| #endif
 | |
|     v = bind_variable (lhs, rhs, 0);
 | |
| 
 | |
|   if (v && isint)
 | |
|     VSETATTR (v, att_integer);
 | |
| 
 | |
|   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));
 | |
| }
 | |
| 
 | |
| /* 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.*/
 | |
| void
 | |
| bind_function_def (name, value)
 | |
|      const char *name;
 | |
|      FUNCTION_DEF *value;
 | |
| {
 | |
|   FUNCTION_DEF *entry;
 | |
|   BUCKET_CONTENTS *elt;
 | |
|   COMMAND *cmd;
 | |
| 
 | |
|   entry = find_function_def (name);
 | |
|   if (entry)
 | |
|     {
 | |
|       dispose_function_def_contents (entry);
 | |
|       entry = copy_function_def_contents (value, entry);
 | |
|     }
 | |
|   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;
 | |
|   char *name, *temp, *value;
 | |
|   SHELL_VAR *var;
 | |
|   const char *string;
 | |
| 
 | |
|   string = word->word;
 | |
| 
 | |
|   offset = assignment (string, 0);
 | |
|   name = savestring (string);
 | |
|   value = (char *)NULL;
 | |
| 
 | |
|   if (name[offset] == '=')
 | |
|     {
 | |
|       name[offset] = 0;
 | |
| 
 | |
|       /* ignore the `+' when assigning temporary environment */
 | |
|       if (name[offset - 1] == '+')
 | |
| 	name[offset - 1] = '\0';
 | |
| 
 | |
|       var = find_variable (name);
 | |
|       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 (temporary_env == 0)
 | |
|     temporary_env = hash_create (TEMPENV_HASH_BUCKETS);
 | |
| 
 | |
|   var = hash_lookup (name, temporary_env);
 | |
|   if (var == 0)
 | |
|     var = make_new_variable (name, temporary_env);
 | |
|   else
 | |
|     FREE (value_cell (var));
 | |
| 
 | |
|   if (value == 0)
 | |
|     {
 | |
|       value = (char *)xmalloc (1);	/* like do_assignment_internal */
 | |
|       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 (name, value);
 | |
| 
 | |
|   array_needs_making = 1;
 | |
| 
 | |
| #if 0
 | |
|   if (ifsname (name))
 | |
|     setifs (var);
 | |
| else
 | |
| #endif
 | |
|   if (flags)
 | |
|     stupidly_hack_special_variables (name);
 | |
| 
 | |
|   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 (value_cell (var))
 | |
| 	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
 | |
|     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. */
 | |
| int
 | |
| unbind_variable (name)
 | |
|      const char *name;
 | |
| {
 | |
|   return makunbound (name, shell_variables);
 | |
| }
 | |
| 
 | |
| /* 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 */
 | |
| 
 | |
| /* 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) && variable_context == old_var->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
 | |
| 	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));
 | |
| }
 | |
| 
 | |
| 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;
 | |
| 
 | |
| /* Push the variable described by (SHELL_VAR *)DATA down to the next
 | |
|    variable context from the temporary environment. */
 | |
| 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 (0);
 | |
|       else
 | |
| 	binding_table = shell_variables->table = hash_create (TEMPENV_HASH_BUCKETS);
 | |
|     }
 | |
| 
 | |
|   v = bind_variable_internal (var->name, value_cell (var), binding_table, 0, 0);
 | |
| 
 | |
|   /* XXX - should we set the context here?  It shouldn't matter because of how
 | |
|      assign_in_env works, but might want to check. */
 | |
|   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;
 | |
|     }
 | |
|   v->attributes |= var->attributes;
 | |
| 
 | |
|   if (find_special_var (var->name) >= 0)
 | |
|     tempvar_list[tvlist_ind++] = savestring (var->name);
 | |
| 
 | |
|   dispose_variable (var);
 | |
| }
 | |
| 
 | |
| 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;
 | |
| 
 | |
| #if 0
 | |
|   sv_ifs ("IFS");		/* XXX here for now -- check setifs in assign_in_env */  
 | |
| #endif
 | |
|   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. */
 | |
| void
 | |
| merge_temporary_env ()
 | |
| {
 | |
|   if (temporary_env)
 | |
|     dispose_temporary_env (push_temp_var);
 | |
| }
 | |
| 
 | |
| /* **************************************************************** */
 | |
| /*								    */
 | |
| /*	     Creating and manipulating the environment		    */
 | |
| /*								    */
 | |
| /* **************************************************************** */
 | |
| 
 | |
| static inline char *
 | |
| mk_env_string (name, value)
 | |
|      const char *name, *value;
 | |
| {
 | |
|   int name_len, value_len;
 | |
|   char	*p;
 | |
| 
 | |
|   name_len = strlen (name);
 | |
|   value_len = STRLEN (value);
 | |
|   p = (char *)xmalloc (2 + name_len + value_len);
 | |
|   strcpy (p, name);
 | |
|   p[name_len] = '=';
 | |
|   if (value && *value)
 | |
|     strcpy (p + name_len + 1, value);
 | |
|   else
 | |
|     p[name_len + 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 (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 0
 | |
| 	value = array_to_assignment_string (array_cell (var));
 | |
| #  else
 | |
| 	continue;	/* XXX array vars cannot yet be exported */
 | |
| #  endif
 | |
|       else if (assoc_p (var))
 | |
| #  if 0
 | |
| 	value = assoc_to_assignment_string (assoc_cell (var));
 | |
| #  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);
 | |
| 
 | |
| 	  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;
 | |
| 
 | |
|   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);
 | |
|       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;
 | |
|       
 | |
|       temp_array = make_var_export_array (tcxt);
 | |
|       if (temp_array)
 | |
| 	add_temp_array_to_env (temp_array, 0, 0);
 | |
| 
 | |
|       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);
 | |
| }
 | |
| 
 | |
| #if 0	/* UNUSED -- it caused too many problems */
 | |
| void
 | |
| put_gnu_argv_flags_into_env (pid, flags_string)
 | |
|      intmax_t pid;
 | |
|      char *flags_string;
 | |
| {
 | |
|   char *dummy, *pbuf;
 | |
|   int l, fl;
 | |
| 
 | |
|   pbuf = itos (pid);
 | |
|   l = strlen (pbuf);
 | |
| 
 | |
|   fl = strlen (flags_string);
 | |
| 
 | |
|   dummy = (char *)xmalloc (l + fl + 30);
 | |
|   dummy[0] = '_';
 | |
|   strcpy (dummy + 1, pbuf);
 | |
|   strcpy (dummy + 1 + l, "_GNU_nonoption_argv_flags_");
 | |
|   dummy[l + 27] = '=';
 | |
|   strcpy (dummy + l + 28, flags_string);
 | |
| 
 | |
|   free (pbuf);
 | |
| 
 | |
|   export_env = add_or_supercede_exported_var (dummy, 0);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| /* **************************************************************** */
 | |
| /*								    */
 | |
| /*		      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;
 | |
| 
 | |
|   vc = new_var_context (name, flags);
 | |
|   vc->table = tempvars;
 | |
|   if (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);
 | |
| }
 | |
| 
 | |
| static void
 | |
| push_func_var (data)
 | |
|      PTR_T data;
 | |
| {
 | |
|   SHELL_VAR *var, *v;
 | |
| 
 | |
|   var = (SHELL_VAR *)data;
 | |
| 
 | |
|   if (tempvar_p (var) && (posixly_correct || (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 (0);
 | |
|       /* XXX - should we set v->context here? */
 | |
|       v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0, 0);
 | |
|       if (shell_variables == global_variables)
 | |
| 	var->attributes &= ~(att_tempvar|att_propagate);
 | |
|       else
 | |
| 	shell_variables->flags |= VC_HASTMPVAR;
 | |
|       v->attributes |= var->attributes;
 | |
|     }
 | |
|   else
 | |
|     stupidly_hack_special_variables (var->name);	/* XXX */
 | |
| 
 | |
|   dispose_variable (var);
 | |
| }
 | |
| 
 | |
| /* 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 0
 | |
|   if (exported_p (var) || (var->attributes & att_propagate))
 | |
| #else
 | |
|   if (tempvar_p (var) && exported_p (var) && (var->attributes & att_propagate))
 | |
| #endif
 | |
|     {
 | |
|       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;
 | |
|       v->attributes |= var->attributes;
 | |
|     }
 | |
|   else
 | |
|     stupidly_hack_special_variables (var->name);	/* XXX */
 | |
| 
 | |
|   dispose_variable (var);
 | |
| }
 | |
| 
 | |
| void
 | |
| pop_scope (is_special)
 | |
|      int is_special;
 | |
| {
 | |
|   VAR_CONTEXT *vcxt, *ret;
 | |
| 
 | |
|   vcxt = shell_variables;
 | |
|   if (vc_istempscope (vcxt) == 0)
 | |
|     {
 | |
|       internal_error (_("pop_scope: head of shell_variables not a temporary environment scope"));
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|   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_func_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		    */
 | |
| /*								    */
 | |
| /* **************************************************************** */
 | |
| 
 | |
| static WORD_LIST **dollar_arg_stack = (WORD_LIST **)NULL;
 | |
| static int dollar_arg_stack_slots;
 | |
| static int dollar_arg_stack_index;
 | |
| 
 | |
| /* XXX - we might want to consider pushing and popping the `getopts' state
 | |
|    when we modify the positional parameters. */
 | |
| 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 = (WORD_LIST **)
 | |
| 	xrealloc (dollar_arg_stack, (dollar_arg_stack_slots += 10)
 | |
| 		  * sizeof (WORD_LIST **));
 | |
|     }
 | |
|   dollar_arg_stack[dollar_arg_stack_index++] = list_rest_of_args ();
 | |
|   dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL;
 | |
| }
 | |
| 
 | |
| /* Restore the positional parameters from our stack. */
 | |
| void
 | |
| pop_dollar_vars ()
 | |
| {
 | |
|   if (!dollar_arg_stack || dollar_arg_stack_index == 0)
 | |
|     return;
 | |
| 
 | |
|   remember_args (dollar_arg_stack[--dollar_arg_stack_index], 1);
 | |
|   dispose_words (dollar_arg_stack[dollar_arg_stack_index]);
 | |
|   dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL;
 | |
|   set_dollar_vars_unchanged ();
 | |
| }
 | |
| 
 | |
| void
 | |
| dispose_saved_dollar_vars ()
 | |
| {
 | |
|   if (!dollar_arg_stack || dollar_arg_stack_index == 0)
 | |
|     return;
 | |
| 
 | |
|   dispose_words (dollar_arg_stack[dollar_arg_stack_index]);
 | |
|   dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL;
 | |
| }
 | |
| 
 | |
| /* 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. */
 | |
| extern int eof_encountered, eof_encountered_limit, ignoreeof;
 | |
| 
 | |
| #if defined (READLINE)
 | |
| extern int hostname_list_initialized;
 | |
| #endif
 | |
| 
 | |
| /* 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_XTRACEFD", sv_xtracefd },
 | |
| 
 | |
| #if defined (READLINE)
 | |
| #  if defined (STRICT_POSIX)
 | |
|   { "COLUMNS", sv_winsize },
 | |
| #  endif
 | |
|   { "COMP_WORDBREAKS", sv_comp_wordbreaks },
 | |
| #endif
 | |
| 
 | |
|   { "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 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_isnull (v))
 | |
|     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 (name[4] == 'S')
 | |
| 	    {
 | |
| 	      stifle_history (hmax);
 | |
| 	      hmax = where_history ();
 | |
| 	      if (history_lines_this_session > hmax)
 | |
| 		history_lines_this_session = hmax;
 | |
| 	    }
 | |
| 	  else
 | |
| 	    {
 | |
| 	      history_truncate_file (get_string_value ("HISTFILE"), hmax);
 | |
| 	      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;
 | |
| 
 | |
|   v = find_variable (name);
 | |
|   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 != 0;
 | |
|   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;
 | |
| {
 | |
|   char *tt;
 | |
|   int s;
 | |
| 
 | |
|   tt = get_string_value ("OPTIND");
 | |
|   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;
 | |
| {
 | |
|   SET_INT_VAR (name, posixly_correct);
 | |
|   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;
 | |
| 
 | |
|   v = get_string_value (name);
 | |
|   if (name[0] == 'L' && name[1] == 'A')	/* LANG */
 | |
|     set_lang (name, v);
 | |
|   else
 | |
|     set_locale_var (name, v);		/* LC_*, TEXTDOMAIN* */
 | |
| }
 | |
| 
 | |
| #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));
 | |
|       ae->value = 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));
 | |
| 	  ae->value = 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, *a2;
 | |
| 
 | |
|   v = find_variable ("PIPESTATUS");
 | |
|   if (v == 0 || array_p (v) == 0 || array_cell (v) == 0)
 | |
|     return ((ARRAY *)NULL);
 | |
|     
 | |
|   a = array_cell (v);
 | |
|   a2 = array_copy (array_cell (v));
 | |
| 
 | |
|   return a2;
 | |
| }
 | |
| 
 | |
| 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));
 | |
|     }
 | |
| }
 | 
