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