i-bash/variables.c
2009-09-12 16:46:51 +00:00

2952 lines
70 KiB
C

/* variables.c -- Functions for hacking shell variables. */
/* Copyright (C) 1987,1989 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
Bash is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 1, or (at your option)
any later version.
Bash is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Bash; see the file COPYING. If not, write to the Free
Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
#include "config.h"
#include "bashtypes.h"
#include "posixstat.h"
#if defined (qnx)
# include <sys/vc.h>
#endif
#if defined (HAVE_UNISTD_H)
# include <unistd.h>
#endif
#include <stdio.h>
#include <ctype.h>
#include <pwd.h>
#include "bashansi.h"
#include "shell.h"
#include "flags.h"
#include "execute_cmd.h"
#include "findcmd.h"
#include "mailcheck.h"
#include "input.h"
#include "builtins/getopt.h"
#include "builtins/common.h"
#if defined (READLINE)
# include "bashline.h"
# include <readline/readline.h>
#else
# include <tilde/tilde.h>
#endif
#if defined (HISTORY)
# include "bashhist.h"
# include <readline/history.h>
#endif /* HISTORY */
/* Variables used here and defined in other files. */
extern int posixly_correct;
extern int variable_context, line_number;
extern int interactive, interactive_shell, login_shell;
extern int subshell_environment, indirection_level;
extern int build_version, patch_level;
extern char *dist_version, *release_status;
extern char *shell_name;
extern char *primary_prompt, *secondary_prompt;
extern char *current_host_name;
extern Function *this_shell_builtin;
extern char *this_command_name;
extern time_t shell_start_time;
/* The list of shell variables that the user has created, or that came from
the environment. */
HASH_TABLE *shell_variables = (HASH_TABLE *)NULL;
/* The list of shell functions that the user has created, or that came from
the environment. */
HASH_TABLE *shell_functions = (HASH_TABLE *)NULL;
/* The current variable context. This is really a count of how deep into
executing functions we are. */
int variable_context = 0;
/* The array of shell assignments which are made only in the environment
for a single command. */
char **temporary_env = (char **)NULL;
/* The array of shell assignments which are in the environment for the
execution of a shell function. */
char **function_env = (char **)NULL;
/* The array of shell assignments which are made only in the environment
for the execution of a shell builtin command which may cause more than
one command to be executed (e.g., "source"). */
char **builtin_env = (char **)NULL;
/* Some funky variables which are known about specially. Here is where
"$*", "$1", and all the cruft is kept. */
char *dollar_vars[10];
WORD_LIST *rest_of_args = (WORD_LIST *)NULL;
/* The value of $$. */
int dollar_dollar_pid;
/* An array which is passed to commands as their environment. It is
manufactured from the 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;
/* 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;
static char *have_local_variables;
static int local_variable_stack_size;
/* Some forward declarations. */
static void set_home_var ();
static void set_shell_var ();
static char *get_bash_name ();
static void initialize_shell_level ();
static void uidset ();
static void initialize_dynamic_variables ();
static void make_vers_array ();
static void sbrand (); /* set bash random number generator. */
static int qsort_var_comp ();
/* Make VAR be auto-exported. VAR is a pointer to a SHELL_VAR. */
#define set_auto_export(var) \
do { var->attributes |= att_exported; array_needs_making = 1; } while (0)
/* Initialize the shell variables from the current environment.
If PRIVMODE is nonzero, don't import functions from ENV or
parse $SHELLOPTS. */
void
initialize_shell_variables (env, privmode)
char **env;
int privmode;
{
char *name, *string, *temp_string;
int c, char_index, string_index, string_length;
SHELL_VAR *temp_var;
if (shell_variables == 0)
shell_variables = make_hash_table (0);
if (shell_functions == 0)
shell_functions = make_hash_table (0);
for (string_index = 0; string = env[string_index++]; )
{
char_index = 0;
name = string;
while ((c = *string++) && c != '=')
;
if (string[-1] == '=')
char_index = string - name - 1;
/* If there are weird things in the environment, like `=xxx' or a
string without an `=', just skip them. */
if (char_index == 0)
continue;
/* ASSERT(name[char_index] == '=') */
name[char_index] = '\0';
/* Now, name = env variable name, string = env variable value, and
char_index == strlen (name) */
/* If exported function, define it now. */
if (privmode == 0 && STREQN ("() {", string, 4))
{
string_length = strlen (string);
temp_string = xmalloc (3 + string_length + char_index);
#if 1
strcpy (temp_string, name);
temp_string[char_index] = ' ';
strcpy (temp_string + char_index + 1, string);
#else
sprintf (temp_string, "%s %s", name, string);
#endif
parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST);
/* Ancient backwards compatibility. Old versions of bash exported
functions like name()=() {...} */
if (name[char_index - 1] == ')' && name[char_index - 2] == '(')
name[char_index - 2] = '\0';
if (temp_var = find_function (name))
{
temp_var->attributes |= (att_exported | att_imported);
array_needs_making = 1;
}
else
report_error ("error importing function definition for `%s'", name);
if (name[char_index - 1] == ')' && name[char_index - 2] == '\0')
name[char_index - 2] = '(';
}
#if defined (ARRAY_VARS)
# if 0
/* Array variables may not yet be exported. */
else if (*string == '(' && string[1] == '[' && strchr (string, ')'))
{
string_length = 1;
temp_string = extract_array_assignment_list (string, &string_length);
temp_var = assign_array_from_string (name, temp_string);
FREE (temp_string);
temp_var->attributes |= (att_exported | att_imported);
array_needs_making = 1;
}
# endif
#endif
else
{
temp_var = bind_variable (name, string);
temp_var->attributes |= (att_exported | att_imported);
array_needs_making = 1;
}
name[char_index] = '=';
}
/* 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. */
temp_var = find_variable ("PWD");
if (temp_var && imported_p (temp_var) &&
(temp_string = value_cell (temp_var)) &&
same_file (temp_string, ".", (struct stat *)NULL, (struct stat *)NULL))
set_working_directory (temp_string);
else
{
temp_string = get_working_directory ("shell-init");
if (temp_string)
{
temp_var = bind_variable ("PWD", temp_string);
set_auto_export (temp_var);
free (temp_string);
}
}
/* Set up initial value of $_ */
temp_var = bind_variable ("_", dollar_vars[0]);
/* Remember this pid. */
dollar_dollar_pid = (int)getpid ();
/* Now make our own defaults in case the vars that we think are
important are missing. */
temp_var = set_if_not ("PATH", DEFAULT_PATH_VALUE);
set_auto_export (temp_var);
temp_var = set_if_not ("TERM", "dumb");
set_auto_export (temp_var);
#if defined (qnx)
/* set node id -- don't import it from the environment */
{
char node_name[22];
qnx_nidtostr (getnid (), node_name, sizeof (node_name));
temp_var = bind_variable ("NODE", node_name);
set_auto_export (temp_var);
}
#endif
/* set up the prompts. */
if (interactive_shell)
{
set_if_not ("PS1", primary_prompt);
set_if_not ("PS2", secondary_prompt);
}
set_if_not ("PS4", "+ ");
/* Don't allow IFS to be imported from the environment. */
temp_var = bind_variable ("IFS", " \t\n");
/* Magic machine types. Pretty convenient. */
temp_var = bind_variable ("HOSTTYPE", HOSTTYPE);
set_auto_export (temp_var);
temp_var = bind_variable ("OSTYPE", OSTYPE);
set_auto_export (temp_var);
temp_var = bind_variable ("MACHTYPE", MACHTYPE);
set_auto_export (temp_var);
temp_var = bind_variable ("HOSTNAME", current_host_name);
set_auto_export (temp_var);
/* 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 MAILCHECK is not set, and we should provide a
default only if neither is set. */
if (interactive_shell)
set_if_not ("MAILCHECK", "60");
/* Do some things with shell level. */
initialize_shell_level ();
/* Make a variable $PPID, which holds the pid of the shell's parent. */
name = itos ((int) getppid ());
temp_var = find_variable ("PPID");
if (temp_var)
temp_var->attributes &= ~(att_readonly | att_exported);
temp_var = bind_variable ("PPID", name);
temp_var->attributes |= (att_readonly | att_integer);
free (name);
/* Initialize the `getopts' stuff. */
bind_variable ("OPTIND", "1");
getopts_reset (0);
bind_variable ("OPTERR", "1");
sh_opterr = 1;
if (login_shell == 1)
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);
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 ());
#if defined (ARRAY_VARS)
make_vers_array ();
#endif
/* 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");
set_if_not ("HISTFILE", name);
free (name);
set_if_not ("HISTSIZE", "500");
sv_histsize ("HISTSIZE");
}
#endif /* HISTORY */
/* Seed the random number generator. */
sbrand (dollar_dollar_pid + (long)shell_start_time);
/* 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");
}
#endif /* HISTORY */
/* Get the user's real and effective user ids. */
uidset ();
/* Initialize the dynamic variables, and seed their values. */
initialize_dynamic_variables ();
}
/* Set $HOME to the information in the password file if we didn't get
it from the environment. */
static void
set_home_var ()
{
SHELL_VAR *temp_var;
temp_var = find_variable ("HOME");
if (temp_var == 0)
{
if (current_user.home_dir == 0)
get_current_user_info ();
temp_var = bind_variable ("HOME", current_user.home_dir);
}
temp_var->attributes |= att_exported;
}
/* 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);
}
temp_var->attributes |= att_exported;
}
static char *
get_bash_name ()
{
char *name;
if ((login_shell == 1) && (*shell_name != '/'))
{
if (current_user.shell == 0)
get_current_user_info ();
name = savestring (current_user.shell);
}
else if (*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");
len = strlen (cdir);
name = xmalloc (len + strlen (shell_name) + 1);
strcpy (name, cdir);
strcpy (name + len, shell_name + 1);
}
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 = canonicalize_pathname (tname);
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;
int old_level;
SHELL_VAR *temp_var;
old_SHLVL = get_string_value ("SHLVL");
old_level = old_SHLVL ? atoi (old_SHLVL) : 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);
set_auto_export (temp_var);
}
static void
initialize_shell_level ()
{
#if 0
SHELL_VAR *temp_var;
temp_var = set_if_not ("SHLVL", "0");
set_auto_export (temp_var);
#endif
adjust_shell_level (1);
}
static void
uidset ()
{
char *buff;
register SHELL_VAR *v;
buff = itos (current_user.uid);
v = find_variable ("UID");
if (v)
v->attributes &= ~att_readonly;
v = bind_variable ("UID", buff);
v->attributes |= (att_readonly | att_integer);
if (current_user.euid != current_user.uid)
{
free (buff);
buff = itos (current_user.euid);
}
v = find_variable ("EUID");
if (v)
v->attributes &= ~att_readonly;
v = bind_variable ("EUID", buff);
v->attributes |= (att_readonly | att_integer);
free (buff);
}
#if defined (ARRAY_VARS)
static void
make_vers_array ()
{
SHELL_VAR *vv;
ARRAY *av;
char *s, d[16];
makunbound ("BASH_VERSINFO", shell_variables);
vv = make_new_array_variable ("BASH_VERSINFO");
av = array_cell (vv);
strcpy (d, dist_version);
s = strchr (d, '.');
if (s)
*s++ = '\0';
array_add_element (av, 0, d);
array_add_element (av, 1, s);
s = itos (patch_level);
array_add_element (av, 2, s);
free (s);
s = itos (build_version);
array_add_element (av, 3, s);
free (s);
array_add_element (av, 4, release_status);
array_add_element (av, 5, MACHTYPE);
vv->attributes |= att_readonly;
}
#endif /* ARRAY_VARS */
/* Set the environment variables $LINES and $COLUMNS in response to
a window size change. */
void
set_lines_and_columns (lines, cols)
int lines, cols;
{
char *val;
val = itos (lines);
bind_variable ("LINES", val);
free (val);
val = itos (cols);
bind_variable ("COLUMNS", val);
free (val);
}
/* Set NAME to VALUE if NAME has no value. */
SHELL_VAR *
set_if_not (name, value)
char *name, *value;
{
SHELL_VAR *v;
v = find_variable (name);
if (v == 0)
v = bind_variable (name, value);
return (v);
}
/* Map FUNCTION over the variables in VARIABLES. 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, var_hash_table)
Function *function;
HASH_TABLE* var_hash_table;
{
register int i;
register BUCKET_CONTENTS *tlist;
SHELL_VAR *var, **list;
int list_index, list_size;
list = (SHELL_VAR **)NULL;
for (i = list_index = list_size = 0; i < var_hash_table->nbuckets; i++)
{
tlist = get_hash_bucket (i, var_hash_table);
while (tlist)
{
var = (SHELL_VAR *)tlist->data;
if (!function || (*function) (var))
{
if (list_index + 1 >= list_size)
list = (SHELL_VAR **)
xrealloc (list, (list_size += 20) * sizeof (SHELL_VAR *));
list[list_index++] = var;
list[list_index] = (SHELL_VAR *)NULL;
}
tlist = tlist->next;
}
}
return (list);
}
void
sort_variables (array)
SHELL_VAR **array;
{
qsort (array, array_len ((char **)array), sizeof (SHELL_VAR *), 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);
}
/* Create a NULL terminated array of all the shell variables in TABLE. */
static SHELL_VAR **
all_vars (table)
HASH_TABLE *table;
{
SHELL_VAR **list;
list = map_over ((Function *)NULL, table);
if (list /* && posixly_correct */)
sort_variables (list);
return (list);
}
/* Create a NULL terminated array of all the shell variables. */
SHELL_VAR **
all_shell_variables ()
{
return (all_vars (shell_variables));
}
/* Create a NULL terminated array of all the shell functions. */
SHELL_VAR **
all_shell_functions ()
{
return (all_vars (shell_functions));
}
/* Print VARS to stdout in such a way that they can be read back in. */
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))
print_assignment (var);
}
#if defined (NOTDEF)
/* Print LIST (a linked list of shell variables) to stdout
by printing the names, without the values. Used to support the
`set +' command. */
void
print_vars_no_values (list)
register SHELL_VAR **list;
{
register int i;
register SHELL_VAR *var;
for (i = 0; list && (var = list[i]); i++)
if (!invisible_p (var))
printf ("%s\n", var->name);
}
#endif
/* Print the value of a single SHELL_VAR. No newline is
output, but the variable is printed in such a way that
it can be read back in. */
void
print_assignment (var)
SHELL_VAR *var;
{
if (function_p (var) && var->value)
{
printf ("%s=", var->name);
print_var_function (var);
printf ("\n");
}
#if defined (ARRAY_VARS)
else if (array_p (var) && var->value)
print_array_assignment (var, 0);
#endif /* ARRAY_VARS */
else if (var->value)
{
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->value)
{
if (quote && contains_shell_metas (var->value))
{
t = single_quote (var->value);
printf ("%s", t);
free (t);
}
else
printf ("%s", var->value);
}
}
/* 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;
{
if (function_p (var) && var->value)
printf ("%s", named_function_string ((char *)NULL, function_cell(var), 1));
}
#if defined (ARRAY_VARS)
void
print_array_assignment (var, quoted)
SHELL_VAR *var;
int quoted;
{
char *vstr;
if (quoted)
vstr = quoted_array_assignment_string (array_cell (var));
else
vstr = array_to_assignment_string (array_cell (var));
if (vstr == 0)
printf ("%s=%s\n", var->name, quoted ? "'()'" : "()");
else
{
printf ("%s=%s\n", var->name, vstr);
free (vstr);
}
}
#endif /* ARRAY_VARS */
/* **************************************************************** */
/* */
/* Dynamic Variable Extension */
/* */
/* **************************************************************** */
/* DYNAMIC VARIABLES
These are variables whose values are generated anew each time they are
referenced. These are implemented using a pair of function pointers
in the struct variable: assign_func, which is called from bind_variable,
and dynamic_value, which is called from find_variable.
assign_func is called from bind_variable, if bind_variable discovers
that the variable being assigned to has such a function. The function
is called as
SHELL_VAR *temp = (*(entry->assign_func)) (entry, value)
and the (SHELL_VAR *)temp is returned as the value of bind_variable. It
is usually ENTRY (self).
dynamic_value is called from find_variable to return a `new' value for
the specified dynamic varible. If this function is NULL, the variable
is treated as a `normal' shell variable. If it is not, however, then
this function is called like this:
tempvar = (*(var->dynamic_value)) (var);
Sometimes `tempvar' will replace the value of `var'. Other times, the
shell will simply use the string value. Pretty object-oriented, huh?
Be warned, though: if you `unset' a special variable, it loses its
special meaning, even if you subsequently set it.
The special assignment code would probably have been better put in
subst.c: do_assignment, in the same style as
stupidly_hack_special_variables, but I wanted the changes as
localized as possible. */
/* The value of $SECONDS. This is the number of seconds since shell
invocation, or, the number of seconds since the last assignment + the
value of the last assignment. */
static long seconds_value_assigned;
static SHELL_VAR *
assign_seconds (self, value)
SHELL_VAR *self;
char *value;
{
seconds_value_assigned = strtol (value, (char **)NULL, 10);
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((int) seconds_value_assigned + time_since_start);
FREE (var->value);
var->attributes |= att_integer;
var->value = p;
return (var);
}
/* The random number seed. You can change this by setting RANDOM. */
static unsigned long rseed = 1;
static unsigned long last_random_value;
/* A linear congruential random number generator based on the ANSI
C standard. This one isn't very good (the values are alternately
odd and even, for example), but a more complicated one is overkill. */
/* Returns a pseudo-random number between 0 and 32767. */
static int
brand ()
{
rseed = rseed * 1103515245 + 12345;
return ((unsigned int)(rseed & 32767)); /* was % 32768 */
}
/* Set the random number generator seed to SEED. */
static void
sbrand (seed)
int seed;
{
rseed = seed;
last_random_value = 0;
}
static SHELL_VAR *
assign_random (self, value)
SHELL_VAR *self;
char *value;
{
sbrand (atoi (value));
return (self);
}
static SHELL_VAR *
get_random (var)
SHELL_VAR *var;
{
int rv;
char *p;
/* Reset for command and process substitution. */
if (subshell_environment)
sbrand (rseed + (int)(getpid() + NOW));
do
rv = brand ();
while (rv == (int)last_random_value);
last_random_value = rv;
p = itos ((int)rv);
FREE (var->value);
var->attributes |= att_integer;
var->value = p;
return (var);
}
/* Function which returns the current line number. */
static SHELL_VAR *
get_lineno (var)
SHELL_VAR *var;
{
char *p;
int ln;
ln = executing_line_number ();
p = itos (ln);
FREE (var->value);
var->value = p;
return (var);
}
static SHELL_VAR *
assign_lineno (var, value)
SHELL_VAR *var;
char *value;
{
line_number = atoi (value);
return var;
}
#if defined (HISTORY)
static SHELL_VAR *
get_histcmd (var)
SHELL_VAR *var;
{
char *p;
p = itos (history_number ());
FREE (var->value);
var->value = p;
return (var);
}
#endif
#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS)
static SHELL_VAR *
get_dirstack (self)
SHELL_VAR *self;
{
ARRAY *a;
WORD_LIST *l;
l = get_directory_stack ();
a = word_list_to_array (l);
dispose_array (array_cell (self));
dispose_words (l);
self->value = (char *)a;
return self;
}
static SHELL_VAR *
assign_dirstack (self, ind, value)
SHELL_VAR *self;
int ind;
char *value;
{
set_dirstack_element (ind, 1, value);
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_add_element (a, i, group_set[i]);
}
return (self);
}
#endif /* ARRAY_VARS */
static void
initialize_dynamic_variables ()
{
SHELL_VAR *v;
v = bind_variable ("SECONDS", (char *)NULL);
v->dynamic_value = get_seconds;
v->assign_func = assign_seconds;
v = bind_variable ("RANDOM", (char *)NULL);
v->dynamic_value = get_random;
v->assign_func = assign_random;
v = bind_variable ("LINENO", (char *)NULL);
v->dynamic_value = get_lineno;
v->assign_func = assign_lineno;
#if defined (HISTORY)
v = bind_variable ("HISTCMD", (char *)NULL);
v->dynamic_value = get_histcmd;
v->assign_func = (DYNAMIC_FUNC *)NULL;
#endif
#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS)
v = make_new_array_variable ("DIRSTACK");
v->dynamic_value = get_dirstack;
v->assign_func = assign_dirstack;
#endif /* PUSHD_AND_POPD && ARRAY_VARS */
#if defined (ARRAY_VARS)
v = make_new_array_variable ("GROUPS");
v->dynamic_value = get_groupset;
v->assign_func = (DYNAMIC_FUNC *)NULL;
v->attributes |= att_readonly;
#endif
}
/* How to get a pointer to the shell variable or function named NAME.
HASHED_VARS is a pointer to the hash table containing the list
of interest (either variables or functions). */
SHELL_VAR *
var_lookup (name, hashed_vars)
char *name;
HASH_TABLE *hashed_vars;
{
BUCKET_CONTENTS *bucket;
bucket = find_hash_item (name, hashed_vars);
return (bucket ? (SHELL_VAR *)bucket->data : (SHELL_VAR *)NULL);
}
/* Look up the variable entry named NAME. If SEARCH_TEMPENV is non-zero,
then also search the temporarily built list of exported variables. */
SHELL_VAR *
find_variable_internal (name, search_tempenv)
char *name;
int search_tempenv;
{
SHELL_VAR *var = (SHELL_VAR *)NULL;
/* If explicitly requested, first look in the temporary environment for
the variable. This allows constructs such as "foo=x eval 'echo $foo'"
to get the `exported' value of $foo. This happens if we are executing
a function or builtin, or if we are looking up a variable in a
"subshell environment". */
if ((search_tempenv || subshell_environment) &&
(temporary_env || builtin_env || function_env))
var = find_tempenv_variable (name);
if (!var)
var = var_lookup (name, shell_variables);
if (!var)
return ((SHELL_VAR *)NULL);
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)
char *name;
{
return (find_variable_internal
(name, (variable_context || this_shell_builtin || builtin_env)));
}
/* Look up the function entry whose name matches STRING.
Returns the entry or NULL. */
SHELL_VAR *
find_function (name)
char *name;
{
return (var_lookup (name, shell_functions));
}
/* Return the string value of a variable. Return NULL if the variable
doesn't exist, or only has a function as a value. Don't cons a new
string. This is a potential memory leak if the variable is found
in the temporary environment. */
char *
get_string_value (var_name)
char *var_name;
{
SHELL_VAR *var;
var = find_variable (var_name);
if (!var)
return (char *)NULL;
#if defined (ARRAY_VARS)
else if (array_p (var))
return (array_reference (array_cell (var), 0));
#endif
else
return (var->value);
}
/* Create a local variable referenced by NAME. */
SHELL_VAR *
make_local_variable (name)
char *name;
{
SHELL_VAR *new_var, *old_var;
BUCKET_CONTENTS *elt;
/* local foo; local foo; is a no-op. */
old_var = find_variable (name);
if (old_var && old_var->context == variable_context)
return (old_var);
elt = remove_hash_item (name, shell_variables);
if (elt)
{
old_var = (SHELL_VAR *)elt->data;
free (elt->key);
free (elt);
}
else
old_var = (SHELL_VAR *)NULL;
/* If a variable does not already exist with this name, then
just make a new one. */
if (old_var == 0)
new_var = bind_variable (name, "");
else
{
new_var = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
new_var->name = savestring (name);
new_var->value = xmalloc (1);
new_var->value[0] = '\0';
new_var->dynamic_value = (DYNAMIC_FUNC *)NULL;
new_var->assign_func = (DYNAMIC_FUNC *)NULL;
new_var->attributes = exported_p (old_var) ? att_exported : 0;
new_var->prev_context = old_var;
elt = add_hash_item (savestring (name), shell_variables);
elt->data = (char *)new_var;
}
new_var->context = variable_context;
new_var->attributes |= att_local;
/* XXX */
if (variable_context >= local_variable_stack_size)
{
int old_size = local_variable_stack_size;
RESIZE_MALLOCED_BUFFER (have_local_variables, variable_context, 1,
local_variable_stack_size, 8);
bzero ((char *)have_local_variables + old_size,
local_variable_stack_size - old_size);
}
have_local_variables[variable_context] = 1; /* XXX */
return (new_var);
}
#if defined (ARRAY_VARS)
SHELL_VAR *
make_local_array_variable (name)
char *name;
{
SHELL_VAR *var;
ARRAY *array;
var = make_local_variable (name);
array = new_array ();
FREE (value_cell(var));
var->value = (char *)array;
var->attributes |= att_array;
return var;
}
#endif /* ARRAY_VARS */
/* Create a new shell variable with name NAME and add it to the hash table
of shell variables. */
static
SHELL_VAR *
make_new_variable (name)
char *name;
{
SHELL_VAR *entry;
BUCKET_CONTENTS *elt;
entry = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
entry->attributes = 0;
entry->name = savestring (name);
entry->value = (char *)NULL;
entry->dynamic_value = (DYNAMIC_FUNC *)NULL;
entry->assign_func = (DYNAMIC_FUNC *)NULL;
/* Always assume variables are to be made at toplevel!
make_local_variable has the responsibilty of changing the
variable context. */
entry->context = 0;
entry->prev_context = (SHELL_VAR *)NULL;
elt = add_hash_item (savestring (name), shell_variables);
elt->data = (char *)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);
array = new_array ();
entry->value = (char *)array;
entry->attributes |= att_array;
return entry;
}
#endif
char *
make_variable_value (var, value)
SHELL_VAR *var;
char *value;
{
char *retval;
long lval;
int expok;
/* If this variable has had its type set to integer (via `declare -i'),
then do expression evaluation on it and store the result. The
functions in expr.c (evalexp and bind_int_variable) are responsible
for turning off the integer flag if they don't want further
evaluation done. */
if (integer_p (var))
{
lval = evalexp (value, &expok);
if (expok == 0)
jump_to_top_level (DISCARD);
retval = itos (lval);
}
else if (value)
{
if (*value)
retval = savestring (value);
else
{
retval = xmalloc (1);
retval[0] = '\0';
}
}
else
retval = (char *)NULL;
return retval;
}
/* Bind a variable NAME to VALUE. This conses up the name
and value strings. */
SHELL_VAR *
bind_variable (name, value)
char *name, *value;
{
char *newval;
SHELL_VAR *entry;
entry = var_lookup (name, shell_variables);
if (entry == 0)
{
entry = make_new_variable (name);
entry->value = make_variable_value (entry, value);
}
#if defined (ARRAY_VARS)
else if (entry->assign_func && array_p (entry) == 0)
#else
else if (entry->assign_func)
#endif
return ((*(entry->assign_func)) (entry, value));
else
{
if (readonly_p (entry))
{
report_error ("%s: readonly variable", name);
return (entry);
}
/* Variables which are bound are visible. */
entry->attributes &= ~att_invisible;
newval = make_variable_value (entry, value);
#if defined (ARRAY_VARS)
/* XXX -- this bears looking at again -- XXX */
/* If an existing array variable x is being assigned to with x=b or
`read x' or something of that nature, silently convert it to
x[0]=b or `read x[0]'. */
if (array_p (entry))
{
array_add_element (array_cell (entry), 0, newval);
free (newval);
}
else
{
FREE (entry->value);
entry->value = newval;
}
#else
FREE (entry->value);
entry->value = newval;
#endif
}
if (mark_modified_vars)
entry->attributes |= att_exported;
if (exported_p (entry))
array_needs_making = 1;
return (entry);
}
#if defined (ARRAY_VARS)
/* Convert a shell variable to an array variable. The original value is
saved as array[0]. */
SHELL_VAR *
convert_var_to_array (var)
SHELL_VAR *var;
{
char *oldval;
ARRAY *array;
oldval = value_cell (var);
array = new_array ();
array_add_element (array, 0, oldval);
FREE (value_cell (var));
var->value = (char *)array;
var->attributes |= att_array;
var->attributes &= ~att_invisible;
return var;
}
/* Perform an array assignment name[ind]=value. If NAME already exists and
is not an array, and IND is 0, perform name=value instead. If NAME exists
and is not an array, and IND is not 0, convert it into an array with the
existing value as name[0].
If NAME does not exist, just create an array variable, no matter what
IND's value may be. */
SHELL_VAR *
bind_array_variable (name, ind, value)
char *name;
int ind;
char *value;
{
SHELL_VAR *entry;
char *newval;
entry = var_lookup (name, shell_variables);
if (entry == (SHELL_VAR *) 0)
entry = make_new_array_variable (name);
else if (readonly_p (entry))
{
report_error ("%s: readonly variable", name);
return (entry);
}
else if (array_p (entry) == 0)
entry = convert_var_to_array (entry);
/* ENTRY is an array variable, and ARRAY points to the value. */
newval = make_variable_value (entry, value);
if (entry->assign_func)
(*entry->assign_func) (entry, ind, newval);
else
array_add_element (array_cell (entry), ind, newval);
FREE (newval);
return (entry);
}
/* Perform a compound assignment statement for array NAME, where VALUE is
the text between the parens: NAME=( VALUE ) */
SHELL_VAR *
assign_array_from_string (name, value)
char *name, *value;
{
SHELL_VAR *var;
var = find_variable (name);
if (var == 0)
var = make_new_array_variable (name);
else if (readonly_p (var))
{
report_error ("%s: readonly variable", name);
return ((SHELL_VAR *)NULL);
}
else if (array_p (var) == 0)
var = convert_var_to_array (var);
return (assign_array_var_from_string (var, value));
}
SHELL_VAR *
assign_array_var_from_word_list (var, list)
SHELL_VAR *var;
WORD_LIST *list;
{
register int i;
register WORD_LIST *l;
ARRAY *a;
for (a = array_cell (var), l = list, i = 0; l; l = l->next, i++)
if (var->assign_func)
(*var->assign_func) (var, i, l->word->word);
else
array_add_element (a, i, l->word->word);
return var;
}
/* Perform a compound array assignment: VAR->name=( VALUE ). The
VALUE has already had the parentheses stripped. */
SHELL_VAR *
assign_array_var_from_string (var, value)
SHELL_VAR *var;
char *value;
{
ARRAY *a;
WORD_LIST *list, *nlist;
char *w, *val, *nval;
int ni, len, ind, last_ind;
if (value == 0)
return var;
/* If this is called from declare_builtin, value[0] == '(' and
strchr(value, ')') != 0. In this case, we need to extract
the value from between the parens before going on. */
if (*value == '(') /*)*/
{
ni = 1;
val = extract_array_assignment_list (value, &ni);
if (val == 0)
return var;
}
else
val = value;
/* Expand the value string into a list of words, performing all the
shell expansions including word splitting. */
#if 1
/* First we split the string on whitespace, using the shell parser
(ksh93 seems to do this). */
list = parse_string_to_word_list (val, "array assign");
/* Now that we've split it, perform the shell expansions on each
word in the list. */
nlist = list ? expand_words_shellexp (list) : (WORD_LIST *)NULL;
dispose_words (list);
#else
nlist = expand_string (val, 0);
#endif
if (val != value)
free (val);
a = array_cell (var);
/* Now that we are ready to assign values to the array, kill the existing
value. */
if (a)
empty_array (a);
for (last_ind = 0, list = nlist; list; list = list->next)
{
w = list->word->word;
/* We have a word of the form [ind]=value */
if (w[0] == '[')
{
len = skipsubscript (w, 0);
if (w[len] != ']' || w[len+1] != '=')
{
nval = make_variable_value (var, w);
if (var->assign_func)
(*var->assign_func) (var, last_ind, nval);
else
array_add_element (a, last_ind, nval);
FREE (nval);
last_ind++;
continue;
}
if (len == 1)
{
report_error ("%s: bad array subscript", w);
continue;
}
if (ALL_ELEMENT_SUB (w[1]) && len == 2)
{
report_error ("%s: cannot assign to non-numeric index", w);
continue;
}
ind = array_expand_index (w + 1, len);
if (ind < 0)
{
report_error ("%s: bad array subscript", w);
continue;
}
last_ind = ind;
val = w + len + 2;
}
else /* No [ind]=value, just a stray `=' */
{
ind = last_ind;
val = w;
}
if (integer_p (var))
this_command_name = (char *)NULL; /* no command name for errors */
nval = make_variable_value (var, val);
if (var->assign_func)
(*var->assign_func) (var, ind, nval);
else
array_add_element (a, ind, nval);
FREE (nval);
last_ind++;
}
dispose_words (nlist);
return (var);
}
#endif /* ARRAY_VARS */
/* Dispose of the information attached to VAR. */
void
dispose_variable (var)
SHELL_VAR *var;
{
if (!var)
return;
if (function_p (var))
dispose_command (function_cell (var));
#if defined (ARRAY_VARS)
else if (array_p (var))
dispose_array (array_cell (var));
#endif
else
FREE (value_cell (var));
free (var->name);
if (exported_p (var))
array_needs_making = 1;
free (var);
}
#if defined (ARRAY_VARS)
/* This function is called with SUB pointing to just after the beginning
`[' of an array subscript. */
int
unbind_array_element (var, sub)
SHELL_VAR *var;
char *sub;
{
int len, ind;
ARRAY_ELEMENT *ae;
len = skipsubscript (sub, 0);
if (sub[len] != ']' || len == 0)
{
builtin_error ("%s[%s: bad array subscript", var->name, sub);
return -1;
}
sub[len] = '\0';
if (ALL_ELEMENT_SUB (sub[0]) && sub[1] == 0)
{
makunbound (var->name, shell_variables);
return (0);
}
ind = array_expand_index (sub, len+1);
if (ind < 0)
{
builtin_error ("[%s]: bad array subscript", sub);
return -1;
}
ae = array_delete_element (array_cell (var), ind);
if (ae)
destroy_array_element (ae);
return 0;
}
#endif
/* Unset the variable referenced by NAME. */
int
unbind_variable (name)
char *name;
{
SHELL_VAR *var;
var = find_variable (name);
if (!var)
return (-1);
/* This function should never be called with an array variable name. */
#if defined (ARRAY_VARS)
if (array_p (var) == 0 && var->value)
#else
if (var->value)
#endif
{
free (var->value);
var->value = (char *)NULL;
}
makunbound (name, shell_variables);
return (0);
}
/* Make the variable associated with NAME go away. HASH_LIST is the
hash table from which this variable should be deleted (either
shell_variables or shell_functions).
Returns non-zero if the variable couldn't be found. */
int
makunbound (name, hash_list)
char *name;
HASH_TABLE *hash_list;
{
BUCKET_CONTENTS *elt, *new_elt;
SHELL_VAR *old_var, *new_var;
char *t;
elt = remove_hash_item (name, hash_list);
if (elt == 0)
return (-1);
old_var = (SHELL_VAR *)elt->data;
new_var = old_var->prev_context;
if (old_var && exported_p (old_var))
array_needs_making++;
/* If we're unsetting a local variable and we're still executing inside
the function, just mark the variable as invisible.
kill_all_local_variables will clean it up later. This must be done
so that if the variable is subsequently assigned a new value inside
the function, the `local' attribute is still present. We also need
to add it back into the correct hash table. */
if (old_var && local_p (old_var) && variable_context == old_var->context)
{
old_var->attributes |= att_invisible;
new_elt = add_hash_item (savestring (old_var->name), hash_list);
new_elt->data = (char *)old_var;
stupidly_hack_special_variables (old_var->name);
free (elt->key);
free (elt);
return (0);
}
if (new_var)
{
/* Has to be a variable, functions don't have previous contexts. */
new_elt = add_hash_item (savestring (new_var->name), hash_list);
new_elt->data = (char *)new_var;
if (exported_p (new_var))
set_auto_export (new_var);
}
/* 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);
}
#ifdef INCLUDE_UNUSED
/* Remove the variable with NAME if it is a local variable in the
current context. */
int
kill_local_variable (name)
char *name;
{
SHELL_VAR *temp;
temp = find_variable (name);
if (temp && temp->context == variable_context)
{
makunbound (name, shell_variables);
return (0);
}
return (-1);
}
#endif
/* Get rid of all of the variables in the current context. */
int
variable_in_context (var)
SHELL_VAR *var;
{
return (var && var->context == variable_context);
}
void
kill_all_local_variables ()
{
register int i, pass;
register SHELL_VAR *var, **list;
HASH_TABLE *varlist;
/* If HAVE_LOCAL_VARIABLES == 0, it means that we don't have any local
variables at all. If VARIABLE_CONTEXT >= LOCAL_VARIABLE_STACK_SIZE,
it means that we have some local variables, but not in this variable
context (level of function nesting). Also, if
HAVE_LOCAL_VARIABLES[VARIABLE_CONTEXT] == 0, we have no local variables
at this context. */
if (have_local_variables == 0 ||
variable_context >= local_variable_stack_size ||
have_local_variables[variable_context] == 0)
return;
for (pass = 0; pass < 2; pass++)
{
varlist = pass ? shell_functions : shell_variables;
list = map_over (variable_in_context, varlist);
if (list)
{
for (i = 0; var = list[i]; i++)
{
var->attributes &= ~att_local;
makunbound (var->name, varlist);
}
free (list);
}
}
have_local_variables[variable_context] = 0; /* XXX */
}
static void
free_variable_hash_data (data)
char *data;
{
SHELL_VAR *var, *prev;
var = (SHELL_VAR *)data;
while (var)
{
prev = var->prev_context;
dispose_variable (var);
var = prev;
}
}
/* Delete the entire contents of the hash table. */
void
delete_all_variables (hashed_vars)
HASH_TABLE *hashed_vars;
{
flush_hash_table (hashed_vars, free_variable_hash_data);
}
static SHELL_VAR *
new_shell_variable (name)
char *name;
{
SHELL_VAR *var;
var = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
bzero ((char *)var, sizeof (SHELL_VAR));
var->name = savestring (name);
return (var);
}
/* Do a function binding to a variable. You pass the name and
the command to bind to. This conses the name and command. */
SHELL_VAR *
bind_function (name, value)
char *name;
COMMAND *value;
{
SHELL_VAR *entry;
entry = find_function (name);
if (!entry)
{
BUCKET_CONTENTS *elt;
elt = add_hash_item (savestring (name), shell_functions);
elt->data = (char *)new_shell_variable (name);
entry = (SHELL_VAR *)elt->data;
entry->dynamic_value = entry->assign_func = (DYNAMIC_FUNC *)NULL;
/* Functions are always made at the top level. This allows a
function to define another function (like autoload). */
entry->context = 0;
}
if (entry->value)
dispose_command ((COMMAND *)entry->value);
entry->value = value ? (char *)copy_command (value) : (char *)NULL;
entry->attributes |= att_function;
if (mark_modified_vars)
entry->attributes |= att_exported;
entry->attributes &= ~att_invisible; /* Just to be sure */
if (exported_p (entry))
array_needs_making = 1;
return (entry);
}
#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))
copy->value = (char *)copy_command (function_cell (var));
#if defined (ARRAY_VARS)
else if (array_p (var))
copy->value = (char *)dup_array (array_cell (var));
#endif
else if (value_cell (var))
copy->value = savestring (value_cell (var));
else
copy->value = (char *)NULL;
copy->dynamic_value = var->dynamic_value;
copy->assign_func = var->assign_func;
copy->context = var->context;
/* Don't bother copying previous contexts along with this variable. */
copy->prev_context = (SHELL_VAR *)NULL;
}
return (copy);
}
#endif
#define FIND_OR_MAKE_VARIABLE(name, entry) \
do \
{ \
entry = find_variable (name); \
if (!entry) \
{ \
entry = bind_variable (name, ""); \
if (!no_invisible_vars) 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);
entry->attributes |= 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)
char *name;
{
SHELL_VAR *entry;
entry = find_function (name);
if (entry)
entry->attributes |= att_readonly;
}
/* Make the variable associated with NAME be auto-exported.
If NAME does not exist yet, create it. */
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)
char *name;
{
SHELL_VAR *entry;
entry = find_function (name);
if (entry)
set_auto_export (entry);
}
#endif
#if defined (ARRAY_VARS)
/* This function assumes s[i] == '['; returns with s[ret] == ']' if
an array subscript is correctly parsed. */
int
skipsubscript (s, i)
char *s;
int i;
{
int count, c;
for (count = 1; count && (c = s[++i]); )
{
if (c == '[')
count++;
else if (c == ']')
count--;
}
return i;
}
#endif /* ARRAY_VARS */
/* Returns non-zero if STRING is an assignment statement. The returned value
is the index of the `=' sign. */
int
assignment (string)
char *string;
{
register int c, newi, indx;
c = string[indx = 0];
if (legal_variable_starter (c) == 0)
return (0);
while (c = string[indx])
{
/* The following is safe. Note that '=' at the start of a word
is not an assignment statement. */
if (c == '=')
return (indx);
#if defined (ARRAY_VARS)
if (c == '[')
{
newi = skipsubscript (string, indx);
if (string[newi++] != ']')
return (0);
return ((string[newi] == '=') ? newi : 0);
}
#endif /* ARRAY_VARS */
/* Variable names in assignment statements may contain only letters,
digits, and `_'. */
if (legal_variable_char (c) == 0)
return (0);
indx++;
}
return (0);
}
#ifdef READLINE
static int
visible_var (var)
SHELL_VAR *var;
{
return (invisible_p (var) == 0);
}
static SHELL_VAR **
_visible_names (table)
HASH_TABLE *table;
{
SHELL_VAR **list;
list = map_over (visible_var, table);
if (list /* && posixly_correct */)
sort_variables (list);
return (list);
}
SHELL_VAR **
all_visible_variables ()
{
return (_visible_names (shell_variables));
}
SHELL_VAR **
all_visible_functions ()
{
return (_visible_names (shell_functions));
}
#endif /* READLINE */
/* 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));
}
/* Make an array of assignment statements from the hash table
HASHED_VARS which contains SHELL_VARs. Only visible, exported
variables are eligible. */
char **
make_var_array (hashed_vars)
HASH_TABLE *hashed_vars;
{
register int i, list_index;
register SHELL_VAR *var;
char **list, *value;
SHELL_VAR **vars;
vars = map_over (visible_and_exported, hashed_vars);
if (vars == 0)
return (char **)NULL;
list = (char **)xmalloc ((1 + array_len ((char **)vars)) * sizeof (char *));
for (i = 0, list_index = 0; var = vars[i]; i++)
{
if (function_p (var))
value = named_function_string ((char *)NULL, function_cell (var), 0);
#if defined (ARRAY_VARS)
else if (array_p (var))
# if 0
value = array_to_assignment_string (array_cell (var));
# else
continue; /* XXX array vars cannot yet be exported */
# endif
#endif
else
value = value_cell (var);
if (value)
{
int name_len, value_len;
char *p;
name_len = strlen (var->name);
value_len = strlen (value);
p = list[list_index] = xmalloc (2 + name_len + value_len);
strcpy (p, var->name);
p[name_len] = '=';
strcpy (p + name_len + 1, value);
list_index++;
#if defined (ARRAY_VARS)
if (array_p (var))
free (value);
#endif
}
}
free (vars);
list[list_index] = (char *)NULL;
return (list);
}
/* Add STRING to the array of foo=bar strings that we already
have to add to the environment. */
int
assign_in_env (string)
char *string;
{
int size, offset;
char *name, *temp, *value;
int nlen, vlen;
WORD_LIST *list;
SHELL_VAR *var;
offset = assignment (string);
name = savestring (string);
value = (char *)NULL;
if (name[offset] == '=')
{
name[offset] = 0;
var = find_variable (name);
if (var && readonly_p (var))
{
report_error ("%s: readonly variable", name);
free (name);
return (0);
}
temp = name + offset + 1;
temp = (strchr (temp, '~') != 0) ? bash_tilde_expand (temp) : savestring (temp);
list = expand_string_unsplit (temp, 0);
value = string_list (list);
if (list)
dispose_words (list);
free (temp);
}
nlen = strlen (name);
vlen = value ? strlen (value) : 0;
temp = xmalloc (2 + nlen + vlen);
strcpy (temp, name);
temp[nlen] = '=';
temp[nlen + 1] = '\0';
if (value)
{
if (*value)
strcpy (temp + nlen + 1, value);
free (value);
}
free (name);
if (temporary_env == 0)
{
temporary_env = (char **)xmalloc (sizeof (char *));
temporary_env [0] = (char *)NULL;
}
size = array_len (temporary_env);
temporary_env = (char **)
xrealloc (temporary_env, (size + 2) * (sizeof (char *)));
temporary_env[size] = temp;
temporary_env[size + 1] = (char *)NULL;
array_needs_making = 1;
if (echo_command_at_execute)
{
/* The Korn shell prints the `+ ' in front of assignment statements,
so we do too. */
fprintf (stderr, "%s%s\n", indirection_level_string (), temp);
fflush (stderr);
}
return 1;
}
/* Search for NAME in ARRAY, an array of strings in the same format as the
environment array (i.e, name=value). If NAME is present, make a new
variable and return it. Otherwise, return NULL. */
static SHELL_VAR *
find_name_in_env_array (name, array)
char *name;
char **array;
{
register int i, l;
if (array == 0)
return ((SHELL_VAR *)NULL);
for (i = 0, l = strlen (name); array[i]; i++)
{
if (STREQN (array[i], name, l) && array[i][l] == '=')
{
SHELL_VAR *temp;
char *w;
/* This is a potential memory leak. The code should really save
the created variables in some auxiliary data structure, which
can be disposed of at the appropriate time. */
temp = new_shell_variable (name);
w = array[i] + l + 1;
temp->value = *w ? savestring (w) : (char *)NULL;
temp->attributes = att_exported|att_tempvar;
temp->context = 0;
temp->prev_context = (SHELL_VAR *)NULL;
temp->dynamic_value = temp->assign_func = (DYNAMIC_FUNC *)NULL;
return (temp);
}
}
return ((SHELL_VAR *)NULL);
}
/* Find a variable in the temporary environment that is named NAME.
The temporary environment can be either the environment provided
to a simple command, or the environment provided to a shell function.
We only search the function environment if we are currently executing
a shell function body (variable_context > 0). Return a consed variable,
or NULL if not found. */
SHELL_VAR *
find_tempenv_variable (name)
char *name;
{
SHELL_VAR *var;
var = (SHELL_VAR *)NULL;
if (temporary_env)
var = find_name_in_env_array (name, temporary_env);
/* We don't check this_shell_builtin because the command that needs the
value from builtin_env may be a disk command run inside a script run
with `.' and a temporary env. */
if (!var && builtin_env)
var = find_name_in_env_array (name, builtin_env);
if (!var && variable_context && function_env)
var = find_name_in_env_array (name, function_env);
return (var);
}
/* Free the storage allocated to the string array pointed to by ARRAYP, and
make that variable have a null pointer as a value. */
static void
dispose_temporary_vars (arrayp)
char ***arrayp;
{
if (!*arrayp)
return;
free_array (*arrayp);
*arrayp = (char **)NULL;
array_needs_making = 1;
}
/* Free the storage used in the variable array for temporary
environment variables. */
void
dispose_used_env_vars ()
{
dispose_temporary_vars (&temporary_env);
}
/* Free the storage used for temporary environment variables given to
commands when executing inside of a function body. */
void
dispose_function_env ()
{
dispose_temporary_vars (&function_env);
}
/* Free the storage used for temporary environment variables given to
commands when executing a builtin command such as "source". */
void
dispose_builtin_env ()
{
dispose_temporary_vars (&builtin_env);
}
/* Take all of the shell variables in ENV_ARRAY and make shell variables
from them at the current variable context. */
static void
merge_env_array (env_array)
char **env_array;
{
register int i, l;
SHELL_VAR *temp;
char *val, *name;
if (env_array == 0)
return;
for (i = 0; env_array[i]; i++)
{
l = assignment (env_array[i]);
name = env_array[i];
val = env_array[i] + l + 1;
name[l] = '\0';
temp = bind_variable (name, val);
name[l] = '=';
}
}
void
merge_temporary_env ()
{
merge_env_array (temporary_env);
}
void
merge_builtin_env ()
{
merge_env_array (builtin_env);
}
/* 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 = (char **)xrealloc (export_env, export_env_size * sizeof (char *)); \
} \
export_env[export_env_index++] = (do_alloc) ? savestring (envstr) : envstr; \
export_env[export_env_index] = (char *)NULL; \
} while (0)
#define ISFUNCTION(s, o) ((s[o + 1] == '(') && (s[o + 2] == ')'))
/* 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);
if (equal_offset == 0)
return (export_env);
/* If this is a function, then only supercede the function definition.
We do this by including the `=(' in the comparison. */
if (assign[equal_offset + 1] == '(')
equal_offset++;
for (i = 0; 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);
}
/* 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++'. */
void
maybe_make_export_env ()
{
register int i;
register char **temp_array;
int new_size;
if (array_needs_making)
{
if (export_env)
free_array_members (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, without any temporary
or function environments. */
new_size = shell_variables->nentries + shell_functions->nentries + 1;
if (new_size > export_env_size)
{
export_env_size = new_size;
export_env = (char **)xrealloc (export_env, export_env_size * sizeof (char *));
}
export_env[export_env_index = 0] = (char *)NULL;
temp_array = make_var_array (shell_variables);
if (temp_array)
{
for (i = 0; temp_array[i]; i++)
add_to_export_env (temp_array[i], 0);
free (temp_array);
}
temp_array = make_var_array (shell_functions);
if (temp_array)
{
for (i = 0; temp_array[i]; i++)
add_to_export_env (temp_array[i], 0);
free (temp_array);
}
if (function_env)
for (i = 0; function_env[i]; i++)
export_env = add_or_supercede_exported_var (function_env[i], 1);
if (temporary_env)
for (i = 0; temporary_env[i]; i++)
export_env = add_or_supercede_exported_var (temporary_env[i], 1);
#if 0
/* If we changed the array, then sort it alphabetically. */
if (posixly_correct == 0 && (temporary_env || function_env))
sort_char_array (export_env);
#endif
array_needs_making = 0;
}
}
/* We always put _ in the environment as the name of this command. */
void
put_command_name_into_env (command_name)
char *command_name;
{
char *dummy;
dummy = xmalloc (4 + strlen (command_name));
/* These three statements replace a call to sprintf */
dummy[0] = '_';
dummy[1] = '=';
strcpy (dummy + 2, command_name);
export_env = add_or_supercede_exported_var (dummy, 0);
}
#if 0 /* UNUSED -- it caused too many problems */
void
put_gnu_argv_flags_into_env (pid, flags_string)
int pid;
char *flags_string;
{
char *dummy, *pbuf;
int l, fl;
pbuf = itos (pid);
l = strlen (pbuf);
fl = strlen (flags_string);
dummy = xmalloc (l + fl + 30);
dummy[0] = '_';
strcpy (dummy + 1, pbuf);
strcpy (dummy + 1 + l, "_GNU_nonoption_argv_flags_");
dummy[l + 27] = '=';
strcpy (dummy + l + 28, flags_string);
free (pbuf);
export_env = add_or_supercede_exported_var (dummy, 0);
}
#endif
/* Return a string denoting what our indirection level is. */
static char indirection_string[100];
char *
indirection_level_string ()
{
register int i, j;
char *ps4;
indirection_string[0] = '\0';
ps4 = get_string_value ("PS4");
if (ps4 == 0 || *ps4 == '\0')
return (indirection_string);
ps4 = decode_prompt_string (ps4);
for (i = 0; *ps4 && i < indirection_level && i < 99; i++)
indirection_string[i] = *ps4;
for (j = 1; *ps4 && ps4[j] && i < 99; i++, j++)
indirection_string[i] = ps4[j];
indirection_string[i] = '\0';
free (ps4);
return (indirection_string);
}
/*************************************************
* *
* Functions to manage special variables *
* *
*************************************************/
/* Extern declarations for variables this code has to manage. */
extern int eof_encountered, eof_encountered_limit, ignoreeof;
#if defined (READLINE)
extern int no_line_editing;
extern int hostname_list_initialized;
#endif
/* An alist of name.function for each special variable. Most of the
functions don't do much, and in fact, this would be faster with a
switch statement, but by the end of this file, I am sick of switch
statements. */
#define SET_INT_VAR(name, intvar) intvar = find_variable (name) != 0
struct name_and_function {
char *name;
VFunction *function;
} special_vars[] = {
{ "PATH", sv_path },
{ "MAIL", sv_mail },
{ "MAILPATH", sv_mail },
{ "MAILCHECK", sv_mail },
{ "POSIXLY_CORRECT", sv_strict_posix },
{ "GLOBIGNORE", sv_globignore },
/* Variables which only do something special when READLINE is defined. */
#if defined (READLINE)
{ "TERM", sv_terminal },
{ "TERMCAP", sv_terminal },
{ "TERMINFO", sv_terminal },
{ "HOSTFILE", sv_hostfile },
#endif /* READLINE */
/* Variables which only do something special when HISTORY is defined. */
#if defined (HISTORY)
{ "HISTIGNORE", sv_histignore },
{ "HISTSIZE", sv_histsize },
{ "HISTFILESIZE", sv_histsize },
{ "HISTCONTROL", sv_history_control },
# if defined (BANG_HISTORY)
{ "histchars", sv_histchars },
# endif /* BANG_HISTORY */
#endif /* HISTORY */
{ "IGNOREEOF", sv_ignoreeof },
{ "ignoreeof", sv_ignoreeof },
{ "OPTIND", sv_optind },
{ "OPTERR", sv_opterr },
{ "TEXTDOMAIN", sv_locale },
{ "TEXTDOMAINDIR", sv_locale },
{ "LC_ALL", sv_locale },
{ "LC_COLLATE", sv_locale },
{ "LC_CTYPE", sv_locale },
{ "LC_MESSAGES", sv_locale },
{ "LANG", sv_locale },
#if defined (HAVE_TZSET) && defined (PROMPT_STRING_DECODE)
{ "TZ", sv_tz },
#endif
{ (char *)0, (VFunction *)0 }
};
/* 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;
{
int i;
for (i = 0; special_vars[i].name; i++)
{
if (STREQ (special_vars[i].name, name))
{
(*(special_vars[i].function)) (name);
return;
}
}
}
/* What to do just after the PATH variable has changed. */
void
sv_path (name)
char *name;
{
/* hash -r */
flush_hashed_filenames ();
}
/* 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 ();
}
}
/* What to do when GLOBIGNORE changes. */
void
sv_globignore (name)
char *name;
{
setup_glob_ignore (name);
}
#if defined (READLINE)
/* 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;
{
hostname_list_initialized = 0;
}
#endif /* READLINE */
#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;
long num;
temp = get_string_value (name);
if (temp && *temp)
{
if (legal_number (temp, &num))
{
if (name[4] == 'S')
{
stifle_history (num);
num = where_history ();
if (history_lines_this_session > num)
history_lines_this_session = num;
}
else
{
history_truncate_file (get_string_value ("HISTFILE"), (int)num);
if (num <= history_lines_in_file)
history_lines_in_file = num;
}
}
}
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;
history_control = 0;
temp = get_string_value (name);
if (temp && *temp && STREQN (temp, "ignore", 6))
{
if (temp[6] == 's') /* ignorespace */
history_control = 1;
else if (temp[6] == 'd') /* ignoredups */
history_control = 2;
else if (temp[6] == 'b') /* ignoreboth */
history_control = 3;
}
}
#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 */
#endif /* HISTORY */
#if defined (HAVE_TZSET) && defined (PROMPT_STRING_DECODE)
void
sv_tz (name)
char *name;
{
tzset ();
}
#endif
/* If the variable exists, then the value of it can be the number
of times we actually ignore the EOF. The default is small,
(smaller than csh, anyway). */
void
sv_ignoreeof (name)
char *name;
{
SHELL_VAR *tmp_var;
char *temp;
eof_encountered = 0;
tmp_var = find_variable (name);
ignoreeof = tmp_var != 0;
temp = tmp_var ? value_cell (tmp_var) : (char *)NULL;
if (temp)
eof_encountered_limit = (*temp && all_digits (temp)) ? atoi (temp) : 10;
set_shellopts (); /* make sure `ignoreeof' is/is not in $SHELLOPTS */
}
void
sv_optind (name)
char *name;
{
char *tt;
int s;
tt = get_string_value ("OPTIND");
if (tt && *tt)
{
s = atoi (tt);
/* According to POSIX, setting OPTIND=1 resets the internal state
of getopt (). */
if (s < 0 || s == 1)
s = 0;
}
else
s = 0;
getopts_reset (s);
}
void
sv_opterr (name)
char *name;
{
char *tt;
tt = get_string_value ("OPTERR");
sh_opterr = (tt && *tt) ? atoi (tt) : 1;
}
void
sv_strict_posix (name)
char *name;
{
SET_INT_VAR (name, posixly_correct);
posix_initialize (posixly_correct);
#if defined (READLINE)
if (interactive_shell)
posix_readline_initialize (posixly_correct);
#endif /* READLINE */
set_shellopts (); /* make sure `posix' is/is not in $SHELLOPTS */
}
void
sv_locale (name)
char *name;
{
char *v;
v = get_string_value (name);
if (name[0] == 'L' && name[1] == 'A') /* LANG */
set_lang (name, v);
else
set_locale_var (name, v); /* LC_*, TEXTDOMAIN* */
}
#if defined (ARRAY_VARS)
void
set_pipestatus_array (ps)
int *ps;
{
SHELL_VAR *v;
ARRAY *a;
register int i;
char *t;
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)
empty_array (a);
for (i = 0; ps[i] != -1; i++)
{
t = itos (ps[i]);
array_add_element (a, i, t);
free (t);
}
}
#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);
#endif
}