- some typo fixes from Aharon Robbins <arnold@skeeve.com>
- added descriptions of ENV, COPROC, and MAPFILE variables
- added descriptions of READLINE_LINE and READLINE_POINT
+
+ 1/21
+ ----
+arrayfunc.c
+ - free `akey', the word-expanded index into the assoc array to avoid
+ mem leak in array_value_internal
+ - free index into assoc array in unbind_array_element
+ - change array_value_internal to take an additional argument: an
+ arrayind_t *. If not null, an index to an indexed array is
+ returned there. If not an indexed array or subscript is @ or
+ *, the value is unchanged
+
+ 1/22
+ ----
+builtins/ulimit.def
+ - include <ulimit.h> if we found it during configure and we don't
+ have resources. Fixes omission reported by Joachim Schmitz
+ <jojo@schmitz-digital.de>
+
+{configure,config.h}.in
+ - check for <ulimit.h>, define HAVE_ULIMIT_H if found
+
+lib/sh/oslib.c
+ - include <signal.h> for extern declaration for kill(2) if
+ HAVE_KILLPG not defined
+
+jobs.c
+ - if HAVE_KILLPG is not defined, add an extern prototype decl for
+ killpg()
+
+ 1/24
+ ----
+print_cmd.c
+ - when printing here-string redirections, don't quote the string. The
+ original quotes, if any, are still in place and don't need to be
+ requoted. Fixes bug reported by Arfrever Frehtes Taifersar Arahesis
+ <arfrever.fta@gmail.com>
+
+subst.c
+ - fix array_length_reference to return 0 for variables that have not
+ been assigned a value. Fixes bug reported by Mart Frauenlab
+ <mart.frauenlob@chello.at>, but is not backwards compatible
+
+arrayfunc.[ch]
+ - change array_value to take a new arrayind_t *indp parameter like
+ get_array_value; changed extern prototype declaration
+
+subst.c
+ - changed callers of array_value to add extra parameter
+
+expr.c
+ - change expr_streval to set a new `lvalue' parameter with information
+ about the string being evaluated: string, value, array index (if
+ any), variable evaluated (if set).
+ - saving and restoring current context now saves and restores the
+ current `lvalue'
+ - new function expr_bind_array_element, binds an array element with an
+ already-computed index to a specified value
+ - anywhere we set the current token to a string (STR), save and set
+ the current lvalue
+ - change calls to expr_bind_variable to check whether or not the
+ current lvalue indicates an indexed array was evaluated, and, if so,
+ call expr_bind_array_element using the already-computed index
+ (curlval.ind). Fixes problems with dynamic variables (e.g., RANDOM)
+ in array indices with {pre,post}-{inc,dec}rement and op=
+ operators reported by <dennis@netstrata.com>
+
+ 1/25
+ ----
+expr.c
+ - fix subexpr() to initialize curlval and lastlval when resetting all
+ of the rest of the expression-parsing variables
+
+ 1/26
+ ----
+builtins/setattr.def
+ - in show_var_attributes, if the variable is not set (value == 0),
+ don't print `name=""', just print `name'. Pointed out by
+ Mart Frauenlab <mart.frauenlob@chello.at>
+
+arrayfunc.c
+ - fix array_keys to return NULL if the variable is not set or
+ invisible. Pointed out by Mart Frauenlab <mart.frauenlob@chello.at>
+ - change array_value_internal to return NULL for variable which has
+ not been set
[bash-4.1 frozen]
+ 12/31
+ -----
+[bash-4.1 released]
+
1/5/2010
--------
doc/bashref.texi
builtins/printf.def
- fix prototype in extern declaration for vsnprintf. Fix for bug
reported by Yann Rouillard <yann@pleiades.fr.eu.org>
+
+ 1/9
+ ---
+parse.y
+ - fix shell_getc to handle alias expansions containing quoted
+ newlines. Problems in bash-4.1 with aliases containing quoted
+ newlines in the middle of and at the end of their expansion.
+ Fix for bug reported by Jonathan Claggett
+ <jonathan@claggett.org>
+ - change mk_alexpansion to not append a space to an alias
+ expansion ending with a newline. Works with shell_getc
+
+ 1/11
+ ----
+lib/glob/Makefile.in
+ - add dependencies on shell.h and pathnames.h. From Mike Frysinger
+ <vapier@gentoo.org>
+
+ 1/15
+ ----
+doc/{bash.1,{bashref,version}.texi},lib/readine/doc/rluser.texi
+ - some typo fixes from Aharon Robbins <arnold@skeeve.com>
+ - added descriptions of ENV, COPROC, and MAPFILE variables
+ - added descriptions of READLINE_LINE and READLINE_POINT
+
+ 1/21
+ ----
+arrayfunc.c
+ - free `akey', the word-expanded index into the assoc array to avoid
+ mem leak in array_value_internal
+ - free index into assoc array in unbind_array_element
+ - change array_value_internal to take an additional argument: an
+ arrayind_t *. If not null, an index to an indexed array is
+ returned there. If not an indexed array or subscript is @ or
+ *, the value is unchanged
+
+ 1/22
+ ----
+builtins/ulimit.def
+ - include <ulimit.h> if we found it during configure and we don't
+ have resources. Fixes omission reported by Joachim Schmitz
+ <jojo@schmitz-digital.de>
+
+{configure,config.h}.in
+ - check for <ulimit.h>, define HAVE_ULIMIT_H if found
+
+lib/sh/oslib.c
+ - include <signal.h> for extern declaration for kill(2) if
+ HAVE_KILLPG not defined
+
+jobs.c
+ - if HAVE_KILLPG is not defined, add an extern prototype decl for
+ killpg()
+
+ 1/24
+ ----
+print_cmd.c
+ - when printing here-string redirections, don't quote the string. The
+ original quotes, if any, are still in place and don't need to be
+ requoted. Fixes bug reported by Arfrever Frehtes Taifersar Arahesis
+ <arfrever.fta@gmail.com>
+
+subst.c
+ - fix array_length_reference to return 0 for variables that have not
+ been assigned a value. Fixes bug reported by Mart Frauenlab
+ <mart.frauenlob@chello.at>, but is not backwards compatible
+
+arrayfunc.[ch]
+ - change array_value to take a new arrayind_t *indp parameter like
+ get_array_value; changed extern prototype declaration
+
+subst.c
+ - changed callers of array_value to add extra parameter
+
+expr.c
+ - change expr_streval to set a new `lvalue' parameter with information
+ about the string being evaluated: string, value, array index (if
+ any), variable evaluated (if set).
+ - saving and restoring current context now saves and restores the
+ current `lvalue'
+ - new function expr_bind_array_element, binds an array element with an
+ already-computed index to a specified value
+ - anywhere we set the current token to a string (STR), save and set
+ the current lvalue
+ - change calls to expr_bind_variable to check whether or not the
+ current lvalue indicates an indexed array was evaluated, and, if so,
+ call expr_bind_array_element using the already-computed index
+ (curlval.ind). Fixes problems with dynamic variables (e.g., RANDOM)
+ in array indices with {pre,post}-{inc,dec}rement and op=
+ operators reported by <dennis@netstrata.com>
+
+ 1/25
+ ----
+expr.c
+ - fix subexpr() to initialize curlval and lastlval when resetting all
+ of the rest of the expression-parsing variables
+
+ 1/26
+ ----
+builtins/setattr.def
+ - in show_var_attributes, if the variable is not set (value == 0),
+ don't print `name=""', just print `name'. Pointed out by
+ Mart Frauenlab <mart.frauenlob@chello.at>
+
+arrayfunc.c
+ - fix array_keys to return NULL if the variable is not set or
+ invisible. Pointed out by Mart Frauenlab <mart.frauenlob@chello.at>
static char *quote_assign __P((const char *));
static void quote_array_assignment_chars __P((WORD_LIST *));
-static char *array_value_internal __P((char *, int, int, int *));
+static char *array_value_internal __P((char *, int, int, int *, arrayind_t *));
/* Standard error message to use when encountering an invalid array subscript */
const char * const bash_badsub_errmsg = N_("bad array subscript");
return -1;
}
assoc_remove (assoc_cell (var), akey);
+ free (akey);
}
else
{
is non-null it gets 1 if the array reference is name[*], 2 if the
reference is name[@], and 0 otherwise. */
static char *
-array_value_internal (s, quoted, allow_all, rtype)
+array_value_internal (s, quoted, allow_all, rtype, indp)
char *s;
int quoted, allow_all, *rtype;
+ arrayind_t *indp;
{
int len;
arrayind_t ind;
err_badarraysub (s);
return ((char *)NULL);
}
- else if (var == 0 || value_cell (var) == 0)
+ else if (var == 0 || value_cell (var) == 0) /* XXX - check for invisible_p(var) ? */
return ((char *)NULL);
else if (array_p (var) == 0 && assoc_p (var) == 0)
l = add_string_to_list (value_cell (var), (WORD_LIST *)NULL);
}
return ((char *)NULL);
}
+ if (indp)
+ *indp = ind;
}
else if (assoc_p (var))
{
goto index_error;
}
- if (var == 0)
+ if (var == 0 || value_cell (var) == 0) /* XXX - check invisible_p(var) ? */
return ((char *)NULL);
if (array_p (var) == 0 && assoc_p (var) == 0)
return (ind == 0 ? value_cell (var) : (char *)NULL);
else if (assoc_p (var))
- retval = assoc_reference (assoc_cell (var), akey);
+ {
+ retval = assoc_reference (assoc_cell (var), akey);
+ free (akey);
+ }
else
retval = array_reference (array_cell (var), ind);
}
/* Return a string containing the elements described by the array and
subscript contained in S, obeying quoting for subscripts * and @. */
char *
-array_value (s, quoted, rtype)
+array_value (s, quoted, rtype, indp)
char *s;
int quoted, *rtype;
+ arrayind_t *indp;
{
- return (array_value_internal (s, quoted, 1, rtype));
+ return (array_value_internal (s, quoted, 1, rtype, indp));
}
/* Return the value of the array indexing expression S as a single string.
by other parts of the shell such as the arithmetic expression evaluator
in expr.c. */
char *
-get_array_value (s, allow_all, rtype)
+get_array_value (s, allow_all, rtype, indp)
char *s;
int allow_all, *rtype;
+ arrayind_t *indp;
{
- return (array_value_internal (s, 0, allow_all, rtype));
+ return (array_value_internal (s, 0, allow_all, rtype, indp));
}
char *
if (var == 0 || ALL_ELEMENT_SUB (t[0]) == 0 || t[1] != ']')
return (char *)NULL;
+ if (var_isset (var) == 0 || invisible_p (var))
+ return (char *)NULL;
+
if (array_p (var) == 0 && assoc_p (var) == 0)
l = add_string_to_list ("0", (WORD_LIST *)NULL);
else if (assoc_p (var))
--- /dev/null
+/* arrayfunc.c -- High-level array functions used by other parts of the shell. */
+
+/* Copyright (C) 2001-2009 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"
+
+#if defined (ARRAY_VARS)
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+#include <stdio.h>
+
+#include "bashintl.h"
+
+#include "shell.h"
+#include "pathexp.h"
+
+#include "shmbutil.h"
+
+#include "builtins/common.h"
+
+extern char *this_command_name;
+extern int last_command_exit_value;
+extern int array_needs_making;
+
+static SHELL_VAR *bind_array_var_internal __P((SHELL_VAR *, arrayind_t, char *, char *, int));
+
+static char *quote_assign __P((const char *));
+static void quote_array_assignment_chars __P((WORD_LIST *));
+static char *array_value_internal __P((char *, int, int, int *, arrayind_t *));
+
+/* Standard error message to use when encountering an invalid array subscript */
+const char * const bash_badsub_errmsg = N_("bad array subscript");
+
+/* **************************************************************** */
+/* */
+/* Functions to manipulate array variables and perform assignments */
+/* */
+/* **************************************************************** */
+
+/* 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 = array_create ();
+ if (oldval)
+ array_insert (array, 0, oldval);
+
+ FREE (value_cell (var));
+ var_setarray (var, array);
+
+ /* these aren't valid anymore */
+ var->dynamic_value = (sh_var_value_func_t *)NULL;
+ var->assign_func = (sh_var_assign_func_t *)NULL;
+
+ INVALIDATE_EXPORTSTR (var);
+ if (exported_p (var))
+ array_needs_making++;
+
+ VSETATTR (var, att_array);
+ VUNSETATTR (var, att_invisible);
+
+ return var;
+}
+
+/* Convert a shell variable to an array variable. The original value is
+ saved as array[0]. */
+SHELL_VAR *
+convert_var_to_assoc (var)
+ SHELL_VAR *var;
+{
+ char *oldval;
+ HASH_TABLE *hash;
+
+ oldval = value_cell (var);
+ hash = assoc_create (0);
+ if (oldval)
+ assoc_insert (hash, savestring ("0"), oldval);
+
+ FREE (value_cell (var));
+ var_setassoc (var, hash);
+
+ /* these aren't valid anymore */
+ var->dynamic_value = (sh_var_value_func_t *)NULL;
+ var->assign_func = (sh_var_assign_func_t *)NULL;
+
+ INVALIDATE_EXPORTSTR (var);
+ if (exported_p (var))
+ array_needs_making++;
+
+ VSETATTR (var, att_assoc);
+ VUNSETATTR (var, att_invisible);
+
+ return var;
+}
+
+static SHELL_VAR *
+bind_array_var_internal (entry, ind, key, value, flags)
+ SHELL_VAR *entry;
+ arrayind_t ind;
+ char *key;
+ char *value;
+ int flags;
+{
+ SHELL_VAR *dentry;
+ char *newval;
+
+ /* If we're appending, we need the old value of the array reference, so
+ fake out make_variable_value with a dummy SHELL_VAR */
+ if (flags & ASS_APPEND)
+ {
+ dentry = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
+ dentry->name = savestring (entry->name);
+ if (assoc_p (entry))
+ newval = assoc_reference (assoc_cell (entry), key);
+ else
+ newval = array_reference (array_cell (entry), ind);
+ if (newval)
+ dentry->value = savestring (newval);
+ else
+ {
+ dentry->value = (char *)xmalloc (1);
+ dentry->value[0] = '\0';
+ }
+ dentry->exportstr = 0;
+ dentry->attributes = entry->attributes & ~(att_array|att_assoc|att_exported);
+ /* Leave the rest of the members uninitialized; the code doesn't look
+ at them. */
+ newval = make_variable_value (dentry, value, flags);
+ dispose_variable (dentry);
+ }
+ else
+ newval = make_variable_value (entry, value, flags);
+
+ if (entry->assign_func)
+ (*entry->assign_func) (entry, newval, ind, key);
+ else if (assoc_p (entry))
+ assoc_insert (assoc_cell (entry), key, newval);
+ else
+ array_insert (array_cell (entry), ind, newval);
+ FREE (newval);
+
+ return (entry);
+}
+
+/* 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, flags)
+ char *name;
+ arrayind_t ind;
+ char *value;
+ int flags;
+{
+ SHELL_VAR *entry;
+
+ entry = var_lookup (name, shell_variables);
+
+ if (entry == (SHELL_VAR *) 0)
+ entry = make_new_array_variable (name);
+ else if (readonly_p (entry) || noassign_p (entry))
+ {
+ if (readonly_p (entry))
+ err_readonly (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. */
+ return (bind_array_var_internal (entry, ind, 0, value, flags));
+}
+
+SHELL_VAR *
+bind_array_element (entry, ind, value, flags)
+ SHELL_VAR *entry;
+ arrayind_t ind;
+ char *value;
+ int flags;
+{
+ return (bind_array_var_internal (entry, ind, 0, value, flags));
+}
+
+SHELL_VAR *
+bind_assoc_variable (entry, name, key, value, flags)
+ SHELL_VAR *entry;
+ char *name;
+ char *key;
+ char *value;
+ int flags;
+{
+ SHELL_VAR *dentry;
+ char *newval;
+
+ if (readonly_p (entry) || noassign_p (entry))
+ {
+ if (readonly_p (entry))
+ err_readonly (name);
+ return (entry);
+ }
+
+ return (bind_array_var_internal (entry, 0, key, value, flags));
+}
+
+/* Parse NAME, a lhs of an assignment statement of the form v[s], and
+ assign VALUE to that array element by calling bind_array_variable(). */
+SHELL_VAR *
+assign_array_element (name, value, flags)
+ char *name, *value;
+ int flags;
+{
+ char *sub, *vname, *akey;
+ arrayind_t ind;
+ int sublen;
+ SHELL_VAR *entry;
+
+ vname = array_variable_name (name, &sub, &sublen);
+
+ if (vname == 0)
+ return ((SHELL_VAR *)NULL);
+
+ if ((ALL_ELEMENT_SUB (sub[0]) && sub[1] == ']') || (sublen <= 1))
+ {
+ free (vname);
+ err_badarraysub (name);
+ return ((SHELL_VAR *)NULL);
+ }
+
+ entry = find_variable (vname);
+
+ if (entry && assoc_p (entry))
+ {
+ sub[sublen-1] = '\0';
+ akey = expand_assignment_string_to_string (sub, 0); /* [ */
+ sub[sublen-1] = ']';
+ if (akey == 0 || *akey == 0)
+ {
+ free (vname);
+ err_badarraysub (name);
+ return ((SHELL_VAR *)NULL);
+ }
+ entry = bind_assoc_variable (entry, vname, akey, value, flags);
+ }
+ else
+ {
+ ind = array_expand_index (sub, sublen);
+ if (ind < 0)
+ {
+ free (vname);
+ err_badarraysub (name);
+ return ((SHELL_VAR *)NULL);
+ }
+ entry = bind_array_variable (vname, ind, value, flags);
+ }
+
+ free (vname);
+ return (entry);
+}
+
+/* Find the array variable corresponding to NAME. If there is no variable,
+ create a new array variable. If the variable exists but is not an array,
+ convert it to an indexed array. If FLAGS&1 is non-zero, an existing
+ variable is checked for the readonly or noassign attribute in preparation
+ for assignment (e.g., by the `read' builtin). If FLAGS&2 is non-zero, we
+ create an associative array. */
+SHELL_VAR *
+find_or_make_array_variable (name, flags)
+ char *name;
+ int flags;
+{
+ SHELL_VAR *var;
+
+ var = find_variable (name);
+
+ if (var == 0)
+ var = (flags & 2) ? make_new_assoc_variable (name) : make_new_array_variable (name);
+ else if ((flags & 1) && (readonly_p (var) || noassign_p (var)))
+ {
+ if (readonly_p (var))
+ err_readonly (name);
+ return ((SHELL_VAR *)NULL);
+ }
+ else if ((flags & 2) && array_p (var))
+ {
+ report_error (_("%s: cannot convert indexed to associative array"), name);
+ return ((SHELL_VAR *)NULL);
+ }
+ else if (array_p (var) == 0 && assoc_p (var) == 0)
+ var = convert_var_to_array (var);
+
+ return (var);
+}
+
+/* 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, flags)
+ char *name, *value;
+ int flags;
+{
+ SHELL_VAR *var;
+ int vflags;
+
+ vflags = 1;
+ if (flags & ASS_MKASSOC)
+ vflags |= 2;
+
+ var = find_or_make_array_variable (name, vflags);
+ if (var == 0)
+ return ((SHELL_VAR *)NULL);
+
+ return (assign_array_var_from_string (var, value, flags));
+}
+
+/* Sequentially assign the indices of indexed array variable VAR from the
+ words in LIST. */
+SHELL_VAR *
+assign_array_var_from_word_list (var, list, flags)
+ SHELL_VAR *var;
+ WORD_LIST *list;
+ int flags;
+{
+ register arrayind_t i;
+ register WORD_LIST *l;
+ ARRAY *a;
+
+ a = array_cell (var);
+ i = (flags & ASS_APPEND) ? array_max_index (a) + 1 : 0;
+
+ for (l = list; l; l = l->next, i++)
+ if (var->assign_func)
+ (*var->assign_func) (var, l->word->word, i, 0);
+ else
+ array_insert (a, i, l->word->word);
+ return var;
+}
+
+WORD_LIST *
+expand_compound_array_assignment (var, value, flags)
+ SHELL_VAR *var;
+ char *value;
+ int flags;
+{
+ WORD_LIST *list, *nlist;
+ char *val;
+ int ni;
+
+ /* I don't believe this condition is ever true any more. */
+ if (*value == '(') /*)*/
+ {
+ ni = 1;
+ val = extract_array_assignment_list (value, &ni);
+ if (val == 0)
+ return (WORD_LIST *)NULL;
+ }
+ else
+ val = value;
+
+ /* Expand the value string into a list of words, performing all the
+ shell expansions including pathname generation and word splitting. */
+ /* First we split the string on whitespace, using the shell parser
+ (ksh93 seems to do this). */
+ list = parse_string_to_word_list (val, 1, "array assign");
+
+ /* If we're using [subscript]=value, we need to quote each [ and ] to
+ prevent unwanted filename expansion. */
+ if (list)
+ quote_array_assignment_chars (list);
+
+ /* Now that we've split it, perform the shell expansions on each
+ word in the list. */
+ nlist = list ? expand_words_no_vars (list) : (WORD_LIST *)NULL;
+
+ dispose_words (list);
+
+ if (val != value)
+ free (val);
+
+ return nlist;
+}
+
+/* Callers ensure that VAR is not NULL */
+void
+assign_compound_array_list (var, nlist, flags)
+ SHELL_VAR *var;
+ WORD_LIST *nlist;
+ int flags;
+{
+ ARRAY *a;
+ HASH_TABLE *h;
+ WORD_LIST *list;
+ char *w, *val, *nval;
+ int len, iflags;
+ arrayind_t ind, last_ind;
+ char *akey;
+
+ a = (var && array_p (var)) ? array_cell (var) : (ARRAY *)0;
+ h = (var && assoc_p (var)) ? assoc_cell (var) : (HASH_TABLE *)0;
+
+ akey = (char *)0;
+ ind = 0;
+
+ /* Now that we are ready to assign values to the array, kill the existing
+ value. */
+ if ((flags & ASS_APPEND) == 0)
+ {
+ if (a && array_p (var))
+ array_flush (a);
+ else if (h && assoc_p (var))
+ assoc_flush (h);
+ }
+
+ last_ind = (a && (flags & ASS_APPEND)) ? array_max_index (a) + 1 : 0;
+
+ for (list = nlist; list; list = list->next)
+ {
+ iflags = flags;
+ w = list->word->word;
+
+ /* We have a word of the form [ind]=value */
+ if ((list->word->flags & W_ASSIGNMENT) && w[0] == '[')
+ {
+ len = skipsubscript (w, 0, (var && assoc_p (var) != 0));
+
+ /* XXX - changes for `+=' */
+ if (w[len] != ']' || (w[len+1] != '=' && (w[len+1] != '+' || w[len+2] != '=')))
+ {
+ if (assoc_p (var))
+ {
+ err_badarraysub (w);
+ continue;
+ }
+ nval = make_variable_value (var, w, flags);
+ if (var->assign_func)
+ (*var->assign_func) (var, nval, last_ind, 0);
+ else
+ array_insert (a, last_ind, nval);
+ FREE (nval);
+ last_ind++;
+ continue;
+ }
+
+ if (len == 1)
+ {
+ err_badarraysub (w);
+ continue;
+ }
+
+ if (ALL_ELEMENT_SUB (w[1]) && len == 2)
+ {
+ if (assoc_p (var))
+ report_error (_("%s: invalid associative array key"), w);
+ else
+ report_error (_("%s: cannot assign to non-numeric index"), w);
+ continue;
+ }
+
+ if (array_p (var))
+ {
+ ind = array_expand_index (w + 1, len);
+ if (ind < 0)
+ {
+ err_badarraysub (w);
+ continue;
+ }
+
+ last_ind = ind;
+ }
+ else if (assoc_p (var))
+ {
+ akey = substring (w, 1, len);
+ if (akey == 0 || *akey == 0)
+ {
+ err_badarraysub (w);
+ continue;
+ }
+ }
+
+ /* XXX - changes for `+=' -- just accept the syntax. ksh93 doesn't do this */
+ if (w[len + 1] == '+' && w[len + 2] == '=')
+ {
+ iflags |= ASS_APPEND;
+ val = w + len + 3;
+ }
+ else
+ val = w + len + 2;
+ }
+ else if (assoc_p (var))
+ {
+ report_error (_("%s: %s: must use subscript when assigning associative array"), var->name, w);
+ continue;
+ }
+ 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 */
+ bind_array_var_internal (var, ind, akey, val, iflags);
+ last_ind++;
+ }
+}
+
+/* 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, flags)
+ SHELL_VAR *var;
+ char *value;
+ int flags;
+{
+ WORD_LIST *nlist;
+
+ if (value == 0)
+ return var;
+
+ nlist = expand_compound_array_assignment (var, value, flags);
+ assign_compound_array_list (var, nlist, flags);
+
+ if (nlist)
+ dispose_words (nlist);
+ return (var);
+}
+
+/* Quote globbing chars and characters in $IFS before the `=' in an assignment
+ statement (usually a compound array assignment) to protect them from
+ unwanted filename expansion or word splitting. */
+static char *
+quote_assign (string)
+ const char *string;
+{
+ size_t slen;
+ int saw_eq;
+ char *temp, *t, *subs;
+ const char *s, *send;
+ int ss, se;
+ DECLARE_MBSTATE;
+
+ slen = strlen (string);
+ send = string + slen;
+
+ t = temp = (char *)xmalloc (slen * 2 + 1);
+ saw_eq = 0;
+ for (s = string; *s; )
+ {
+ if (*s == '=')
+ saw_eq = 1;
+ if (saw_eq == 0 && *s == '[') /* looks like a subscript */
+ {
+ ss = s - string;
+ se = skipsubscript (string, ss, 0);
+ subs = substring (s, ss, se);
+ *t++ = '\\';
+ strcpy (t, subs);
+ t += se - ss;
+ *t++ = '\\';
+ *t++ = ']';
+ s += se + 1;
+ free (subs);
+ continue;
+ }
+ if (saw_eq == 0 && (glob_char_p (s) || isifs (*s)))
+ *t++ = '\\';
+
+ COPY_CHAR_P (t, s, send);
+ }
+ *t = '\0';
+ return temp;
+}
+
+/* For each word in a compound array assignment, if the word looks like
+ [ind]=value, quote globbing chars and characters in $IFS before the `='. */
+static void
+quote_array_assignment_chars (list)
+ WORD_LIST *list;
+{
+ char *nword;
+ WORD_LIST *l;
+
+ for (l = list; l; l = l->next)
+ {
+ if (l->word == 0 || l->word->word == 0 || l->word->word[0] == '\0')
+ continue; /* should not happen, but just in case... */
+ /* Don't bother if it doesn't look like [ind]=value */
+ if (l->word->word[0] != '[' || mbschr (l->word->word, '=') == 0) /* ] */
+ continue;
+ nword = quote_assign (l->word->word);
+ free (l->word->word);
+ l->word->word = nword;
+ }
+}
+
+/* skipsubscript moved to subst.c to use private functions. 2009/02/24. */
+
+/* This function is called with SUB pointing to just after the beginning
+ `[' of an array subscript and removes the array element to which SUB
+ expands from array VAR. A subscript of `*' or `@' unsets the array. */
+int
+unbind_array_element (var, sub)
+ SHELL_VAR *var;
+ char *sub;
+{
+ int len;
+ arrayind_t ind;
+ char *akey;
+ ARRAY_ELEMENT *ae;
+
+ len = skipsubscript (sub, 0, 0);
+ if (sub[len] != ']' || len == 0)
+ {
+ builtin_error ("%s[%s: %s", var->name, sub, _(bash_badsub_errmsg));
+ return -1;
+ }
+ sub[len] = '\0';
+
+ if (ALL_ELEMENT_SUB (sub[0]) && sub[1] == 0)
+ {
+ unbind_variable (var->name);
+ return (0);
+ }
+
+ if (assoc_p (var))
+ {
+ akey = expand_assignment_string_to_string (sub, 0); /* [ */
+ if (akey == 0 || *akey == 0)
+ {
+ builtin_error ("[%s]: %s", sub, _(bash_badsub_errmsg));
+ return -1;
+ }
+ assoc_remove (assoc_cell (var), akey);
+ free (akey);
+ }
+ else
+ {
+ ind = array_expand_index (sub, len+1);
+ if (ind < 0)
+ {
+ builtin_error ("[%s]: %s", sub, _(bash_badsub_errmsg));
+ return -1;
+ }
+ ae = array_remove (array_cell (var), ind);
+ if (ae)
+ array_dispose_element (ae);
+ }
+
+ return 0;
+}
+
+/* Format and output an array assignment in compound form VAR=(VALUES),
+ suitable for re-use as input. */
+void
+print_array_assignment (var, quoted)
+ SHELL_VAR *var;
+ int quoted;
+{
+ char *vstr;
+
+ vstr = array_to_assign (array_cell (var), quoted);
+
+ if (vstr == 0)
+ printf ("%s=%s\n", var->name, quoted ? "'()'" : "()");
+ else
+ {
+ printf ("%s=%s\n", var->name, vstr);
+ free (vstr);
+ }
+}
+
+/* Format and output an associative array assignment in compound form
+ VAR=(VALUES), suitable for re-use as input. */
+void
+print_assoc_assignment (var, quoted)
+ SHELL_VAR *var;
+ int quoted;
+{
+ char *vstr;
+
+ vstr = assoc_to_assign (assoc_cell (var), quoted);
+
+ if (vstr == 0)
+ printf ("%s=%s\n", var->name, quoted ? "'()'" : "()");
+ else
+ {
+ printf ("%s=%s\n", var->name, vstr);
+ free (vstr);
+ }
+}
+
+/***********************************************************************/
+/* */
+/* Utility functions to manage arrays and their contents for expansion */
+/* */
+/***********************************************************************/
+
+/* Return 1 if NAME is a properly-formed array reference v[sub]. */
+int
+valid_array_reference (name)
+ char *name;
+{
+ char *t;
+ int r, len;
+
+ t = mbschr (name, '['); /* ] */
+ if (t)
+ {
+ *t = '\0';
+ r = legal_identifier (name);
+ *t = '[';
+ if (r == 0)
+ return 0;
+ /* Check for a properly-terminated non-blank subscript. */
+ len = skipsubscript (t, 0, 0);
+ if (t[len] != ']' || len == 1)
+ return 0;
+ for (r = 1; r < len; r++)
+ if (whitespace (t[r]) == 0)
+ return 1;
+ return 0;
+ }
+ return 0;
+}
+
+/* Expand the array index beginning at S and extending LEN characters. */
+arrayind_t
+array_expand_index (s, len)
+ char *s;
+ int len;
+{
+ char *exp, *t;
+ int expok;
+ arrayind_t val;
+
+ exp = (char *)xmalloc (len);
+ strncpy (exp, s, len - 1);
+ exp[len - 1] = '\0';
+ t = expand_arith_string (exp, 0);
+ this_command_name = (char *)NULL;
+ val = evalexp (t, &expok);
+ free (t);
+ free (exp);
+ if (expok == 0)
+ {
+ last_command_exit_value = EXECUTION_FAILURE;
+
+ top_level_cleanup ();
+ jump_to_top_level (DISCARD);
+ }
+ return val;
+}
+
+/* Return the name of the variable specified by S without any subscript.
+ If SUBP is non-null, return a pointer to the start of the subscript
+ in *SUBP. If LENP is non-null, the length of the subscript is returned
+ in *LENP. This returns newly-allocated memory. */
+char *
+array_variable_name (s, subp, lenp)
+ char *s, **subp;
+ int *lenp;
+{
+ char *t, *ret;
+ int ind, ni;
+
+ t = mbschr (s, '[');
+ if (t == 0)
+ {
+ if (subp)
+ *subp = t;
+ if (lenp)
+ *lenp = 0;
+ return ((char *)NULL);
+ }
+ ind = t - s;
+ ni = skipsubscript (s, ind, 0);
+ if (ni <= ind + 1 || s[ni] != ']')
+ {
+ err_badarraysub (s);
+ if (subp)
+ *subp = t;
+ if (lenp)
+ *lenp = 0;
+ return ((char *)NULL);
+ }
+
+ *t = '\0';
+ ret = savestring (s);
+ *t++ = '['; /* ] */
+
+ if (subp)
+ *subp = t;
+ if (lenp)
+ *lenp = ni - ind;
+
+ return ret;
+}
+
+/* Return the variable specified by S without any subscript. If SUBP is
+ non-null, return a pointer to the start of the subscript in *SUBP.
+ If LENP is non-null, the length of the subscript is returned in *LENP. */
+SHELL_VAR *
+array_variable_part (s, subp, lenp)
+ char *s, **subp;
+ int *lenp;
+{
+ char *t;
+ SHELL_VAR *var;
+
+ t = array_variable_name (s, subp, lenp);
+ if (t == 0)
+ return ((SHELL_VAR *)NULL);
+ var = find_variable (t);
+
+ free (t);
+ return (var == 0 || invisible_p (var)) ? (SHELL_VAR *)0 : var;
+}
+
+/* Return a string containing the elements in the array and subscript
+ described by S. If the subscript is * or @, obeys quoting rules akin
+ to the expansion of $* and $@ including double quoting. If RTYPE
+ is non-null it gets 1 if the array reference is name[*], 2 if the
+ reference is name[@], and 0 otherwise. */
+static char *
+array_value_internal (s, quoted, allow_all, rtype, indp)
+ char *s;
+ int quoted, allow_all, *rtype;
+ arrayind_t *indp;
+{
+ int len;
+ arrayind_t ind;
+ char *akey;
+ char *retval, *t, *temp;
+ WORD_LIST *l;
+ SHELL_VAR *var;
+
+ var = array_variable_part (s, &t, &len);
+
+ /* Expand the index, even if the variable doesn't exist, in case side
+ effects are needed, like ${w[i++]} where w is unset. */
+#if 0
+ if (var == 0)
+ return (char *)NULL;
+#endif
+
+ if (len == 0)
+ return ((char *)NULL); /* error message already printed */
+
+ /* [ */
+ if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']')
+ {
+ if (rtype)
+ *rtype = (t[0] == '*') ? 1 : 2;
+ if (allow_all == 0)
+ {
+ err_badarraysub (s);
+ return ((char *)NULL);
+ }
+ else if (var == 0 || value_cell (var) == 0)
+ return ((char *)NULL);
+ else if (array_p (var) == 0 && assoc_p (var) == 0)
+ l = add_string_to_list (value_cell (var), (WORD_LIST *)NULL);
+ else if (assoc_p (var))
+ {
+ l = assoc_to_word_list (assoc_cell (var));
+ if (l == (WORD_LIST *)NULL)
+ return ((char *)NULL);
+ }
+ else
+ {
+ l = array_to_word_list (array_cell (var));
+ if (l == (WORD_LIST *)NULL)
+ return ((char *) NULL);
+ }
+
+ if (t[0] == '*' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+ {
+ temp = string_list_dollar_star (l);
+ retval = quote_string (temp);
+ free (temp);
+ }
+ else /* ${name[@]} or unquoted ${name[*]} */
+ retval = string_list_dollar_at (l, quoted);
+
+ dispose_words (l);
+ }
+ else
+ {
+ if (rtype)
+ *rtype = 0;
+ if (var == 0 || array_p (var) || assoc_p (var) == 0)
+ {
+ ind = array_expand_index (t, len);
+ if (ind < 0)
+ {
+index_error:
+ if (var)
+ err_badarraysub (var->name);
+ else
+ {
+ t[-1] = '\0';
+ err_badarraysub (s);
+ t[-1] = '['; /* ] */
+ }
+ return ((char *)NULL);
+ }
+ if (indp)
+ *indp = ind;
+ }
+ else if (assoc_p (var))
+ {
+ t[len - 1] = '\0';
+ akey = expand_assignment_string_to_string (t, 0); /* [ */
+ t[len - 1] = ']';
+ if (akey == 0 || *akey == 0)
+ goto index_error;
+ }
+
+ if (var == 0)
+ return ((char *)NULL);
+ if (array_p (var) == 0 && assoc_p (var) == 0)
+ return (ind == 0 ? value_cell (var) : (char *)NULL);
+ else if (assoc_p (var))
+ {
+ retval = assoc_reference (assoc_cell (var), akey);
+ free (akey);
+ }
+ else
+ retval = array_reference (array_cell (var), ind);
+ }
+
+ return retval;
+}
+
+/* Return a string containing the elements described by the array and
+ subscript contained in S, obeying quoting for subscripts * and @. */
+char *
+array_value (s, quoted, rtype, indp)
+ char *s;
+ int quoted, *rtype;
+ arrayind_t *indp;
+{
+ return (array_value_internal (s, quoted, 1, rtype, indp));
+}
+
+/* Return the value of the array indexing expression S as a single string.
+ If ALLOW_ALL is 0, do not allow `@' and `*' subscripts. This is used
+ by other parts of the shell such as the arithmetic expression evaluator
+ in expr.c. */
+char *
+get_array_value (s, allow_all, rtype, indp)
+ char *s;
+ int allow_all, *rtype;
+ arrayind_t *indp;
+{
+ return (array_value_internal (s, 0, allow_all, rtype, indp));
+}
+
+char *
+array_keys (s, quoted)
+ char *s;
+ int quoted;
+{
+ int len;
+ char *retval, *t, *temp;
+ WORD_LIST *l;
+ SHELL_VAR *var;
+
+ var = array_variable_part (s, &t, &len);
+
+ /* [ */
+ if (var == 0 || ALL_ELEMENT_SUB (t[0]) == 0 || t[1] != ']')
+ return (char *)NULL;
+
+ if (var_isset (var) == 0 || invisible_p (var))
+ return (char *)NULL;
+
+ if (array_p (var) == 0 && assoc_p (var) == 0)
+ l = add_string_to_list ("0", (WORD_LIST *)NULL);
+ else if (assoc_p (var))
+ l = assoc_keys_to_word_list (assoc_cell (var));
+ else
+ l = array_keys_to_word_list (array_cell (var));
+ if (l == (WORD_LIST *)NULL)
+ return ((char *) NULL);
+
+ if (t[0] == '*' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+ {
+ temp = string_list_dollar_star (l);
+ retval = quote_string (temp);
+ free (temp);
+ }
+ else /* ${!name[@]} or unquoted ${!name[*]} */
+ retval = string_list_dollar_at (l, quoted);
+
+ dispose_words (l);
+ return retval;
+}
+#endif /* ARRAY_VARS */
extern arrayind_t array_expand_index __P((char *, int));
extern int valid_array_reference __P((char *));
-extern char *array_value __P((char *, int, int *));
-extern char *get_array_value __P((char *, int, int *));
+extern char *array_value __P((char *, int, int *, arrayind_t *));
+extern char *get_array_value __P((char *, int, int *, arrayind_t *));
extern char *array_keys __P((char *, int));
--- /dev/null
+/* arrayfunc.h -- declarations for miscellaneous array functions in arrayfunc.c */
+
+/* Copyright (C) 2001-2009 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/>.
+*/
+
+#if !defined (_ARRAYFUNC_H_)
+#define _ARRAYFUNC_H_
+
+/* Must include variables.h before including this file. */
+
+#if defined (ARRAY_VARS)
+
+extern SHELL_VAR *convert_var_to_array __P((SHELL_VAR *));
+extern SHELL_VAR *convert_var_to_assoc __P((SHELL_VAR *));
+
+extern SHELL_VAR *bind_array_variable __P((char *, arrayind_t, char *, int));
+extern SHELL_VAR *bind_array_element __P((SHELL_VAR *, arrayind_t, char *, int));
+extern SHELL_VAR *assign_array_element __P((char *, char *, int));
+
+extern SHELL_VAR *bind_assoc_variable __P((SHELL_VAR *, char *, char *, char *, int));
+
+extern SHELL_VAR *find_or_make_array_variable __P((char *, int));
+
+extern SHELL_VAR *assign_array_from_string __P((char *, char *, int));
+extern SHELL_VAR *assign_array_var_from_word_list __P((SHELL_VAR *, WORD_LIST *, int));
+
+extern WORD_LIST *expand_compound_array_assignment __P((SHELL_VAR *, char *, int));
+extern void assign_compound_array_list __P((SHELL_VAR *, WORD_LIST *, int));
+extern SHELL_VAR *assign_array_var_from_string __P((SHELL_VAR *, char *, int));
+
+extern int unbind_array_element __P((SHELL_VAR *, char *));
+extern int skipsubscript __P((const char *, int, int));
+
+extern void print_array_assignment __P((SHELL_VAR *, int));
+extern void print_assoc_assignment __P((SHELL_VAR *, int));
+
+extern arrayind_t array_expand_index __P((char *, int));
+extern int valid_array_reference __P((char *));
+extern char *array_value __P((char *, int, int *));
+extern char *get_array_value __P((char *, int, int *, arrayind_t *));
+
+extern char *array_keys __P((char *, int));
+
+extern char *array_variable_name __P((char *, char **, int *));
+extern SHELL_VAR *array_variable_part __P((char *, char **, int *));
+
+#endif
+
+#endif /* !_ARRAYFUNC_H_ */
@%:@! /bin/sh
-@%:@ From configure.in for Bash 4.1, version 4.019.
+@%:@ From configure.in for Bash 4.1, version 4.020.
@%:@ Guess values for system-dependent variables and create Makefiles.
-@%:@ Generated by GNU Autoconf 2.63 for bash 4.1-release.
+@%:@ Generated by GNU Autoconf 2.63 for bash 4.1-maint.
@%:@
@%:@ Report bugs to <bug-bash@gnu.org>.
@%:@
# Identity of this package.
PACKAGE_NAME='bash'
PACKAGE_TARNAME='bash'
-PACKAGE_VERSION='4.1-release'
-PACKAGE_STRING='bash 4.1-release'
+PACKAGE_VERSION='4.1-maint'
+PACKAGE_STRING='bash 4.1-maint'
PACKAGE_BUGREPORT='bug-bash@gnu.org'
ac_unique_file="shell.h"
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-\`configure' configures bash 4.1-release to adapt to many kinds of systems.
+\`configure' configures bash 4.1-maint to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of bash 4.1-release:";;
+ short | recursive ) echo "Configuration of bash 4.1-maint:";;
esac
cat <<\_ACEOF
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-bash configure 4.1-release
+bash configure 4.1-maint
generated by GNU Autoconf 2.63
Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by bash $as_me 4.1-release, which was
+It was created by bash $as_me 4.1-maint, which was
generated by GNU Autoconf 2.63. Invocation command line was
$ $0 $@
BASHVERS=4.1
-RELSTATUS=release
+RELSTATUS=maint
case "$RELSTATUS" in
alp*|bet*|dev*|rc*|maint*) DEBUG='-DDEBUG' MALLOC_DEBUG='-DMALLOC_DEBUG' ;;
+
for ac_header in unistd.h stdlib.h stdarg.h varargs.h limits.h string.h \
memory.h locale.h termcap.h termio.h termios.h dlfcn.h \
stddef.h stdint.h netdb.h pwd.h grp.h strings.h regex.h \
- syslog.h
+ syslog.h ulimit.h
do
as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by bash $as_me 4.1-release, which was
+This file was extended by bash $as_me 4.1-maint, which was
generated by GNU Autoconf 2.63. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
_ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_version="\\
-bash config.status 4.1-release
+bash config.status 4.1-maint
configured by $0, generated by GNU Autoconf 2.63,
with options \\"`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\"
-m4trace:configure.in:29: -1- AC_INIT([bash], [4.1-release], [bug-bash@gnu.org])
+m4trace:configure.in:29: -1- AC_INIT([bash], [4.1-maint], [bug-bash@gnu.org])
m4trace:configure.in:29: -1- m4_pattern_forbid([^_?A[CHUM]_])
m4trace:configure.in:29: -1- m4_pattern_forbid([_AC_])
m4trace:configure.in:29: -1- m4_pattern_forbid([^LIBOBJS$], [do not use LIBOBJS directly, use AC_LIBOBJ (see section `AC_LIBOBJ vs LIBOBJS'])
#undef HAVE_REGEX_H])
m4trace:configure.in:659: -1- AH_OUTPUT([HAVE_SYSLOG_H], [/* Define to 1 if you have the <syslog.h> header file. */
#undef HAVE_SYSLOG_H])
+m4trace:configure.in:659: -1- AH_OUTPUT([HAVE_ULIMIT_H], [/* Define to 1 if you have the <ulimit.h> header file. */
+#undef HAVE_ULIMIT_H])
m4trace:configure.in:663: -1- AH_OUTPUT([HAVE_SYS_PTE_H], [/* Define to 1 if you have the <sys/pte.h> header file. */
#undef HAVE_SYS_PTE_H])
m4trace:configure.in:663: -1- AH_OUTPUT([HAVE_SYS_STREAM_H], [/* Define to 1 if you have the <sys/stream.h> header file. */
putx(c)
int c;
{
- putc (c, rl_outstream);
+ int x;
+
+ x = putc (c, rl_outstream);
+ return (x);
}
static int
--- /dev/null
+/* bashline.c -- Bash's interface to the readline library. */
+
+/* Copyright (C) 1987-2009 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"
+
+#if defined (READLINE)
+
+#include "bashtypes.h"
+#include "posixstat.h"
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#if defined (HAVE_GRP_H)
+# include <grp.h>
+#endif
+
+#if defined (HAVE_NETDB_H)
+# include <netdb.h>
+#endif
+
+#include <stdio.h>
+#include "chartypes.h"
+#include "bashansi.h"
+#include "bashintl.h"
+
+#include "shell.h"
+#include "input.h"
+#include "builtins.h"
+#include "bashhist.h"
+#include "bashline.h"
+#include "execute_cmd.h"
+#include "findcmd.h"
+#include "pathexp.h"
+#include "shmbutil.h"
+
+#include "builtins/common.h"
+
+#include <readline/rlconf.h>
+#include <readline/readline.h>
+#include <readline/history.h>
+
+#include <glob/glob.h>
+
+#if defined (ALIAS)
+# include "alias.h"
+#endif
+
+#if defined (PROGRAMMABLE_COMPLETION)
+# include "pcomplete.h"
+#endif
+
+/* These should agree with the defines for emacs_mode and vi_mode in
+ rldefs.h, even though that's not a public readline header file. */
+#ifndef EMACS_EDITING_MODE
+# define NO_EDITING_MODE -1
+# define EMACS_EDITING_MODE 1
+# define VI_EDITING_MODE 0
+#endif
+
+#define RL_BOOLEAN_VARIABLE_VALUE(s) ((s)[0] == 'o' && (s)[1] == 'n' && (s)[2] == '\0')
+
+#if defined (BRACE_COMPLETION)
+extern int bash_brace_completion __P((int, int));
+#endif /* BRACE_COMPLETION */
+
+/* To avoid including curses.h/term.h/termcap.h and that whole mess. */
+extern int tputs __P((const char *string, int nlines, int (*outx)(int)));
+
+/* Forward declarations */
+
+/* Functions bound to keys in Readline for Bash users. */
+static int shell_expand_line __P((int, int));
+static int display_shell_version __P((int, int));
+static int operate_and_get_next __P((int, int));
+
+static int bash_ignore_filenames __P((char **));
+static int bash_ignore_everything __P((char **));
+
+#if defined (BANG_HISTORY)
+static char *history_expand_line_internal __P((char *));
+static int history_expand_line __P((int, int));
+static int tcsh_magic_space __P((int, int));
+#endif /* BANG_HISTORY */
+#ifdef ALIAS
+static int alias_expand_line __P((int, int));
+#endif
+#if defined (BANG_HISTORY) && defined (ALIAS)
+static int history_and_alias_expand_line __P((int, int));
+#endif
+
+static int bash_forward_shellword __P((int, int));
+static int bash_backward_shellword __P((int, int));
+static int bash_kill_shellword __P((int, int));
+static int bash_backward_kill_shellword __P((int, int));
+
+/* Helper functions for Readline. */
+static char *restore_tilde __P((char *, char *));
+
+static char *bash_filename_rewrite_hook __P((char *, int));
+static void bash_directory_expansion __P((char **));
+static int bash_directory_completion_hook __P((char **));
+static int filename_completion_ignore __P((char **));
+static int bash_push_line __P((void));
+
+static void cleanup_expansion_error __P((void));
+static void maybe_make_readline_line __P((char *));
+static void set_up_new_line __P((char *));
+
+static int check_redir __P((int));
+static char **attempt_shell_completion __P((const char *, int, int));
+static char *variable_completion_function __P((const char *, int));
+static char *hostname_completion_function __P((const char *, int));
+static char *command_subst_completion_function __P((const char *, int));
+
+static void build_history_completion_array __P((void));
+static char *history_completion_generator __P((const char *, int));
+static int dynamic_complete_history __P((int, int));
+static int bash_dabbrev_expand __P((int, int));
+
+static void initialize_hostname_list __P((void));
+static void add_host_name __P((char *));
+static void snarf_hosts_from_file __P((char *));
+static char **hostnames_matching __P((char *));
+
+static void _ignore_completion_names __P((char **, sh_ignore_func_t *));
+static int name_is_acceptable __P((const char *));
+static int test_for_directory __P((const char *));
+static int return_zero __P((const char *));
+
+static char *bash_dequote_filename __P((char *, int));
+static char *quote_word_break_chars __P((char *));
+static char *bash_quote_filename __P((char *, int, char *));
+
+static int putx __P((int));
+static int bash_execute_unix_command __P((int, int));
+static void init_unix_command_map __P((void));
+static int isolate_sequence __P((char *, int, int, int *));
+
+static int set_saved_history __P((void));
+
+#if defined (ALIAS)
+static int posix_edit_macros __P((int, int));
+#endif
+
+#if defined (PROGRAMMABLE_COMPLETION)
+static int find_cmd_start __P((int));
+static int find_cmd_end __P((int));
+static char *find_cmd_name __P((int));
+static char *prog_complete_return __P((const char *, int));
+
+static char **prog_complete_matches;
+#endif
+
+/* Variables used here but defined in other files. */
+#if defined (BANG_HISTORY)
+extern int hist_verify;
+#endif
+
+extern int current_command_line_count, last_command_exit_value;
+extern int array_needs_making;
+extern int posixly_correct, no_symbolic_links;
+extern char *current_prompt_string, *ps1_prompt;
+extern STRING_INT_ALIST word_token_alist[];
+extern sh_builtin_func_t *last_shell_builtin, *this_shell_builtin;
+
+/* SPECIFIC_COMPLETION_FUNCTIONS specifies that we have individual
+ completion functions which indicate what type of completion should be
+ done (at or before point) that can be bound to key sequences with
+ the readline library. */
+#define SPECIFIC_COMPLETION_FUNCTIONS
+
+#if defined (SPECIFIC_COMPLETION_FUNCTIONS)
+static int bash_specific_completion __P((int, rl_compentry_func_t *));
+
+static int bash_complete_filename_internal __P((int));
+static int bash_complete_username_internal __P((int));
+static int bash_complete_hostname_internal __P((int));
+static int bash_complete_variable_internal __P((int));
+static int bash_complete_command_internal __P((int));
+
+static int bash_complete_filename __P((int, int));
+static int bash_possible_filename_completions __P((int, int));
+static int bash_complete_username __P((int, int));
+static int bash_possible_username_completions __P((int, int));
+static int bash_complete_hostname __P((int, int));
+static int bash_possible_hostname_completions __P((int, int));
+static int bash_complete_variable __P((int, int));
+static int bash_possible_variable_completions __P((int, int));
+static int bash_complete_command __P((int, int));
+static int bash_possible_command_completions __P((int, int));
+
+static char *glob_complete_word __P((const char *, int));
+static int bash_glob_completion_internal __P((int));
+static int bash_glob_complete_word __P((int, int));
+static int bash_glob_expand_word __P((int, int));
+static int bash_glob_list_expansions __P((int, int));
+
+#endif /* SPECIFIC_COMPLETION_FUNCTIONS */
+
+static int edit_and_execute_command __P((int, int, int, char *));
+#if defined (VI_MODE)
+static int vi_edit_and_execute_command __P((int, int));
+static int bash_vi_complete __P((int, int));
+#endif
+static int emacs_edit_and_execute_command __P((int, int));
+
+/* Non-zero once initalize_readline () has been called. */
+int bash_readline_initialized = 0;
+
+/* If non-zero, we do hostname completion, breaking words at `@' and
+ trying to complete the stuff after the `@' from our own internal
+ host list. */
+int perform_hostname_completion = 1;
+
+/* If non-zero, we don't do command completion on an empty line. */
+int no_empty_command_completion;
+
+/* Set FORCE_FIGNORE if you want to honor FIGNORE even if it ignores the
+ only possible matches. Set to 0 if you want to match filenames if they
+ are the only possible matches, even if FIGNORE says to. */
+int force_fignore = 1;
+
+/* Perform spelling correction on directory names during word completion */
+int dircomplete_spelling = 0;
+
+static char *bash_completer_word_break_characters = " \t\n\"'@><=;|&(:";
+static char *bash_nohostname_word_break_characters = " \t\n\"'><=;|&(:";
+/* )) */
+
+static rl_hook_func_t *old_rl_startup_hook = (rl_hook_func_t *)NULL;
+
+static int dot_in_path = 0;
+
+/* Set to non-zero when dabbrev-expand is running */
+static int dabbrev_expand_active = 0;
+
+/* What kind of quoting is performed by bash_quote_filename:
+ COMPLETE_DQUOTE = double-quoting the filename
+ COMPLETE_SQUOTE = single_quoting the filename
+ COMPLETE_BSQUOTE = backslash-quoting special chars in the filename
+*/
+#define COMPLETE_DQUOTE 1
+#define COMPLETE_SQUOTE 2
+#define COMPLETE_BSQUOTE 3
+static int completion_quoting_style = COMPLETE_BSQUOTE;
+
+/* Flag values for the final argument to bash_default_completion */
+#define DEFCOMP_CMDPOS 1
+
+/* Change the readline VI-mode keymaps into or out of Posix.2 compliance.
+ Called when the shell is put into or out of `posix' mode. */
+void
+posix_readline_initialize (on_or_off)
+ int on_or_off;
+{
+ if (on_or_off)
+ rl_variable_bind ("comment-begin", "#");
+#if defined (VI_MODE)
+ rl_bind_key_in_map (CTRL ('I'), on_or_off ? rl_insert : rl_complete, vi_insertion_keymap);
+#endif
+}
+
+void
+reset_completer_word_break_chars ()
+{
+ rl_completer_word_break_characters = perform_hostname_completion ? savestring (bash_completer_word_break_characters) : savestring (bash_nohostname_word_break_characters);
+}
+
+/* When this function returns, rl_completer_word_break_characters points to
+ dynamically allocated memory. */
+int
+enable_hostname_completion (on_or_off)
+ int on_or_off;
+{
+ int old_value;
+ char *at, *nv, *nval;
+
+ old_value = perform_hostname_completion;
+
+ if (on_or_off)
+ {
+ perform_hostname_completion = 1;
+ rl_special_prefixes = "$@";
+ }
+ else
+ {
+ perform_hostname_completion = 0;
+ rl_special_prefixes = "$";
+ }
+
+ /* Now we need to figure out how to appropriately modify and assign
+ rl_completer_word_break_characters depending on whether we want
+ hostname completion on or off. */
+
+ /* If this is the first time this has been called
+ (bash_readline_initialized == 0), use the sames values as before, but
+ allocate new memory for rl_completer_word_break_characters. */
+
+ if (bash_readline_initialized == 0 &&
+ (rl_completer_word_break_characters == 0 ||
+ rl_completer_word_break_characters == rl_basic_word_break_characters))
+ {
+ if (on_or_off)
+ rl_completer_word_break_characters = savestring (bash_completer_word_break_characters);
+ else
+ rl_completer_word_break_characters = savestring (bash_nohostname_word_break_characters);
+ }
+ else
+ {
+ /* See if we have anything to do. */
+ at = strchr (rl_completer_word_break_characters, '@');
+ if ((at == 0 && on_or_off == 0) || (at != 0 && on_or_off != 0))
+ return old_value;
+
+ /* We have something to do. Do it. */
+ nval = (char *)xmalloc (strlen (rl_completer_word_break_characters) + 1 + on_or_off);
+
+ if (on_or_off == 0)
+ {
+ /* Turn it off -- just remove `@' from word break chars. We want
+ to remove all occurrences of `@' from the char list, so we loop
+ rather than just copy the rest of the list over AT. */
+ for (nv = nval, at = rl_completer_word_break_characters; *at; )
+ if (*at != '@')
+ *nv++ = *at++;
+ else
+ at++;
+ *nv = '\0';
+ }
+ else
+ {
+ nval[0] = '@';
+ strcpy (nval + 1, rl_completer_word_break_characters);
+ }
+
+ free (rl_completer_word_break_characters);
+ rl_completer_word_break_characters = nval;
+ }
+
+ return (old_value);
+}
+
+/* Called once from parse.y if we are going to use readline. */
+void
+initialize_readline ()
+{
+ rl_command_func_t *func;
+ char kseq[2];
+
+ if (bash_readline_initialized)
+ return;
+
+ rl_terminal_name = get_string_value ("TERM");
+ rl_instream = stdin;
+ rl_outstream = stderr;
+
+ /* Allow conditional parsing of the ~/.inputrc file. */
+ rl_readline_name = "Bash";
+
+ /* Add bindable names before calling rl_initialize so they may be
+ referenced in the various inputrc files. */
+ rl_add_defun ("shell-expand-line", shell_expand_line, -1);
+#ifdef BANG_HISTORY
+ rl_add_defun ("history-expand-line", history_expand_line, -1);
+ rl_add_defun ("magic-space", tcsh_magic_space, -1);
+#endif
+
+ rl_add_defun ("shell-forward-word", bash_forward_shellword, -1);
+ rl_add_defun ("shell-backward-word", bash_backward_shellword, -1);
+ rl_add_defun ("shell-kill-word", bash_kill_shellword, -1);
+ rl_add_defun ("shell-backward-kill-word", bash_backward_kill_shellword, -1);
+
+#ifdef ALIAS
+ rl_add_defun ("alias-expand-line", alias_expand_line, -1);
+# ifdef BANG_HISTORY
+ rl_add_defun ("history-and-alias-expand-line", history_and_alias_expand_line, -1);
+# endif
+#endif
+
+ /* Backwards compatibility. */
+ rl_add_defun ("insert-last-argument", rl_yank_last_arg, -1);
+
+ rl_add_defun ("operate-and-get-next", operate_and_get_next, -1);
+ rl_add_defun ("display-shell-version", display_shell_version, -1);
+ rl_add_defun ("edit-and-execute-command", emacs_edit_and_execute_command, -1);
+
+#if defined (BRACE_COMPLETION)
+ rl_add_defun ("complete-into-braces", bash_brace_completion, -1);
+#endif
+
+#if defined (SPECIFIC_COMPLETION_FUNCTIONS)
+ rl_add_defun ("complete-filename", bash_complete_filename, -1);
+ rl_add_defun ("possible-filename-completions", bash_possible_filename_completions, -1);
+ rl_add_defun ("complete-username", bash_complete_username, -1);
+ rl_add_defun ("possible-username-completions", bash_possible_username_completions, -1);
+ rl_add_defun ("complete-hostname", bash_complete_hostname, -1);
+ rl_add_defun ("possible-hostname-completions", bash_possible_hostname_completions, -1);
+ rl_add_defun ("complete-variable", bash_complete_variable, -1);
+ rl_add_defun ("possible-variable-completions", bash_possible_variable_completions, -1);
+ rl_add_defun ("complete-command", bash_complete_command, -1);
+ rl_add_defun ("possible-command-completions", bash_possible_command_completions, -1);
+ rl_add_defun ("glob-complete-word", bash_glob_complete_word, -1);
+ rl_add_defun ("glob-expand-word", bash_glob_expand_word, -1);
+ rl_add_defun ("glob-list-expansions", bash_glob_list_expansions, -1);
+#endif
+
+ rl_add_defun ("dynamic-complete-history", dynamic_complete_history, -1);
+ rl_add_defun ("dabbrev-expand", bash_dabbrev_expand, -1);
+
+ /* Bind defaults before binding our custom shell keybindings. */
+ if (RL_ISSTATE(RL_STATE_INITIALIZED) == 0)
+ rl_initialize ();
+
+ /* Bind up our special shell functions. */
+ rl_bind_key_if_unbound_in_map (CTRL('E'), shell_expand_line, emacs_meta_keymap);
+
+#ifdef BANG_HISTORY
+ rl_bind_key_if_unbound_in_map ('^', history_expand_line, emacs_meta_keymap);
+#endif
+
+ rl_bind_key_if_unbound_in_map (CTRL ('O'), operate_and_get_next, emacs_standard_keymap);
+ rl_bind_key_if_unbound_in_map (CTRL ('V'), display_shell_version, emacs_ctlx_keymap);
+
+ /* In Bash, the user can switch editing modes with "set -o [vi emacs]",
+ so it is not necessary to allow C-M-j for context switching. Turn
+ off this occasionally confusing behaviour. */
+ kseq[0] = CTRL('J');
+ kseq[1] = '\0';
+ func = rl_function_of_keyseq (kseq, emacs_meta_keymap, (int *)NULL);
+ if (func == rl_vi_editing_mode)
+ rl_unbind_key_in_map (CTRL('J'), emacs_meta_keymap);
+ kseq[0] = CTRL('M');
+ func = rl_function_of_keyseq (kseq, emacs_meta_keymap, (int *)NULL);
+ if (func == rl_vi_editing_mode)
+ rl_unbind_key_in_map (CTRL('M'), emacs_meta_keymap);
+#if defined (VI_MODE)
+ rl_unbind_key_in_map (CTRL('E'), vi_movement_keymap);
+#endif
+
+#if defined (BRACE_COMPLETION)
+ rl_bind_key_if_unbound_in_map ('{', bash_brace_completion, emacs_meta_keymap); /*}*/
+#endif /* BRACE_COMPLETION */
+
+#if defined (SPECIFIC_COMPLETION_FUNCTIONS)
+ rl_bind_key_if_unbound_in_map ('/', bash_complete_filename, emacs_meta_keymap);
+ rl_bind_key_if_unbound_in_map ('/', bash_possible_filename_completions, emacs_ctlx_keymap);
+
+ /* Have to jump through hoops here because there is a default binding for
+ M-~ (rl_tilde_expand) */
+ kseq[0] = '~';
+ kseq[1] = '\0';
+ func = rl_function_of_keyseq (kseq, emacs_meta_keymap, (int *)NULL);
+ if (func == 0 || func == rl_tilde_expand)
+ rl_bind_keyseq_in_map (kseq, bash_complete_username, emacs_meta_keymap);
+
+ rl_bind_key_if_unbound_in_map ('~', bash_possible_username_completions, emacs_ctlx_keymap);
+
+ rl_bind_key_if_unbound_in_map ('@', bash_complete_hostname, emacs_meta_keymap);
+ rl_bind_key_if_unbound_in_map ('@', bash_possible_hostname_completions, emacs_ctlx_keymap);
+
+ rl_bind_key_if_unbound_in_map ('$', bash_complete_variable, emacs_meta_keymap);
+ rl_bind_key_if_unbound_in_map ('$', bash_possible_variable_completions, emacs_ctlx_keymap);
+
+ rl_bind_key_if_unbound_in_map ('!', bash_complete_command, emacs_meta_keymap);
+ rl_bind_key_if_unbound_in_map ('!', bash_possible_command_completions, emacs_ctlx_keymap);
+
+ rl_bind_key_if_unbound_in_map ('g', bash_glob_complete_word, emacs_meta_keymap);
+ rl_bind_key_if_unbound_in_map ('*', bash_glob_expand_word, emacs_ctlx_keymap);
+ rl_bind_key_if_unbound_in_map ('g', bash_glob_list_expansions, emacs_ctlx_keymap);
+
+#endif /* SPECIFIC_COMPLETION_FUNCTIONS */
+
+ kseq[0] = TAB;
+ kseq[1] = '\0';
+ func = rl_function_of_keyseq (kseq, emacs_meta_keymap, (int *)NULL);
+ if (func == 0 || func == rl_tab_insert)
+ rl_bind_key_in_map (TAB, dynamic_complete_history, emacs_meta_keymap);
+
+ /* Tell the completer that we want a crack first. */
+ rl_attempted_completion_function = attempt_shell_completion;
+
+ /* Tell the completer that we might want to follow symbolic links or
+ do other expansion on directory names. */
+ rl_directory_completion_hook = bash_directory_completion_hook;
+
+ rl_filename_rewrite_hook = bash_filename_rewrite_hook;
+
+ /* Tell the filename completer we want a chance to ignore some names. */
+ rl_ignore_some_completions_function = filename_completion_ignore;
+
+ /* Bind C-xC-e to invoke emacs and run result as commands. */
+ rl_bind_key_if_unbound_in_map (CTRL ('E'), emacs_edit_and_execute_command, emacs_ctlx_keymap);
+#if defined (VI_MODE)
+ rl_bind_key_if_unbound_in_map ('v', vi_edit_and_execute_command, vi_movement_keymap);
+# if defined (ALIAS)
+ rl_bind_key_if_unbound_in_map ('@', posix_edit_macros, vi_movement_keymap);
+# endif
+
+ rl_bind_key_in_map ('\\', bash_vi_complete, vi_movement_keymap);
+ rl_bind_key_in_map ('*', bash_vi_complete, vi_movement_keymap);
+ rl_bind_key_in_map ('=', bash_vi_complete, vi_movement_keymap);
+#endif
+
+ rl_completer_quote_characters = "'\"";
+
+ /* This sets rl_completer_word_break_characters and rl_special_prefixes
+ to the appropriate values, depending on whether or not hostname
+ completion is enabled. */
+ enable_hostname_completion (perform_hostname_completion);
+
+ /* characters that need to be quoted when appearing in filenames. */
+#if 0
+ rl_filename_quote_characters = " \t\n\\\"'@<>=;|&()#$`?*[!:{"; /*}*/
+#else
+ rl_filename_quote_characters = " \t\n\\\"'@<>=;|&()#$`?*[!:{~"; /*}*/
+#endif
+ rl_filename_quoting_function = bash_quote_filename;
+ rl_filename_dequoting_function = bash_dequote_filename;
+ rl_char_is_quoted_p = char_is_quoted;
+
+#if 0
+ /* This is superfluous and makes it impossible to use tab completion in
+ vi mode even when explicitly binding it in ~/.inputrc. sv_strict_posix()
+ should already have called posix_readline_initialize() when
+ posixly_correct was set. */
+ if (posixly_correct)
+ posix_readline_initialize (1);
+#endif
+
+ bash_readline_initialized = 1;
+}
+
+void
+bashline_reinitialize ()
+{
+ bash_readline_initialized = 0;
+}
+
+/* On Sun systems at least, rl_attempted_completion_function can end up
+ getting set to NULL, and rl_completion_entry_function set to do command
+ word completion if Bash is interrupted while trying to complete a command
+ word. This just resets all the completion functions to the right thing.
+ It's called from throw_to_top_level(). */
+void
+bashline_reset ()
+{
+ tilde_initialize ();
+ rl_attempted_completion_function = attempt_shell_completion;
+ rl_completion_entry_function = NULL;
+ rl_directory_completion_hook = bash_directory_completion_hook;
+ rl_ignore_some_completions_function = filename_completion_ignore;
+}
+
+/* Contains the line to push into readline. */
+static char *push_to_readline = (char *)NULL;
+
+/* Push the contents of push_to_readline into the
+ readline buffer. */
+static int
+bash_push_line ()
+{
+ if (push_to_readline)
+ {
+ rl_insert_text (push_to_readline);
+ free (push_to_readline);
+ push_to_readline = (char *)NULL;
+ rl_startup_hook = old_rl_startup_hook;
+ }
+ return 0;
+}
+
+/* Call this to set the initial text for the next line to read
+ from readline. */
+int
+bash_re_edit (line)
+ char *line;
+{
+ FREE (push_to_readline);
+
+ push_to_readline = savestring (line);
+ old_rl_startup_hook = rl_startup_hook;
+ rl_startup_hook = bash_push_line;
+
+ return (0);
+}
+
+static int
+display_shell_version (count, c)
+ int count, c;
+{
+ rl_crlf ();
+ show_shell_version (0);
+ putc ('\r', rl_outstream);
+ fflush (rl_outstream);
+ rl_on_new_line ();
+ rl_redisplay ();
+ return 0;
+}
+
+/* **************************************************************** */
+/* */
+/* Readline Stuff */
+/* */
+/* **************************************************************** */
+
+/* If the user requests hostname completion, then simply build a list
+ of hosts, and complete from that forever more, or at least until
+ HOSTFILE is unset. */
+
+/* THIS SHOULD BE A STRINGLIST. */
+/* The kept list of hostnames. */
+static char **hostname_list = (char **)NULL;
+
+/* The physical size of the above list. */
+static int hostname_list_size;
+
+/* The number of hostnames in the above list. */
+static int hostname_list_length;
+
+/* Whether or not HOSTNAME_LIST has been initialized. */
+int hostname_list_initialized = 0;
+
+/* Initialize the hostname completion table. */
+static void
+initialize_hostname_list ()
+{
+ char *temp;
+
+ temp = get_string_value ("HOSTFILE");
+ if (temp == 0)
+ temp = get_string_value ("hostname_completion_file");
+ if (temp == 0)
+ temp = DEFAULT_HOSTS_FILE;
+
+ snarf_hosts_from_file (temp);
+
+ if (hostname_list)
+ hostname_list_initialized++;
+}
+
+/* Add NAME to the list of hosts. */
+static void
+add_host_name (name)
+ char *name;
+{
+ if (hostname_list_length + 2 > hostname_list_size)
+ {
+ hostname_list_size = (hostname_list_size + 32) - (hostname_list_size % 32);
+ hostname_list = strvec_resize (hostname_list, hostname_list_size);
+ }
+
+ hostname_list[hostname_list_length++] = savestring (name);
+ hostname_list[hostname_list_length] = (char *)NULL;
+}
+
+#define cr_whitespace(c) ((c) == '\r' || (c) == '\n' || whitespace(c))
+
+static void
+snarf_hosts_from_file (filename)
+ char *filename;
+{
+ FILE *file;
+ char *temp, buffer[256], name[256];
+ register int i, start;
+
+ file = fopen (filename, "r");
+ if (file == 0)
+ return;
+
+ while (temp = fgets (buffer, 255, file))
+ {
+ /* Skip to first character. */
+ for (i = 0; buffer[i] && cr_whitespace (buffer[i]); i++)
+ ;
+
+ /* If comment or blank line, ignore. */
+ if (buffer[i] == '\0' || buffer[i] == '#')
+ continue;
+
+ /* If `preprocessor' directive, do the include. */
+ if (strncmp (buffer + i, "$include ", 9) == 0)
+ {
+ char *incfile, *t;
+
+ /* Find start of filename. */
+ for (incfile = buffer + i + 9; *incfile && whitespace (*incfile); incfile++)
+ ;
+
+ /* Find end of filename. */
+ for (t = incfile; *t && cr_whitespace (*t) == 0; t++)
+ ;
+
+ *t = '\0';
+
+ snarf_hosts_from_file (incfile);
+ continue;
+ }
+
+ /* Skip internet address if present. */
+ if (DIGIT (buffer[i]))
+ for (; buffer[i] && cr_whitespace (buffer[i]) == 0; i++);
+
+ /* Gobble up names. Each name is separated with whitespace. */
+ while (buffer[i])
+ {
+ for (; cr_whitespace (buffer[i]); i++)
+ ;
+ if (buffer[i] == '\0' || buffer[i] == '#')
+ break;
+
+ /* Isolate the current word. */
+ for (start = i; buffer[i] && cr_whitespace (buffer[i]) == 0; i++)
+ ;
+ if (i == start)
+ continue;
+ strncpy (name, buffer + start, i - start);
+ name[i - start] = '\0';
+ add_host_name (name);
+ }
+ }
+ fclose (file);
+}
+
+/* Return the hostname list. */
+char **
+get_hostname_list ()
+{
+ if (hostname_list_initialized == 0)
+ initialize_hostname_list ();
+ return (hostname_list);
+}
+
+void
+clear_hostname_list ()
+{
+ register int i;
+
+ if (hostname_list_initialized == 0)
+ return;
+ for (i = 0; i < hostname_list_length; i++)
+ free (hostname_list[i]);
+ hostname_list_length = hostname_list_initialized = 0;
+}
+
+/* Return a NULL terminated list of hostnames which begin with TEXT.
+ Initialize the hostname list the first time if neccessary.
+ The array is malloc ()'ed, but not the individual strings. */
+static char **
+hostnames_matching (text)
+ char *text;
+{
+ register int i, len, nmatch, rsize;
+ char **result;
+
+ if (hostname_list_initialized == 0)
+ initialize_hostname_list ();
+
+ if (hostname_list_initialized == 0)
+ return ((char **)NULL);
+
+ /* Special case. If TEXT consists of nothing, then the whole list is
+ what is desired. */
+ if (*text == '\0')
+ {
+ result = strvec_create (1 + hostname_list_length);
+ for (i = 0; i < hostname_list_length; i++)
+ result[i] = hostname_list[i];
+ result[i] = (char *)NULL;
+ return (result);
+ }
+
+ /* Scan until found, or failure. */
+ len = strlen (text);
+ result = (char **)NULL;
+ for (i = nmatch = rsize = 0; i < hostname_list_length; i++)
+ {
+ if (STREQN (text, hostname_list[i], len) == 0)
+ continue;
+
+ /* OK, it matches. Add it to the list. */
+ if (nmatch >= (rsize - 1))
+ {
+ rsize = (rsize + 16) - (rsize % 16);
+ result = strvec_resize (result, rsize);
+ }
+
+ result[nmatch++] = hostname_list[i];
+ }
+ if (nmatch)
+ result[nmatch] = (char *)NULL;
+ return (result);
+}
+
+/* The equivalent of the Korn shell C-o operate-and-get-next-history-line
+ editing command. */
+static int saved_history_line_to_use = -1;
+
+static int
+set_saved_history ()
+{
+ if (saved_history_line_to_use >= 0)
+ rl_get_previous_history (history_length - saved_history_line_to_use, 0);
+ saved_history_line_to_use = -1;
+ rl_startup_hook = old_rl_startup_hook;
+ return (0);
+}
+
+static int
+operate_and_get_next (count, c)
+ int count, c;
+{
+ int where;
+
+ /* Accept the current line. */
+ rl_newline (1, c);
+
+ /* Find the current line, and find the next line to use. */
+ where = where_history ();
+
+ if ((history_is_stifled () && (history_length >= history_max_entries)) ||
+ (where >= history_length - 1))
+ saved_history_line_to_use = where;
+ else
+ saved_history_line_to_use = where + 1;
+
+ old_rl_startup_hook = rl_startup_hook;
+ rl_startup_hook = set_saved_history;
+
+ return 0;
+}
+
+/* This vi mode command causes VI_EDIT_COMMAND to be run on the current
+ command being entered (if no explicit argument is given), otherwise on
+ a command from the history file. */
+
+#define VI_EDIT_COMMAND "fc -e \"${VISUAL:-${EDITOR:-vi}}\""
+#define EMACS_EDIT_COMMAND "fc -e \"${VISUAL:-${EDITOR:-emacs}}\""
+#define POSIX_VI_EDIT_COMMAND "fc -e vi"
+
+static int
+edit_and_execute_command (count, c, editing_mode, edit_command)
+ int count, c, editing_mode;
+ char *edit_command;
+{
+ char *command, *metaval;
+ int r, cclc, rrs, metaflag;
+
+ rrs = rl_readline_state;
+ cclc = current_command_line_count;
+
+ /* Accept the current line. */
+ rl_newline (1, c);
+
+ if (rl_explicit_arg)
+ {
+ command = (char *)xmalloc (strlen (edit_command) + 8);
+ sprintf (command, "%s %d", edit_command, count);
+ }
+ else
+ {
+ /* Take the command we were just editing, add it to the history file,
+ then call fc to operate on it. We have to add a dummy command to
+ the end of the history because fc ignores the last command (assumes
+ it's supposed to deal with the command before the `fc'). */
+ using_history ();
+ bash_add_history (rl_line_buffer);
+ bash_add_history ("");
+ history_lines_this_session++;
+ using_history ();
+ command = savestring (edit_command);
+ }
+
+ metaval = rl_variable_value ("input-meta");
+ metaflag = RL_BOOLEAN_VARIABLE_VALUE (metaval);
+
+ /* Now, POSIX.1-2001 and SUSv3 say that the commands executed from the
+ temporary file should be placed into the history. We don't do that
+ yet. */
+ if (rl_deprep_term_function)
+ (*rl_deprep_term_function) ();
+ r = parse_and_execute (command, (editing_mode == VI_EDITING_MODE) ? "v" : "C-xC-e", SEVAL_NOHIST);
+ if (rl_prep_term_function)
+ (*rl_prep_term_function) (metaflag);
+
+ current_command_line_count = cclc;
+
+ /* Now erase the contents of the current line and undo the effects of the
+ rl_accept_line() above. We don't even want to make the text we just
+ executed available for undoing. */
+ rl_line_buffer[0] = '\0'; /* XXX */
+ rl_point = rl_end = 0;
+ rl_done = 0;
+ rl_readline_state = rrs;
+
+ rl_forced_update_display ();
+
+ return r;
+}
+
+#if defined (VI_MODE)
+static int
+vi_edit_and_execute_command (count, c)
+ int count, c;
+{
+ if (posixly_correct)
+ return (edit_and_execute_command (count, c, VI_EDITING_MODE, POSIX_VI_EDIT_COMMAND));
+ else
+ return (edit_and_execute_command (count, c, VI_EDITING_MODE, VI_EDIT_COMMAND));
+}
+#endif /* VI_MODE */
+
+static int
+emacs_edit_and_execute_command (count, c)
+ int count, c;
+{
+ return (edit_and_execute_command (count, c, EMACS_EDITING_MODE, EMACS_EDIT_COMMAND));
+}
+
+#if defined (ALIAS)
+static int
+posix_edit_macros (count, key)
+ int count, key;
+{
+ int c;
+ char alias_name[3], *alias_value, *macro;
+
+ c = rl_read_key ();
+ alias_name[0] = '_';
+ alias_name[1] = c;
+ alias_name[2] = '\0';
+
+ alias_value = get_alias_value (alias_name);
+ if (alias_value && *alias_value)
+ {
+ macro = savestring (alias_value);
+ rl_push_macro_input (macro);
+ }
+ return 0;
+}
+#endif
+
+/* Bindable commands that move `shell-words': that is, sequences of
+ non-unquoted-metacharacters. */
+
+#define WORDDELIM(c) (shellmeta(c) || shellblank(c))
+
+static int
+bash_forward_shellword (count, key)
+ int count, key;
+{
+ size_t slen;
+ int sindex, c, p;
+ DECLARE_MBSTATE;
+
+ if (count < 0)
+ return (bash_backward_shellword (-count, key));
+
+ /* The tricky part of this is deciding whether or not the first character
+ we're on is an unquoted metacharacter. Not completely handled yet. */
+ /* XXX - need to test this stuff with backslash-escaped shell
+ metacharacters and unclosed single- and double-quoted strings. */
+
+ p = rl_point;
+ slen = rl_end;
+
+ while (count)
+ {
+ if (p == rl_end)
+ {
+ rl_point = rl_end;
+ return 0;
+ }
+
+ /* Move forward until we hit a non-metacharacter. */
+ while (p < rl_end && (c = rl_line_buffer[p]) && WORDDELIM (c))
+ {
+ switch (c)
+ {
+ default:
+ ADVANCE_CHAR (rl_line_buffer, slen, p);
+ continue; /* straight back to loop, don't increment p */
+ case '\\':
+ if (p < rl_end && rl_line_buffer[p])
+ ADVANCE_CHAR (rl_line_buffer, slen, p);
+ break;
+ case '\'':
+ p = skip_to_delim (rl_line_buffer, ++p, "'", SD_NOJMP);
+ break;
+ case '"':
+ p = skip_to_delim (rl_line_buffer, ++p, "\"", SD_NOJMP);
+ break;
+ }
+
+ if (p < rl_end)
+ p++;
+ }
+
+ if (rl_line_buffer[p] == 0 || p == rl_end)
+ {
+ rl_point = rl_end;
+ rl_ding ();
+ return 0;
+ }
+
+ /* Now move forward until we hit a non-quoted metacharacter or EOL */
+ while (p < rl_end && (c = rl_line_buffer[p]) && WORDDELIM (c) == 0)
+ {
+ switch (c)
+ {
+ default:
+ ADVANCE_CHAR (rl_line_buffer, slen, p);
+ continue; /* straight back to loop, don't increment p */
+ case '\\':
+ if (p < rl_end && rl_line_buffer[p])
+ ADVANCE_CHAR (rl_line_buffer, slen, p);
+ break;
+ case '\'':
+ p = skip_to_delim (rl_line_buffer, ++p, "'", SD_NOJMP);
+ break;
+ case '"':
+ p = skip_to_delim (rl_line_buffer, ++p, "\"", SD_NOJMP);
+ break;
+ }
+
+ if (p < rl_end)
+ p++;
+ }
+
+ if (p == rl_end || rl_line_buffer[p] == 0)
+ {
+ rl_point = rl_end;
+ return (0);
+ }
+
+ count--;
+ }
+
+ rl_point = p;
+ return (0);
+}
+
+static int
+bash_backward_shellword (count, key)
+ int count, key;
+{
+ size_t slen;
+ int sindex, c, p;
+ DECLARE_MBSTATE;
+
+ if (count < 0)
+ return (bash_forward_shellword (-count, key));
+
+ p = rl_point;
+ slen = rl_end;
+
+ while (count)
+ {
+ if (p == 0)
+ {
+ rl_point = 0;
+ return 0;
+ }
+
+ /* Move backward until we hit a non-metacharacter. */
+ while (p > 0)
+ {
+ c = rl_line_buffer[p];
+ if (WORDDELIM (c) && char_is_quoted (rl_line_buffer, p) == 0)
+ BACKUP_CHAR (rl_line_buffer, slen, p);
+ break;
+ }
+
+ if (p == 0)
+ {
+ rl_point = 0;
+ return 0;
+ }
+
+ /* Now move backward until we hit a metacharacter or BOL. */
+ while (p > 0)
+ {
+ c = rl_line_buffer[p];
+ if (WORDDELIM (c) && char_is_quoted (rl_line_buffer, p) == 0)
+ break;
+ BACKUP_CHAR (rl_line_buffer, slen, p);
+ }
+
+ count--;
+ }
+
+ rl_point = p;
+ return 0;
+}
+
+static int
+bash_kill_shellword (count, key)
+ int count, key;
+{
+ int p;
+
+ if (count < 0)
+ return (bash_backward_kill_shellword (-count, key));
+
+ p = rl_point;
+ bash_forward_shellword (count, key);
+
+ if (rl_point != p)
+ rl_kill_text (p, rl_point);
+
+ rl_point = p;
+ if (rl_editing_mode == 1) /* 1 == emacs_mode */
+ rl_mark = rl_point;
+
+ return 0;
+}
+
+static int
+bash_backward_kill_shellword (count, key)
+ int count, key;
+{
+ int p;
+
+ if (count < 0)
+ return (bash_kill_shellword (-count, key));
+
+ p = rl_point;
+ bash_backward_shellword (count, key);
+
+ if (rl_point != p)
+ rl_kill_text (p, rl_point);
+
+ if (rl_editing_mode == 1) /* 1 == emacs_mode */
+ rl_mark = rl_point;
+
+ return 0;
+}
+
+
+/* **************************************************************** */
+/* */
+/* How To Do Shell Completion */
+/* */
+/* **************************************************************** */
+
+#define COMMAND_SEPARATORS ";|&{(`"
+/* )} */
+
+static int
+check_redir (ti)
+ int ti;
+{
+ register int this_char, prev_char;
+
+ /* Handle the two character tokens `>&', `<&', and `>|'.
+ We are not in a command position after one of these. */
+ this_char = rl_line_buffer[ti];
+ prev_char = rl_line_buffer[ti - 1];
+
+ if ((this_char == '&' && (prev_char == '<' || prev_char == '>')) ||
+ (this_char == '|' && prev_char == '>'))
+ return (1);
+ else if ((this_char == '{' && prev_char == '$') || /* } */
+ (char_is_quoted (rl_line_buffer, ti)))
+ return (1);
+ return (0);
+}
+
+#if defined (PROGRAMMABLE_COMPLETION)
+/*
+ * XXX - because of the <= start test, and setting os = s+1, this can
+ * potentially return os > start. This is probably not what we want to
+ * happen, but fix later after 2.05a-release.
+ */
+static int
+find_cmd_start (start)
+ int start;
+{
+ register int s, os;
+
+ os = 0;
+ while (((s = skip_to_delim (rl_line_buffer, os, COMMAND_SEPARATORS, SD_NOJMP|SD_NOSKIPCMD)) <= start) &&
+ rl_line_buffer[s])
+ os = s+1;
+ return os;
+}
+
+static int
+find_cmd_end (end)
+ int end;
+{
+ register int e;
+
+ e = skip_to_delim (rl_line_buffer, end, COMMAND_SEPARATORS, SD_NOJMP);
+ return e;
+}
+
+static char *
+find_cmd_name (start)
+ int start;
+{
+ char *name;
+ register int s, e;
+
+ for (s = start; whitespace (rl_line_buffer[s]); s++)
+ ;
+
+ /* skip until a shell break character */
+ e = skip_to_delim (rl_line_buffer, s, "()<>;&| \t\n", SD_NOJMP);
+
+ name = substring (rl_line_buffer, s, e);
+
+ return (name);
+}
+
+static char *
+prog_complete_return (text, matchnum)
+ const char *text;
+ int matchnum;
+{
+ static int ind;
+
+ if (matchnum == 0)
+ ind = 0;
+
+ if (prog_complete_matches == 0 || prog_complete_matches[ind] == 0)
+ return (char *)NULL;
+ return (prog_complete_matches[ind++]);
+}
+
+#endif /* PROGRAMMABLE_COMPLETION */
+
+/* Do some completion on TEXT. The indices of TEXT in RL_LINE_BUFFER are
+ at START and END. Return an array of matches, or NULL if none. */
+static char **
+attempt_shell_completion (text, start, end)
+ const char *text;
+ int start, end;
+{
+ int in_command_position, ti, saveti, qc, dflags;
+ char **matches, *command_separator_chars;
+
+ command_separator_chars = COMMAND_SEPARATORS;
+ matches = (char **)NULL;
+ rl_ignore_some_completions_function = filename_completion_ignore;
+
+ /* Determine if this could be a command word. It is if it appears at
+ the start of the line (ignoring preceding whitespace), or if it
+ appears after a character that separates commands. It cannot be a
+ command word if we aren't at the top-level prompt. */
+ ti = start - 1;
+ saveti = qc = -1;
+
+ while ((ti > -1) && (whitespace (rl_line_buffer[ti])))
+ ti--;
+
+#if 1
+ /* If this is an open quote, maybe we're trying to complete a quoted
+ command name. */
+ if (ti >= 0 && (rl_line_buffer[ti] == '"' || rl_line_buffer[ti] == '\''))
+ {
+ qc = rl_line_buffer[ti];
+ saveti = ti--;
+ while (ti > -1 && (whitespace (rl_line_buffer[ti])))
+ ti--;
+ }
+#endif
+
+ in_command_position = 0;
+ if (ti < 0)
+ {
+ /* Only do command completion at the start of a line when we
+ are prompting at the top level. */
+ if (current_prompt_string == ps1_prompt)
+ in_command_position++;
+ }
+ else if (member (rl_line_buffer[ti], command_separator_chars))
+ {
+ in_command_position++;
+
+ if (check_redir (ti) == 1)
+ in_command_position = 0;
+ }
+ else
+ {
+ /* This still could be in command position. It is possible
+ that all of the previous words on the line are variable
+ assignments. */
+ }
+
+ /* Check that we haven't incorrectly flagged a closed command substitution
+ as indicating we're in a command position. */
+ if (in_command_position && ti >= 0 && rl_line_buffer[ti] == '`' &&
+ *text != '`' && unclosed_pair (rl_line_buffer, end, "`") == 0)
+ in_command_position = 0;
+
+ /* Special handling for command substitution. If *TEXT is a backquote,
+ it can be the start or end of an old-style command substitution, or
+ unmatched. If it's unmatched, both calls to unclosed_pair will
+ succeed. Don't bother if readline found a single quote and we are
+ completing on the substring. */
+ if (*text == '`' && rl_completion_quote_character != '\'' &&
+ (in_command_position || (unclosed_pair (rl_line_buffer, start, "`") &&
+ unclosed_pair (rl_line_buffer, end, "`"))))
+ matches = rl_completion_matches (text, command_subst_completion_function);
+
+#if defined (PROGRAMMABLE_COMPLETION)
+ /* Attempt programmable completion. */
+ if (matches == 0 && (in_command_position == 0 || text[0] == '\0') &&
+ prog_completion_enabled && (progcomp_size () > 0) &&
+ current_prompt_string == ps1_prompt)
+ {
+ int s, e, foundcs;
+ char *n;
+
+ /* XXX - don't free the members */
+ if (prog_complete_matches)
+ free (prog_complete_matches);
+ prog_complete_matches = (char **)NULL;
+
+ s = find_cmd_start (start);
+ e = find_cmd_end (end);
+ n = find_cmd_name (s);
+ if (e == 0 && e == s && text[0] == '\0')
+ prog_complete_matches = programmable_completions ("_EmptycmD_", text, s, e, &foundcs);
+ else if (e > s && assignment (n, 0) == 0)
+ prog_complete_matches = programmable_completions (n, text, s, e, &foundcs);
+ else
+ foundcs = 0;
+ FREE (n);
+ /* XXX - if we found a COMPSPEC for the command, just return whatever
+ the programmable completion code returns, and disable the default
+ filename completion that readline will do unless the COPT_DEFAULT
+ option has been set with the `-o default' option to complete or
+ compopt. */
+ if (foundcs)
+ {
+ pcomp_set_readline_variables (foundcs, 1);
+ /* Turn what the programmable completion code returns into what
+ readline wants. I should have made compute_lcd_of_matches
+ external... */
+ matches = rl_completion_matches (text, prog_complete_return);
+ if ((foundcs & COPT_DEFAULT) == 0)
+ rl_attempted_completion_over = 1; /* no default */
+ if (matches || ((foundcs & COPT_BASHDEFAULT) == 0))
+ return (matches);
+ }
+ }
+#endif
+
+ if (matches == 0)
+ {
+ dflags = 0;
+ if (in_command_position)
+ dflags |= DEFCOMP_CMDPOS;
+ matches = bash_default_completion (text, start, end, qc, dflags);
+ }
+
+ return matches;
+}
+
+char **
+bash_default_completion (text, start, end, qc, compflags)
+ const char *text;
+ int start, end, qc, compflags;
+{
+ char **matches;
+
+ matches = (char **)NULL;
+
+ /* New posix-style command substitution or variable name? */
+ if (!matches && *text == '$')
+ {
+ if (qc != '\'' && text[1] == '(') /* ) */
+ matches = rl_completion_matches (text, command_subst_completion_function);
+ else
+ matches = rl_completion_matches (text, variable_completion_function);
+ }
+
+ /* If the word starts in `~', and there is no slash in the word, then
+ try completing this word as a username. */
+ if (matches ==0 && *text == '~' && mbschr (text, '/') == 0)
+ matches = rl_completion_matches (text, rl_username_completion_function);
+
+ /* Another one. Why not? If the word starts in '@', then look through
+ the world of known hostnames for completion first. */
+ if (!matches && perform_hostname_completion && *text == '@')
+ matches = rl_completion_matches (text, hostname_completion_function);
+
+ /* And last, (but not least) if this word is in a command position, then
+ complete over possible command names, including aliases, functions,
+ and command names. */
+ if (matches == 0 && (compflags & DEFCOMP_CMDPOS))
+ {
+ /* If END == START and text[0] == 0, we are trying to complete an empty
+ command word. */
+ if (no_empty_command_completion && end == start && text[0] == '\0')
+ {
+ matches = (char **)NULL;
+ rl_ignore_some_completions_function = bash_ignore_everything;
+ }
+ else
+ {
+#define CMD_IS_DIR(x) (absolute_pathname(x) == 0 && absolute_program(x) == 0 && *(x) != '~' && test_for_directory (x))
+
+ dot_in_path = 0;
+ matches = rl_completion_matches (text, command_word_completion_function);
+
+ /* If we are attempting command completion and nothing matches, we
+ do not want readline to perform filename completion for us. We
+ still want to be able to complete partial pathnames, so set the
+ completion ignore function to something which will remove
+ filenames and leave directories in the match list. */
+ if (matches == (char **)NULL)
+ rl_ignore_some_completions_function = bash_ignore_filenames;
+ else if (matches[1] == 0 && CMD_IS_DIR(matches[0]) && dot_in_path == 0)
+ /* If we found a single match, without looking in the current
+ directory (because it's not in $PATH), but the found name is
+ also a command in the current directory, suppress appending any
+ terminating character, since it's ambiguous. */
+ {
+ rl_completion_suppress_append = 1;
+ rl_filename_completion_desired = 0;
+ }
+ else if (matches[0] && matches[1] && STREQ (matches[0], matches[1]) && CMD_IS_DIR (matches[0]))
+ /* There are multiple instances of the same match (duplicate
+ completions haven't yet been removed). In this case, all of
+ the matches will be the same, and the duplicate removal code
+ will distill them all down to one. We turn on
+ rl_completion_suppress_append for the same reason as above.
+ Remember: we only care if there's eventually a single unique
+ completion. If there are multiple completions this won't
+ make a difference and the problem won't occur. */
+ {
+ rl_completion_suppress_append = 1;
+ rl_filename_completion_desired = 0;
+ }
+ }
+ }
+
+ /* This could be a globbing pattern, so try to expand it using pathname
+ expansion. */
+ if (!matches && glob_pattern_p (text))
+ {
+ matches = rl_completion_matches (text, glob_complete_word);
+ /* A glob expression that matches more than one filename is problematic.
+ If we match more than one filename, punt. */
+ if (matches && matches[1] && rl_completion_type == TAB)
+ {
+ strvec_dispose (matches);
+ matches = (char **)0;
+ }
+ }
+
+ return (matches);
+}
+
+/* This is the function to call when the word to complete is in a position
+ where a command word can be found. It grovels $PATH, looking for commands
+ that match. It also scans aliases, function names, and the shell_builtin
+ table. */
+char *
+command_word_completion_function (hint_text, state)
+ const char *hint_text;
+ int state;
+{
+ static char *hint = (char *)NULL;
+ static char *path = (char *)NULL;
+ static char *val = (char *)NULL;
+ static char *filename_hint = (char *)NULL;
+ static char *dequoted_hint = (char *)NULL;
+ static char *directory_part = (char *)NULL;
+ static char **glob_matches = (char **)NULL;
+ static int path_index, hint_len, dequoted_len, istate, igncase;
+ static int mapping_over, local_index, searching_path, hint_is_dir;
+ static int old_glob_ignore_case, globpat;
+ static SHELL_VAR **varlist = (SHELL_VAR **)NULL;
+#if defined (ALIAS)
+ static alias_t **alias_list = (alias_t **)NULL;
+#endif /* ALIAS */
+ char *temp;
+
+ /* We have to map over the possibilities for command words. If we have
+ no state, then make one just for that purpose. */
+ if (state == 0)
+ {
+ if (dequoted_hint && dequoted_hint != hint)
+ free (dequoted_hint);
+ if (hint)
+ free (hint);
+
+ mapping_over = searching_path = 0;
+ hint_is_dir = CMD_IS_DIR (hint_text);
+ val = (char *)NULL;
+
+ temp = rl_variable_value ("completion-ignore-case");
+ igncase = RL_BOOLEAN_VARIABLE_VALUE (temp);
+
+ if (glob_matches)
+ {
+ free (glob_matches);
+ glob_matches = (char **)NULL;
+ }
+
+ globpat = glob_pattern_p (hint_text);
+
+ /* If this is an absolute program name, do not check it against
+ aliases, reserved words, functions or builtins. We must check
+ whether or not it is unique, and, if so, whether that filename
+ is executable. */
+ if (globpat || absolute_program (hint_text))
+ {
+ /* Perform tilde expansion on what's passed, so we don't end up
+ passing filenames with tildes directly to stat(). */
+ if (*hint_text == '~')
+ {
+ hint = bash_tilde_expand (hint_text, 0);
+ directory_part = savestring (hint_text);
+ temp = strchr (directory_part, '/');
+ if (temp)
+ *temp = 0;
+ else
+ {
+ free (directory_part);
+ directory_part = (char *)NULL;
+ }
+ }
+ else
+ hint = savestring (hint_text);
+
+ dequoted_hint = hint;
+ /* If readline's completer found a quote character somewhere, but
+ didn't set the quote character, there must have been a quote
+ character embedded in the filename. It can't be at the start of
+ the filename, so we need to dequote the filename before we look
+ in the file system for it. */
+ if (rl_completion_found_quote && rl_completion_quote_character == 0)
+ {
+ dequoted_hint = bash_dequote_filename (hint, 0);
+ free (hint);
+ hint = dequoted_hint;
+ }
+ dequoted_len = hint_len = strlen (hint);
+
+ if (filename_hint)
+ free (filename_hint);
+
+ filename_hint = savestring (hint);
+
+ istate = 0;
+
+ if (globpat)
+ {
+ mapping_over = 5;
+ goto globword;
+ }
+ else
+ {
+ mapping_over = 4;
+ goto inner;
+ }
+ }
+
+ dequoted_hint = hint = savestring (hint_text);
+ dequoted_len = hint_len = strlen (hint);
+
+ if (rl_completion_found_quote && rl_completion_quote_character == 0)
+ {
+ dequoted_hint = bash_dequote_filename (hint, 0);
+ dequoted_len = strlen (dequoted_hint);
+ }
+
+ path = get_string_value ("PATH");
+ path_index = dot_in_path = 0;
+
+ /* Initialize the variables for each type of command word. */
+ local_index = 0;
+
+ if (varlist)
+ free (varlist);
+
+ varlist = all_visible_functions ();
+
+#if defined (ALIAS)
+ if (alias_list)
+ free (alias_list);
+
+ alias_list = all_aliases ();
+#endif /* ALIAS */
+ }
+
+ /* mapping_over says what we are currently hacking. Note that every case
+ in this list must fall through when there are no more possibilities. */
+
+ switch (mapping_over)
+ {
+ case 0: /* Aliases come first. */
+#if defined (ALIAS)
+ while (alias_list && alias_list[local_index])
+ {
+ register char *alias;
+
+ alias = alias_list[local_index++]->name;
+
+ if (STREQN (alias, hint, hint_len))
+ return (savestring (alias));
+ }
+#endif /* ALIAS */
+ local_index = 0;
+ mapping_over++;
+
+ case 1: /* Then shell reserved words. */
+ {
+ while (word_token_alist[local_index].word)
+ {
+ register char *reserved_word;
+
+ reserved_word = word_token_alist[local_index++].word;
+
+ if (STREQN (reserved_word, hint, hint_len))
+ return (savestring (reserved_word));
+ }
+ local_index = 0;
+ mapping_over++;
+ }
+
+ case 2: /* Then function names. */
+ while (varlist && varlist[local_index])
+ {
+ register char *varname;
+
+ varname = varlist[local_index++]->name;
+
+ if (STREQN (varname, hint, hint_len))
+ return (savestring (varname));
+ }
+ local_index = 0;
+ mapping_over++;
+
+ case 3: /* Then shell builtins. */
+ for (; local_index < num_shell_builtins; local_index++)
+ {
+ /* Ignore it if it doesn't have a function pointer or if it
+ is not currently enabled. */
+ if (!shell_builtins[local_index].function ||
+ (shell_builtins[local_index].flags & BUILTIN_ENABLED) == 0)
+ continue;
+
+ if (STREQN (shell_builtins[local_index].name, hint, hint_len))
+ {
+ int i = local_index++;
+
+ return (savestring (shell_builtins[i].name));
+ }
+ }
+ local_index = 0;
+ mapping_over++;
+ }
+
+globword:
+ /* Limited support for completing command words with globbing chars. Only
+ a single match (multiple matches that end up reducing the number of
+ characters in the common prefix are bad) will ever be returned on
+ regular completion. */
+ if (glob_pattern_p (hint))
+ {
+ if (state == 0)
+ {
+ glob_ignore_case = igncase;
+ glob_matches = shell_glob_filename (hint);
+ glob_ignore_case = old_glob_ignore_case;
+
+ if (GLOB_FAILED (glob_matches) || glob_matches == 0)
+ {
+ glob_matches = (char **)NULL;
+ return ((char *)NULL);
+ }
+
+ local_index = 0;
+
+ if (glob_matches[1] && rl_completion_type == TAB) /* multiple matches are bad */
+ return ((char *)NULL);
+ }
+
+ while (val = glob_matches[local_index++])
+ {
+ if (executable_or_directory (val))
+ {
+ if (*hint_text == '~')
+ {
+ temp = restore_tilde (val, directory_part);
+ free (val);
+ val = temp;
+ }
+ return (val);
+ }
+ free (val);
+ }
+
+ glob_ignore_case = old_glob_ignore_case;
+ return ((char *)NULL);
+ }
+
+ /* If the text passed is a directory in the current directory, return it
+ as a possible match. Executables in directories in the current
+ directory can be specified using relative pathnames and successfully
+ executed even when `.' is not in $PATH. */
+ if (hint_is_dir)
+ {
+ hint_is_dir = 0; /* only return the hint text once */
+ return (savestring (hint_text));
+ }
+
+ /* Repeatedly call filename_completion_function while we have
+ members of PATH left. Question: should we stat each file?
+ Answer: we call executable_file () on each file. */
+ outer:
+
+ istate = (val != (char *)NULL);
+
+ if (istate == 0)
+ {
+ char *current_path;
+
+ /* Get the next directory from the path. If there is none, then we
+ are all done. */
+ if (path == 0 || path[path_index] == 0 ||
+ (current_path = extract_colon_unit (path, &path_index)) == 0)
+ return ((char *)NULL);
+
+ searching_path = 1;
+ if (*current_path == 0)
+ {
+ free (current_path);
+ current_path = savestring (".");
+ }
+
+ if (*current_path == '~')
+ {
+ char *t;
+
+ t = bash_tilde_expand (current_path, 0);
+ free (current_path);
+ current_path = t;
+ }
+
+ if (current_path[0] == '.' && current_path[1] == '\0')
+ dot_in_path = 1;
+
+ if (filename_hint)
+ free (filename_hint);
+
+ filename_hint = sh_makepath (current_path, hint, 0);
+ free (current_path); /* XXX */
+ }
+
+ inner:
+ val = rl_filename_completion_function (filename_hint, istate);
+ istate = 1;
+
+ if (val == 0)
+ {
+ /* If the hint text is an absolute program, then don't bother
+ searching through PATH. */
+ if (absolute_program (hint))
+ return ((char *)NULL);
+
+ goto outer;
+ }
+ else
+ {
+ int match, freetemp;
+
+ if (absolute_program (hint))
+ {
+ if (igncase == 0)
+ match = strncmp (val, hint, hint_len) == 0;
+ else
+ match = strncasecmp (val, hint, hint_len) == 0;
+
+ /* If we performed tilde expansion, restore the original
+ filename. */
+ if (*hint_text == '~')
+ temp = restore_tilde (val, directory_part);
+ else
+ temp = savestring (val);
+ freetemp = 1;
+ }
+ else
+ {
+ temp = strrchr (val, '/');
+
+ if (temp)
+ {
+ temp++;
+ if (igncase == 0)
+ freetemp = match = strncmp (temp, hint, hint_len) == 0;
+ else
+ freetemp = match = strncasecmp (temp, hint, hint_len) == 0;
+ if (match)
+ temp = savestring (temp);
+ }
+ else
+ freetemp = match = 0;
+ }
+
+#if 0
+ /* If we have found a match, and it is an executable file or a
+ directory name, return it. */
+ if (match && executable_or_directory (val))
+#else
+ /* If we have found a match, and it is an executable file, return it.
+ We don't return directory names when searching $PATH, since the
+ bash execution code won't find executables in directories which
+ appear in directories in $PATH when they're specified using
+ relative pathnames. */
+ if (match && (searching_path ? executable_file (val) : executable_or_directory (val)))
+#endif
+ {
+ free (val);
+ val = ""; /* So it won't be NULL. */
+ return (temp);
+ }
+ else
+ {
+ if (freetemp)
+ free (temp);
+ free (val);
+ goto inner;
+ }
+ }
+}
+
+/* Completion inside an unterminated command substitution. */
+static char *
+command_subst_completion_function (text, state)
+ const char *text;
+ int state;
+{
+ static char **matches = (char **)NULL;
+ static const char *orig_start;
+ static char *filename_text = (char *)NULL;
+ static int cmd_index, start_len;
+ char *value;
+
+ if (state == 0)
+ {
+ if (filename_text)
+ free (filename_text);
+ orig_start = text;
+ if (*text == '`')
+ text++;
+ else if (*text == '$' && text[1] == '(') /* ) */
+ text += 2;
+ /* If the text was quoted, suppress any quote character that the
+ readline completion code would insert. */
+ rl_completion_suppress_quote = 1;
+ start_len = text - orig_start;
+ filename_text = savestring (text);
+ if (matches)
+ free (matches);
+
+ /*
+ * At this point we can entertain the idea of re-parsing
+ * `filename_text' into a (possibly incomplete) command name and
+ * arguments, and doing completion based on that. This is
+ * currently very rudimentary, but it is a small improvement.
+ */
+ for (value = filename_text + strlen (filename_text) - 1; value > filename_text; value--)
+ if (whitespace (*value) || member (*value, COMMAND_SEPARATORS))
+ break;
+ if (value <= filename_text)
+ matches = rl_completion_matches (filename_text, command_word_completion_function);
+ else
+ {
+ value++;
+ start_len += value - filename_text;
+ if (whitespace (value[-1]))
+ matches = rl_completion_matches (value, rl_filename_completion_function);
+ else
+ matches = rl_completion_matches (value, command_word_completion_function);
+ }
+
+ /* If there is more than one match, rl_completion_matches has already
+ put the lcd in matches[0]. Skip over it. */
+ cmd_index = matches && matches[0] && matches[1];
+
+ /* If there's a single match and it's a directory, set the append char
+ to the expected `/'. Otherwise, don't append anything. */
+ if (matches && matches[0] && matches[1] == 0 && test_for_directory (matches[0]))
+ rl_completion_append_character = '/';
+ else
+ rl_completion_suppress_append = 1;
+ }
+
+ if (!matches || !matches[cmd_index])
+ {
+ rl_filename_quoting_desired = 0; /* disable quoting */
+ return ((char *)NULL);
+ }
+ else
+ {
+ value = (char *)xmalloc (1 + start_len + strlen (matches[cmd_index]));
+
+ if (start_len == 1)
+ value[0] = *orig_start;
+ else
+ strncpy (value, orig_start, start_len);
+
+ strcpy (value + start_len, matches[cmd_index]);
+
+ cmd_index++;
+ return (value);
+ }
+}
+
+/* Okay, now we write the entry_function for variable completion. */
+static char *
+variable_completion_function (text, state)
+ const char *text;
+ int state;
+{
+ static char **varlist = (char **)NULL;
+ static int varlist_index;
+ static char *varname = (char *)NULL;
+ static int namelen;
+ static int first_char, first_char_loc;
+
+ if (!state)
+ {
+ if (varname)
+ free (varname);
+
+ first_char_loc = 0;
+ first_char = text[0];
+
+ if (first_char == '$')
+ first_char_loc++;
+
+ if (text[first_char_loc] == '{')
+ first_char_loc++;
+
+ varname = savestring (text + first_char_loc);
+
+ namelen = strlen (varname);
+ if (varlist)
+ strvec_dispose (varlist);
+
+ varlist = all_variables_matching_prefix (varname);
+ varlist_index = 0;
+ }
+
+ if (!varlist || !varlist[varlist_index])
+ {
+ return ((char *)NULL);
+ }
+ else
+ {
+ char *value;
+
+ value = (char *)xmalloc (4 + strlen (varlist[varlist_index]));
+
+ if (first_char_loc)
+ {
+ value[0] = first_char;
+ if (first_char_loc == 2)
+ value[1] = '{';
+ }
+
+ strcpy (value + first_char_loc, varlist[varlist_index]);
+ if (first_char_loc == 2)
+ strcat (value, "}");
+
+ varlist_index++;
+ return (value);
+ }
+}
+
+/* How about a completion function for hostnames? */
+static char *
+hostname_completion_function (text, state)
+ const char *text;
+ int state;
+{
+ static char **list = (char **)NULL;
+ static int list_index = 0;
+ static int first_char, first_char_loc;
+
+ /* If we don't have any state, make some. */
+ if (state == 0)
+ {
+ FREE (list);
+
+ list = (char **)NULL;
+
+ first_char_loc = 0;
+ first_char = *text;
+
+ if (first_char == '@')
+ first_char_loc++;
+
+ list = hostnames_matching ((char *)text+first_char_loc);
+ list_index = 0;
+ }
+
+ if (list && list[list_index])
+ {
+ char *t;
+
+ t = (char *)xmalloc (2 + strlen (list[list_index]));
+ *t = first_char;
+ strcpy (t + first_char_loc, list[list_index]);
+ list_index++;
+ return (t);
+ }
+
+ return ((char *)NULL);
+}
+
+/*
+ * A completion function for service names from /etc/services (or wherever).
+ */
+char *
+bash_servicename_completion_function (text, state)
+ const char *text;
+ int state;
+{
+#if defined (__WIN32__) || defined (__OPENNT) || !defined (HAVE_GETSERVENT)
+ return ((char *)NULL);
+#else
+ static char *sname = (char *)NULL;
+ static struct servent *srvent;
+ static int snamelen, firstc;
+ char *value;
+ char **alist, *aentry;
+ int afound;
+
+ if (state == 0)
+ {
+ FREE (sname);
+ firstc = *text;
+
+ sname = savestring (text);
+ snamelen = strlen (sname);
+ setservent (0);
+ }
+
+ while (srvent = getservent ())
+ {
+ afound = 0;
+ if (snamelen == 0 || (STREQN (sname, srvent->s_name, snamelen)))
+ break;
+ /* Not primary, check aliases */
+ for (alist = srvent->s_aliases; *alist; alist++)
+ {
+ aentry = *alist;
+ if (STREQN (sname, aentry, snamelen))
+ {
+ afound = 1;
+ break;
+ }
+ }
+
+ if (afound)
+ break;
+ }
+
+ if (srvent == 0)
+ {
+ endservent ();
+ return ((char *)NULL);
+ }
+
+ value = afound ? savestring (aentry) : savestring (srvent->s_name);
+ return value;
+#endif
+}
+
+/*
+ * A completion function for group names from /etc/group (or wherever).
+ */
+char *
+bash_groupname_completion_function (text, state)
+ const char *text;
+ int state;
+{
+#if defined (__WIN32__) || defined (__OPENNT) || !defined (HAVE_GRP_H)
+ return ((char *)NULL);
+#else
+ static char *gname = (char *)NULL;
+ static struct group *grent;
+ static int gnamelen;
+ char *value;
+
+ if (state == 0)
+ {
+ FREE (gname);
+ gname = savestring (text);
+ gnamelen = strlen (gname);
+
+ setgrent ();
+ }
+
+ while (grent = getgrent ())
+ {
+ if (gnamelen == 0 || (STREQN (gname, grent->gr_name, gnamelen)))
+ break;
+ }
+
+ if (grent == 0)
+ {
+ endgrent ();
+ return ((char *)NULL);
+ }
+
+ value = savestring (grent->gr_name);
+ return (value);
+#endif
+}
+
+/* Functions to perform history and alias expansions on the current line. */
+
+#if defined (BANG_HISTORY)
+/* Perform history expansion on the current line. If no history expansion
+ is done, pre_process_line() returns what it was passed, so we need to
+ allocate a new line here. */
+static char *
+history_expand_line_internal (line)
+ char *line;
+{
+ char *new_line;
+ int old_verify;
+
+ old_verify = hist_verify;
+ hist_verify = 0;
+ new_line = pre_process_line (line, 0, 0);
+ hist_verify = old_verify;
+
+ return (new_line == line) ? savestring (line) : new_line;
+}
+#endif
+
+/* There was an error in expansion. Let the preprocessor print
+ the error here. */
+static void
+cleanup_expansion_error ()
+{
+ char *to_free;
+#if defined (BANG_HISTORY)
+ int old_verify;
+
+ old_verify = hist_verify;
+ hist_verify = 0;
+#endif
+
+ fprintf (rl_outstream, "\r\n");
+ to_free = pre_process_line (rl_line_buffer, 1, 0);
+#if defined (BANG_HISTORY)
+ hist_verify = old_verify;
+#endif
+ if (to_free != rl_line_buffer)
+ FREE (to_free);
+ putc ('\r', rl_outstream);
+ rl_forced_update_display ();
+}
+
+/* If NEW_LINE differs from what is in the readline line buffer, add an
+ undo record to get from the readline line buffer contents to the new
+ line and make NEW_LINE the current readline line. */
+static void
+maybe_make_readline_line (new_line)
+ char *new_line;
+{
+ if (strcmp (new_line, rl_line_buffer) != 0)
+ {
+ rl_point = rl_end;
+
+ rl_add_undo (UNDO_BEGIN, 0, 0, 0);
+ rl_delete_text (0, rl_point);
+ rl_point = rl_end = rl_mark = 0;
+ rl_insert_text (new_line);
+ rl_add_undo (UNDO_END, 0, 0, 0);
+ }
+}
+
+/* Make NEW_LINE be the current readline line. This frees NEW_LINE. */
+static void
+set_up_new_line (new_line)
+ char *new_line;
+{
+ int old_point, at_end;
+
+ old_point = rl_point;
+ at_end = rl_point == rl_end;
+
+ /* If the line was history and alias expanded, then make that
+ be one thing to undo. */
+ maybe_make_readline_line (new_line);
+ free (new_line);
+
+ /* Place rl_point where we think it should go. */
+ if (at_end)
+ rl_point = rl_end;
+ else if (old_point < rl_end)
+ {
+ rl_point = old_point;
+ if (!whitespace (rl_line_buffer[rl_point]))
+ rl_forward_word (1, 0);
+ }
+}
+
+#if defined (ALIAS)
+/* Expand aliases in the current readline line. */
+static int
+alias_expand_line (count, ignore)
+ int count, ignore;
+{
+ char *new_line;
+
+ new_line = alias_expand (rl_line_buffer);
+
+ if (new_line)
+ {
+ set_up_new_line (new_line);
+ return (0);
+ }
+ else
+ {
+ cleanup_expansion_error ();
+ return (1);
+ }
+}
+#endif
+
+#if defined (BANG_HISTORY)
+/* History expand the line. */
+static int
+history_expand_line (count, ignore)
+ int count, ignore;
+{
+ char *new_line;
+
+ new_line = history_expand_line_internal (rl_line_buffer);
+
+ if (new_line)
+ {
+ set_up_new_line (new_line);
+ return (0);
+ }
+ else
+ {
+ cleanup_expansion_error ();
+ return (1);
+ }
+}
+
+/* Expand history substitutions in the current line and then insert a
+ space (hopefully close to where we were before). */
+static int
+tcsh_magic_space (count, ignore)
+ int count, ignore;
+{
+ int dist_from_end, old_point;
+
+ old_point = rl_point;
+ dist_from_end = rl_end - rl_point;
+ if (history_expand_line (count, ignore) == 0)
+ {
+ /* Try a simple heuristic from Stephen Gildea <gildea@intouchsys.com>.
+ This works if all expansions were before rl_point or if no expansions
+ were performed. */
+ rl_point = (old_point == 0) ? old_point : rl_end - dist_from_end;
+ rl_insert (1, ' ');
+ return (0);
+ }
+ else
+ return (1);
+}
+#endif /* BANG_HISTORY */
+
+/* History and alias expand the line. */
+static int
+history_and_alias_expand_line (count, ignore)
+ int count, ignore;
+{
+ char *new_line;
+
+ new_line = 0;
+#if defined (BANG_HISTORY)
+ new_line = history_expand_line_internal (rl_line_buffer);
+#endif
+
+#if defined (ALIAS)
+ if (new_line)
+ {
+ char *alias_line;
+
+ alias_line = alias_expand (new_line);
+ free (new_line);
+ new_line = alias_line;
+ }
+#endif /* ALIAS */
+
+ if (new_line)
+ {
+ set_up_new_line (new_line);
+ return (0);
+ }
+ else
+ {
+ cleanup_expansion_error ();
+ return (1);
+ }
+}
+
+/* History and alias expand the line, then perform the shell word
+ expansions by calling expand_string. This can't use set_up_new_line()
+ because we want the variable expansions as a separate undo'able
+ set of operations. */
+static int
+shell_expand_line (count, ignore)
+ int count, ignore;
+{
+ char *new_line;
+ WORD_LIST *expanded_string;
+
+ new_line = 0;
+#if defined (BANG_HISTORY)
+ new_line = history_expand_line_internal (rl_line_buffer);
+#endif
+
+#if defined (ALIAS)
+ if (new_line)
+ {
+ char *alias_line;
+
+ alias_line = alias_expand (new_line);
+ free (new_line);
+ new_line = alias_line;
+ }
+#endif /* ALIAS */
+
+ if (new_line)
+ {
+ int old_point = rl_point;
+ int at_end = rl_point == rl_end;
+
+ /* If the line was history and alias expanded, then make that
+ be one thing to undo. */
+ maybe_make_readline_line (new_line);
+ free (new_line);
+
+ /* If there is variable expansion to perform, do that as a separate
+ operation to be undone. */
+ new_line = savestring (rl_line_buffer);
+ expanded_string = expand_string (new_line, 0);
+ FREE (new_line);
+ if (expanded_string == 0)
+ {
+ new_line = (char *)xmalloc (1);
+ new_line[0] = '\0';
+ }
+ else
+ {
+ new_line = string_list (expanded_string);
+ dispose_words (expanded_string);
+ }
+
+ maybe_make_readline_line (new_line);
+ free (new_line);
+
+ /* Place rl_point where we think it should go. */
+ if (at_end)
+ rl_point = rl_end;
+ else if (old_point < rl_end)
+ {
+ rl_point = old_point;
+ if (!whitespace (rl_line_buffer[rl_point]))
+ rl_forward_word (1, 0);
+ }
+ return 0;
+ }
+ else
+ {
+ cleanup_expansion_error ();
+ return 1;
+ }
+}
+
+/* If FIGNORE is set, then don't match files with the given suffixes when
+ completing filenames. If only one of the possibilities has an acceptable
+ suffix, delete the others, else just return and let the completer
+ signal an error. It is called by the completer when real
+ completions are done on filenames by the completer's internal
+ function, not for completion lists (M-?) and not on "other"
+ completion types, such as hostnames or commands. */
+
+static struct ignorevar fignore =
+{
+ "FIGNORE",
+ (struct ign *)0,
+ 0,
+ (char *)0,
+ (sh_iv_item_func_t *) 0,
+};
+
+static void
+_ignore_completion_names (names, name_func)
+ char **names;
+ sh_ignore_func_t *name_func;
+{
+ char **newnames;
+ int idx, nidx;
+ char **oldnames;
+ int oidx;
+
+ /* If there is only one completion, see if it is acceptable. If it is
+ not, free it up. In any case, short-circuit and return. This is a
+ special case because names[0] is not the prefix of the list of names
+ if there is only one completion; it is the completion itself. */
+ if (names[1] == (char *)0)
+ {
+ if (force_fignore)
+ if ((*name_func) (names[0]) == 0)
+ {
+ free (names[0]);
+ names[0] = (char *)NULL;
+ }
+
+ return;
+ }
+
+ /* Allocate space for array to hold list of pointers to matching
+ filenames. The pointers are copied back to NAMES when done. */
+ for (nidx = 1; names[nidx]; nidx++)
+ ;
+ newnames = strvec_create (nidx + 1);
+
+ if (force_fignore == 0)
+ {
+ oldnames = strvec_create (nidx - 1);
+ oidx = 0;
+ }
+
+ newnames[0] = names[0];
+ for (idx = nidx = 1; names[idx]; idx++)
+ {
+ if ((*name_func) (names[idx]))
+ newnames[nidx++] = names[idx];
+ else if (force_fignore == 0)
+ oldnames[oidx++] = names[idx];
+ else
+ free (names[idx]);
+ }
+
+ newnames[nidx] = (char *)NULL;
+
+ /* If none are acceptable then let the completer handle it. */
+ if (nidx == 1)
+ {
+ if (force_fignore)
+ {
+ free (names[0]);
+ names[0] = (char *)NULL;
+ }
+ else
+ free (oldnames);
+
+ free (newnames);
+ return;
+ }
+
+ if (force_fignore == 0)
+ {
+ while (oidx)
+ free (oldnames[--oidx]);
+ free (oldnames);
+ }
+
+ /* If only one is acceptable, copy it to names[0] and return. */
+ if (nidx == 2)
+ {
+ free (names[0]);
+ names[0] = newnames[1];
+ names[1] = (char *)NULL;
+ free (newnames);
+ return;
+ }
+
+ /* Copy the acceptable names back to NAMES, set the new array end,
+ and return. */
+ for (nidx = 1; newnames[nidx]; nidx++)
+ names[nidx] = newnames[nidx];
+ names[nidx] = (char *)NULL;
+ free (newnames);
+}
+
+static int
+name_is_acceptable (name)
+ const char *name;
+{
+ struct ign *p;
+ int nlen;
+
+ for (nlen = strlen (name), p = fignore.ignores; p->val; p++)
+ {
+ if (nlen > p->len && p->len > 0 && STREQ (p->val, &name[nlen - p->len]))
+ return (0);
+ }
+
+ return (1);
+}
+
+#if 0
+static int
+ignore_dot_names (name)
+ char *name;
+{
+ return (name[0] != '.');
+}
+#endif
+
+static int
+filename_completion_ignore (names)
+ char **names;
+{
+#if 0
+ if (glob_dot_filenames == 0)
+ _ignore_completion_names (names, ignore_dot_names);
+#endif
+
+ setup_ignore_patterns (&fignore);
+
+ if (fignore.num_ignores == 0)
+ return 0;
+
+ _ignore_completion_names (names, name_is_acceptable);
+
+ return 0;
+}
+
+/* Return 1 if NAME is a directory. NAME undergoes tilde expansion. */
+static int
+test_for_directory (name)
+ const char *name;
+{
+ char *fn;
+ int r;
+
+ fn = bash_tilde_expand (name, 0);
+ r = file_isdir (fn);
+ free (fn);
+
+ return (r);
+}
+
+/* Remove files from NAMES, leaving directories. */
+static int
+bash_ignore_filenames (names)
+ char **names;
+{
+ _ignore_completion_names (names, test_for_directory);
+ return 0;
+}
+
+static int
+return_zero (name)
+ const char *name;
+{
+ return 0;
+}
+
+static int
+bash_ignore_everything (names)
+ char **names;
+{
+ _ignore_completion_names (names, return_zero);
+ return 0;
+}
+
+/* Replace a tilde-prefix in VAL with a `~', assuming the user typed it. VAL
+ is an expanded filename. DIRECTORY_PART is the tilde-prefix portion
+ of the un-tilde-expanded version of VAL (what the user typed). */
+static char *
+restore_tilde (val, directory_part)
+ char *val, *directory_part;
+{
+ int l, vl, dl2, xl;
+ char *dh2, *expdir, *ret;
+
+ vl = strlen (val);
+
+ /* We need to duplicate the expansions readline performs on the directory
+ portion before passing it to our completion function. */
+ dh2 = directory_part ? bash_dequote_filename (directory_part, 0) : 0;
+ bash_directory_expansion (&dh2);
+ dl2 = strlen (dh2);
+
+ expdir = bash_tilde_expand (directory_part, 0);
+ xl = strlen (expdir);
+ free (expdir);
+
+ /*
+ dh2 = unexpanded but dequoted tilde-prefix
+ dl2 = length of tilde-prefix
+ expdir = tilde-expanded tilde-prefix
+ xl = length of expanded tilde-prefix
+ l = length of remainder after tilde-prefix
+ */
+ l = (vl - xl) + 1;
+
+ ret = (char *)xmalloc (dl2 + 2 + l);
+ strcpy (ret, dh2);
+ strcpy (ret + dl2, val + xl);
+
+ free (dh2);
+ return (ret);
+}
+
+/* Simulate the expansions that will be performed by
+ rl_filename_completion_function. This must be called with the address of
+ a pointer to malloc'd memory. */
+static void
+bash_directory_expansion (dirname)
+ char **dirname;
+{
+ char *d, *nd;
+
+ d = savestring (*dirname);
+
+ if (rl_directory_rewrite_hook)
+ (*rl_directory_rewrite_hook) (&d);
+
+ if (rl_directory_completion_hook && (*rl_directory_completion_hook) (&d))
+ {
+ free (*dirname);
+ *dirname = d;
+ }
+ else if (rl_completion_found_quote)
+ {
+ nd = bash_dequote_filename (d, rl_completion_quote_character);
+ free (*dirname);
+ free (d);
+ *dirname = nd;
+ }
+}
+
+/* If necessary, rewrite directory entry */
+static char *
+bash_filename_rewrite_hook (fname, fnlen)
+ char *fname;
+ int fnlen;
+{
+ char *conv;
+
+ conv = fnx_fromfs (fname, fnlen);
+ if (conv != fname)
+ conv = savestring (conv);
+ return conv;
+}
+
+/* Handle symbolic link references and other directory name
+ expansions while hacking completion. */
+static int
+bash_directory_completion_hook (dirname)
+ char **dirname;
+{
+ char *local_dirname, *new_dirname, *t;
+ int return_value, should_expand_dirname;
+ WORD_LIST *wl;
+ struct stat sb;
+
+ return_value = should_expand_dirname = 0;
+ local_dirname = *dirname;
+
+ if (mbschr (local_dirname, '$'))
+ should_expand_dirname = 1;
+ else
+ {
+ t = mbschr (local_dirname, '`');
+ if (t && unclosed_pair (local_dirname, strlen (local_dirname), "`") == 0)
+ should_expand_dirname = 1;
+ }
+
+#if defined (HAVE_LSTAT)
+ if (should_expand_dirname && lstat (local_dirname, &sb) == 0)
+#else
+ if (should_expand_dirname && stat (local_dirname, &sb) == 0)
+#endif
+ should_expand_dirname = 0;
+
+ if (should_expand_dirname)
+ {
+ new_dirname = savestring (local_dirname);
+ wl = expand_prompt_string (new_dirname, 0, W_NOCOMSUB); /* does the right thing */
+ if (wl)
+ {
+ *dirname = string_list (wl);
+ /* Tell the completer to replace the directory name only if we
+ actually expanded something. */
+ return_value = STREQ (local_dirname, *dirname) == 0;
+ free (local_dirname);
+ free (new_dirname);
+ dispose_words (wl);
+ local_dirname = *dirname;
+ }
+ else
+ {
+ free (new_dirname);
+ free (local_dirname);
+ *dirname = (char *)xmalloc (1);
+ **dirname = '\0';
+ return 1;
+ }
+ }
+ else
+ {
+ /* Dequote the filename even if we don't expand it. */
+ new_dirname = bash_dequote_filename (local_dirname, rl_completion_quote_character);
+ free (local_dirname);
+ local_dirname = *dirname = new_dirname;
+ }
+
+ if (no_symbolic_links == 0 && (local_dirname[0] != '.' || local_dirname[1]))
+ {
+ char *temp1, *temp2;
+ int len1, len2;
+
+ t = get_working_directory ("symlink-hook");
+ temp1 = make_absolute (local_dirname, t);
+ free (t);
+ temp2 = sh_canonpath (temp1, PATH_CHECKDOTDOT|PATH_CHECKEXISTS);
+
+ /* Try spelling correction if initial canonicalization fails. */
+ if (temp2 == 0 && dircomplete_spelling)
+ {
+ temp2 = dirspell (temp1);
+ if (temp2)
+ {
+ free (temp1);
+ temp1 = temp2;
+ temp2 = sh_canonpath (temp1, PATH_CHECKDOTDOT|PATH_CHECKEXISTS);
+ return_value = temp2 != 0;
+ }
+ }
+ /* If we can't canonicalize, bail. */
+ if (temp2 == 0)
+ {
+ free (temp1);
+ return 1;
+ }
+ len1 = strlen (temp1);
+ if (temp1[len1 - 1] == '/')
+ {
+ len2 = strlen (temp2);
+ if (len2 > 2) /* don't append `/' to `/' or `//' */
+ {
+ temp2 = (char *)xrealloc (temp2, len2 + 2);
+ temp2[len2] = '/';
+ temp2[len2 + 1] = '\0';
+ }
+ }
+ free (local_dirname);
+ *dirname = temp2;
+ free (temp1);
+ }
+ return (return_value);
+}
+
+static char **history_completion_array = (char **)NULL;
+static int harry_size;
+static int harry_len;
+
+static void
+build_history_completion_array ()
+{
+ register int i, j;
+ HIST_ENTRY **hlist;
+ char **tokens;
+
+ /* First, clear out the current dynamic history completion list. */
+ if (harry_size)
+ {
+ strvec_dispose (history_completion_array);
+ history_completion_array = (char **)NULL;
+ harry_size = 0;
+ harry_len = 0;
+ }
+
+ /* Next, grovel each line of history, making each shell-sized token
+ a separate entry in the history_completion_array. */
+ hlist = history_list ();
+
+ if (hlist)
+ {
+ for (i = 0; hlist[i]; i++)
+ ;
+ for ( --i; i >= 0; i--)
+ {
+ /* Separate each token, and place into an array. */
+ tokens = history_tokenize (hlist[i]->line);
+
+ for (j = 0; tokens && tokens[j]; j++)
+ {
+ if (harry_len + 2 > harry_size)
+ history_completion_array = strvec_resize (history_completion_array, harry_size += 10);
+
+ history_completion_array[harry_len++] = tokens[j];
+ history_completion_array[harry_len] = (char *)NULL;
+ }
+ free (tokens);
+ }
+
+ /* Sort the complete list of tokens. */
+ if (dabbrev_expand_active == 0)
+ qsort (history_completion_array, harry_len, sizeof (char *), (QSFUNC *)strvec_strcmp);
+ }
+}
+
+static char *
+history_completion_generator (hint_text, state)
+ const char *hint_text;
+ int state;
+{
+ static int local_index, len;
+ static const char *text;
+
+ /* If this is the first call to the generator, then initialize the
+ list of strings to complete over. */
+ if (state == 0)
+ {
+ if (dabbrev_expand_active) /* This is kind of messy */
+ rl_completion_suppress_append = 1;
+ local_index = 0;
+ build_history_completion_array ();
+ text = hint_text;
+ len = strlen (text);
+ }
+
+ while (history_completion_array && history_completion_array[local_index])
+ {
+ if (strncmp (text, history_completion_array[local_index++], len) == 0)
+ return (savestring (history_completion_array[local_index - 1]));
+ }
+ return ((char *)NULL);
+}
+
+static int
+dynamic_complete_history (count, key)
+ int count, key;
+{
+ int r;
+ rl_compentry_func_t *orig_func;
+ rl_completion_func_t *orig_attempt_func;
+
+ orig_func = rl_completion_entry_function;
+ orig_attempt_func = rl_attempted_completion_function;
+
+ rl_completion_entry_function = history_completion_generator;
+ rl_attempted_completion_function = (rl_completion_func_t *)NULL;
+
+ /* XXX - use rl_completion_mode here? */
+ if (rl_last_func == dynamic_complete_history)
+ r = rl_complete_internal ('?');
+ else
+ r = rl_complete_internal (TAB);
+
+ rl_completion_entry_function = orig_func;
+ rl_attempted_completion_function = orig_attempt_func;
+ return r;
+}
+
+static int
+bash_dabbrev_expand (count, key)
+ int count, key;
+{
+ int r, orig_suppress, orig_sort;
+ rl_compentry_func_t *orig_func;
+ rl_completion_func_t *orig_attempt_func;
+
+ orig_func = rl_menu_completion_entry_function;
+ orig_attempt_func = rl_attempted_completion_function;
+ orig_suppress = rl_completion_suppress_append;
+ orig_sort = rl_sort_completion_matches;
+
+ rl_menu_completion_entry_function = history_completion_generator;
+ rl_attempted_completion_function = (rl_completion_func_t *)NULL;
+ rl_filename_completion_desired = 0;
+ rl_completion_suppress_append = 1;
+ rl_sort_completion_matches = 0;
+
+ /* XXX - use rl_completion_mode here? */
+ dabbrev_expand_active = 1;
+ if (rl_last_func == bash_dabbrev_expand)
+ rl_last_func = rl_menu_complete;
+ r = rl_menu_complete (count, key);
+ dabbrev_expand_active = 0;
+
+ rl_last_func = bash_dabbrev_expand;
+ rl_menu_completion_entry_function = orig_func;
+ rl_attempted_completion_function = orig_attempt_func;
+ rl_completion_suppress_append = orig_suppress;
+ rl_sort_completion_matches = orig_sort;
+
+ return r;
+}
+
+#if defined (SPECIFIC_COMPLETION_FUNCTIONS)
+static int
+bash_complete_username (ignore, ignore2)
+ int ignore, ignore2;
+{
+ return bash_complete_username_internal (rl_completion_mode (bash_complete_username));
+}
+
+static int
+bash_possible_username_completions (ignore, ignore2)
+ int ignore, ignore2;
+{
+ return bash_complete_username_internal ('?');
+}
+
+static int
+bash_complete_username_internal (what_to_do)
+ int what_to_do;
+{
+ return bash_specific_completion (what_to_do, rl_username_completion_function);
+}
+
+static int
+bash_complete_filename (ignore, ignore2)
+ int ignore, ignore2;
+{
+ return bash_complete_filename_internal (rl_completion_mode (bash_complete_filename));
+}
+
+static int
+bash_possible_filename_completions (ignore, ignore2)
+ int ignore, ignore2;
+{
+ return bash_complete_filename_internal ('?');
+}
+
+static int
+bash_complete_filename_internal (what_to_do)
+ int what_to_do;
+{
+ rl_compentry_func_t *orig_func;
+ rl_completion_func_t *orig_attempt_func;
+ rl_icppfunc_t *orig_dir_func;
+ /*const*/ char *orig_rl_completer_word_break_characters;
+ int r;
+
+ orig_func = rl_completion_entry_function;
+ orig_attempt_func = rl_attempted_completion_function;
+ orig_dir_func = rl_directory_completion_hook;
+ orig_rl_completer_word_break_characters = rl_completer_word_break_characters;
+ rl_completion_entry_function = rl_filename_completion_function;
+ rl_attempted_completion_function = (rl_completion_func_t *)NULL;
+ rl_directory_completion_hook = (rl_icppfunc_t *)NULL;
+ rl_completer_word_break_characters = " \t\n\"\'";
+
+ r = rl_complete_internal (what_to_do);
+
+ rl_completion_entry_function = orig_func;
+ rl_attempted_completion_function = orig_attempt_func;
+ rl_directory_completion_hook = orig_dir_func;
+ rl_completer_word_break_characters = orig_rl_completer_word_break_characters;
+
+ return r;
+}
+
+static int
+bash_complete_hostname (ignore, ignore2)
+ int ignore, ignore2;
+{
+ return bash_complete_hostname_internal (rl_completion_mode (bash_complete_hostname));
+}
+
+static int
+bash_possible_hostname_completions (ignore, ignore2)
+ int ignore, ignore2;
+{
+ return bash_complete_hostname_internal ('?');
+}
+
+static int
+bash_complete_variable (ignore, ignore2)
+ int ignore, ignore2;
+{
+ return bash_complete_variable_internal (rl_completion_mode (bash_complete_variable));
+}
+
+static int
+bash_possible_variable_completions (ignore, ignore2)
+ int ignore, ignore2;
+{
+ return bash_complete_variable_internal ('?');
+}
+
+static int
+bash_complete_command (ignore, ignore2)
+ int ignore, ignore2;
+{
+ return bash_complete_command_internal (rl_completion_mode (bash_complete_command));
+}
+
+static int
+bash_possible_command_completions (ignore, ignore2)
+ int ignore, ignore2;
+{
+ return bash_complete_command_internal ('?');
+}
+
+static int
+bash_complete_hostname_internal (what_to_do)
+ int what_to_do;
+{
+ return bash_specific_completion (what_to_do, hostname_completion_function);
+}
+
+static int
+bash_complete_variable_internal (what_to_do)
+ int what_to_do;
+{
+ return bash_specific_completion (what_to_do, variable_completion_function);
+}
+
+static int
+bash_complete_command_internal (what_to_do)
+ int what_to_do;
+{
+ return bash_specific_completion (what_to_do, command_word_completion_function);
+}
+
+static char *globtext;
+static char *globorig;
+
+static char *
+glob_complete_word (text, state)
+ const char *text;
+ int state;
+{
+ static char **matches = (char **)NULL;
+ static int ind;
+ int glen;
+ char *ret, *ttext;
+
+ if (state == 0)
+ {
+ rl_filename_completion_desired = 1;
+ FREE (matches);
+ if (globorig != globtext)
+ FREE (globorig);
+ FREE (globtext);
+
+ ttext = bash_tilde_expand (text, 0);
+
+ if (rl_explicit_arg)
+ {
+ globorig = savestring (ttext);
+ glen = strlen (ttext);
+ globtext = (char *)xmalloc (glen + 2);
+ strcpy (globtext, ttext);
+ globtext[glen] = '*';
+ globtext[glen+1] = '\0';
+ }
+ else
+ globtext = globorig = savestring (ttext);
+
+ if (ttext != text)
+ free (ttext);
+
+ matches = shell_glob_filename (globtext);
+ if (GLOB_FAILED (matches))
+ matches = (char **)NULL;
+ ind = 0;
+ }
+
+ ret = matches ? matches[ind] : (char *)NULL;
+ ind++;
+ return ret;
+}
+
+static int
+bash_glob_completion_internal (what_to_do)
+ int what_to_do;
+{
+ return bash_specific_completion (what_to_do, glob_complete_word);
+}
+
+/* A special quoting function so we don't end up quoting globbing characters
+ in the word if there are no matches or multiple matches. */
+static char *
+bash_glob_quote_filename (s, rtype, qcp)
+ char *s;
+ int rtype;
+ char *qcp;
+{
+ if (globorig && qcp && *qcp == '\0' && STREQ (s, globorig))
+ return (savestring (s));
+ else
+ return (bash_quote_filename (s, rtype, qcp));
+}
+
+static int
+bash_glob_complete_word (count, key)
+ int count, key;
+{
+ int r;
+ rl_quote_func_t *orig_quoting_function;
+
+ if (rl_editing_mode == EMACS_EDITING_MODE)
+ rl_explicit_arg = 1; /* force `*' append */
+ orig_quoting_function = rl_filename_quoting_function;
+ rl_filename_quoting_function = bash_glob_quote_filename;
+
+ r = bash_glob_completion_internal (rl_completion_mode (bash_glob_complete_word));
+
+ rl_filename_quoting_function = orig_quoting_function;
+ return r;
+}
+
+static int
+bash_glob_expand_word (count, key)
+ int count, key;
+{
+ return bash_glob_completion_internal ('*');
+}
+
+static int
+bash_glob_list_expansions (count, key)
+ int count, key;
+{
+ return bash_glob_completion_internal ('?');
+}
+
+static int
+bash_specific_completion (what_to_do, generator)
+ int what_to_do;
+ rl_compentry_func_t *generator;
+{
+ rl_compentry_func_t *orig_func;
+ rl_completion_func_t *orig_attempt_func;
+ int r;
+
+ orig_func = rl_completion_entry_function;
+ orig_attempt_func = rl_attempted_completion_function;
+ rl_completion_entry_function = generator;
+ rl_attempted_completion_function = NULL;
+
+ r = rl_complete_internal (what_to_do);
+
+ rl_completion_entry_function = orig_func;
+ rl_attempted_completion_function = orig_attempt_func;
+
+ return r;
+}
+
+#endif /* SPECIFIC_COMPLETION_FUNCTIONS */
+
+#if defined (VI_MODE)
+/* Completion, from vi mode's point of view. This is a modified version of
+ rl_vi_complete which uses the bash globbing code to implement what POSIX
+ specifies, which is to append a `*' and attempt filename generation (which
+ has the side effect of expanding any globbing characters in the word). */
+static int
+bash_vi_complete (count, key)
+ int count, key;
+{
+#if defined (SPECIFIC_COMPLETION_FUNCTIONS)
+ int p, r;
+ char *t;
+
+ if ((rl_point < rl_end) && (!whitespace (rl_line_buffer[rl_point])))
+ {
+ if (!whitespace (rl_line_buffer[rl_point + 1]))
+ rl_vi_end_word (1, 'E');
+ rl_point++;
+ }
+
+ /* Find boundaries of current word, according to vi definition of a
+ `bigword'. */
+ t = 0;
+ if (rl_point > 0)
+ {
+ p = rl_point;
+ rl_vi_bWord (1, 'B');
+ r = rl_point;
+ rl_point = p;
+ p = r;
+
+ t = substring (rl_line_buffer, p, rl_point);
+ }
+
+ if (t && glob_pattern_p (t) == 0)
+ rl_explicit_arg = 1; /* XXX - force glob_complete_word to append `*' */
+ FREE (t);
+
+ if (key == '*') /* Expansion and replacement. */
+ r = bash_glob_expand_word (count, key);
+ else if (key == '=') /* List possible completions. */
+ r = bash_glob_list_expansions (count, key);
+ else if (key == '\\') /* Standard completion */
+ r = bash_glob_complete_word (count, key);
+ else
+ r = rl_complete (0, key);
+
+ if (key == '*' || key == '\\')
+ rl_vi_start_inserting (key, 1, 1);
+
+ return (r);
+#else
+ return rl_vi_complete (count, key);
+#endif /* !SPECIFIC_COMPLETION_FUNCTIONS */
+}
+#endif /* VI_MODE */
+
+/* Filename quoting for completion. */
+/* A function to strip unquoted quote characters (single quotes, double
+ quotes, and backslashes). It allows single quotes to appear
+ within double quotes, and vice versa. It should be smarter. */
+static char *
+bash_dequote_filename (text, quote_char)
+ char *text;
+ int quote_char;
+{
+ char *ret, *p, *r;
+ int l, quoted;
+
+ l = strlen (text);
+ ret = (char *)xmalloc (l + 1);
+ for (quoted = quote_char, p = text, r = ret; p && *p; p++)
+ {
+ /* Allow backslash-escaped characters to pass through unscathed. */
+ if (*p == '\\')
+ {
+ /* Backslashes are preserved within single quotes. */
+ if (quoted == '\'')
+ *r++ = *p;
+ /* Backslashes are preserved within double quotes unless the
+ character is one that is defined to be escaped */
+ else if (quoted == '"' && ((sh_syntaxtab[p[1]] & CBSDQUOTE) == 0))
+ *r++ = *p;
+
+ *r++ = *++p;
+ if (*p == '\0')
+ return ret; /* XXX - was break; */
+ continue;
+ }
+ /* Close quote. */
+ if (quoted && *p == quoted)
+ {
+ quoted = 0;
+ continue;
+ }
+ /* Open quote. */
+ if (quoted == 0 && (*p == '\'' || *p == '"'))
+ {
+ quoted = *p;
+ continue;
+ }
+ *r++ = *p;
+ }
+ *r = '\0';
+ return ret;
+}
+
+/* Quote characters that the readline completion code would treat as
+ word break characters with backslashes. Pass backslash-quoted
+ characters through without examination. */
+static char *
+quote_word_break_chars (text)
+ char *text;
+{
+ char *ret, *r, *s;
+ int l;
+
+ l = strlen (text);
+ ret = (char *)xmalloc ((2 * l) + 1);
+ for (s = text, r = ret; *s; s++)
+ {
+ /* Pass backslash-quoted characters through, including the backslash. */
+ if (*s == '\\')
+ {
+ *r++ = '\\';
+ *r++ = *++s;
+ if (*s == '\0')
+ break;
+ continue;
+ }
+ /* OK, we have an unquoted character. Check its presence in
+ rl_completer_word_break_characters. */
+ if (mbschr (rl_completer_word_break_characters, *s))
+ *r++ = '\\';
+ /* XXX -- check for standalone tildes here and backslash-quote them */
+ if (s == text && *s == '~' && file_exists (text))
+ *r++ = '\\';
+ *r++ = *s;
+ }
+ *r = '\0';
+ return ret;
+}
+
+/* Quote a filename using double quotes, single quotes, or backslashes
+ depending on the value of completion_quoting_style. If we're
+ completing using backslashes, we need to quote some additional
+ characters (those that readline treats as word breaks), so we call
+ quote_word_break_chars on the result. This returns newly-allocated
+ memory. */
+static char *
+bash_quote_filename (s, rtype, qcp)
+ char *s;
+ int rtype;
+ char *qcp;
+{
+ char *rtext, *mtext, *ret;
+ int rlen, cs;
+
+ rtext = (char *)NULL;
+
+ /* If RTYPE == MULT_MATCH, it means that there is
+ more than one match. In this case, we do not add
+ the closing quote or attempt to perform tilde
+ expansion. If RTYPE == SINGLE_MATCH, we try
+ to perform tilde expansion, because single and double
+ quotes inhibit tilde expansion by the shell. */
+
+ cs = completion_quoting_style;
+ /* Might need to modify the default completion style based on *qcp,
+ since it's set to any user-provided opening quote. We also change
+ to single-quoting if there is no user-provided opening quote and
+ the word being completed contains newlines, since those are not
+ quoted correctly using backslashes (a backslash-newline pair is
+ special to the shell parser). */
+ if (*qcp == '\0' && cs == COMPLETE_BSQUOTE && mbschr (s, '\n'))
+ cs = COMPLETE_SQUOTE;
+ else if (*qcp == '"')
+ cs = COMPLETE_DQUOTE;
+ else if (*qcp == '\'')
+ cs = COMPLETE_SQUOTE;
+#if defined (BANG_HISTORY)
+ else if (*qcp == '\0' && history_expansion && cs == COMPLETE_DQUOTE &&
+ history_expansion_inhibited == 0 && mbschr (s, '!'))
+ cs = COMPLETE_BSQUOTE;
+
+ if (*qcp == '"' && history_expansion && cs == COMPLETE_DQUOTE &&
+ history_expansion_inhibited == 0 && mbschr (s, '!'))
+ {
+ cs = COMPLETE_BSQUOTE;
+ *qcp = '\0';
+ }
+#endif
+
+ /* Don't tilde-expand backslash-quoted filenames, since only single and
+ double quotes inhibit tilde expansion. */
+ mtext = s;
+ if (mtext[0] == '~' && rtype == SINGLE_MATCH && cs != COMPLETE_BSQUOTE)
+ mtext = bash_tilde_expand (s, 0);
+
+ switch (cs)
+ {
+ case COMPLETE_DQUOTE:
+ rtext = sh_double_quote (mtext);
+ break;
+ case COMPLETE_SQUOTE:
+ rtext = sh_single_quote (mtext);
+ break;
+ case COMPLETE_BSQUOTE:
+ rtext = sh_backslash_quote (mtext);
+ break;
+ }
+
+ if (mtext != s)
+ free (mtext);
+
+ /* We may need to quote additional characters: those that readline treats
+ as word breaks that are not quoted by backslash_quote. */
+ if (rtext && cs == COMPLETE_BSQUOTE)
+ {
+ mtext = quote_word_break_chars (rtext);
+ free (rtext);
+ rtext = mtext;
+ }
+
+ /* Leave the opening quote intact. The readline completion code takes
+ care of avoiding doubled opening quotes. */
+ rlen = strlen (rtext);
+ ret = (char *)xmalloc (rlen + 1);
+ strcpy (ret, rtext);
+
+ /* If there are multiple matches, cut off the closing quote. */
+ if (rtype == MULT_MATCH && cs != COMPLETE_BSQUOTE)
+ ret[rlen - 1] = '\0';
+ free (rtext);
+ return ret;
+}
+
+/* Support for binding readline key sequences to Unix commands. */
+static Keymap cmd_xmap;
+
+static int
+putx(c)
+ int c;
+{
+ return (putc (c, rl_outstream));
+}
+
+static int
+bash_execute_unix_command (count, key)
+ int count; /* ignored */
+ int key;
+{
+ Keymap ckmap; /* current keymap */
+ Keymap xkmap; /* unix command executing keymap */
+ register int i, r;
+ intmax_t mi;
+ sh_parser_state_t ps;
+ char *cmd, *value, *l, *l1, *ce;
+ SHELL_VAR *v;
+ char ibuf[INT_STRLEN_BOUND(int) + 1];
+
+ /* First, we need to find the right command to execute. This is tricky,
+ because we might have already indirected into another keymap. */
+ ckmap = rl_get_keymap ();
+ if (ckmap != rl_executing_keymap)
+ {
+ /* bogus. we have to search. only handle one level of indirection. */
+ for (i = 0; i < KEYMAP_SIZE; i++)
+ {
+ if (ckmap[i].type == ISKMAP && (Keymap)ckmap[i].function == rl_executing_keymap)
+ break;
+ }
+ if (i < KEYMAP_SIZE)
+ xkmap = (Keymap)cmd_xmap[i].function;
+ else
+ {
+ rl_crlf ();
+ internal_error (_("bash_execute_unix_command: cannot find keymap for command"));
+ rl_forced_update_display ();
+ return 1;
+ }
+ }
+ else
+ xkmap = cmd_xmap;
+
+ cmd = (char *)xkmap[key].function;
+
+ if (cmd == 0)
+ {
+ rl_ding ();
+ return 1;
+ }
+
+ ce = rl_get_termcap ("ce");
+ if (ce) /* clear current line */
+ {
+ fprintf (rl_outstream, "\r");
+ tputs (ce, 1, putx);
+ fflush (rl_outstream);
+ }
+ else
+ rl_crlf (); /* move to a new line */
+
+ v = bind_variable ("READLINE_LINE", rl_line_buffer, 0);
+ if (v)
+ VSETATTR (v, att_exported);
+ l = v ? value_cell (v) : 0;
+ value = inttostr (rl_point, ibuf, sizeof (ibuf));
+ v = bind_int_variable ("READLINE_POINT", value);
+ if (v)
+ VSETATTR (v, att_exported);
+ array_needs_making = 1;
+
+ save_parser_state (&ps);
+ r = parse_and_execute (cmd, "bash_execute_unix_command", SEVAL_NOHIST|SEVAL_NOFREE);
+ restore_parser_state (&ps);
+
+ v = find_variable ("READLINE_LINE");
+ l1 = v ? value_cell (v) : 0;
+ if (l1 != l)
+ maybe_make_readline_line (value_cell (v));
+ v = find_variable ("READLINE_POINT");
+ if (v && legal_number (value_cell (v), &mi))
+ {
+ i = mi;
+ if (i != rl_point)
+ {
+ rl_point = i;
+ if (rl_point > rl_end)
+ rl_point = rl_end;
+ else if (rl_point < 0)
+ rl_point = 0;
+ }
+ }
+
+ unbind_variable ("READLINE_LINE");
+ unbind_variable ("READLINE_POINT");
+ array_needs_making = 1;
+
+ /* and restore the readline buffer and display after command execution. */
+ rl_forced_update_display ();
+ return 0;
+}
+
+static void
+init_unix_command_map ()
+{
+ cmd_xmap = rl_make_bare_keymap ();
+}
+
+static int
+isolate_sequence (string, ind, need_dquote, startp)
+ char *string;
+ int ind, need_dquote, *startp;
+{
+ register int i;
+ int c, passc, delim;
+
+ for (i = ind; string[i] && whitespace (string[i]); i++)
+ ;
+ /* NEED_DQUOTE means that the first non-white character *must* be `"'. */
+ if (need_dquote && string[i] != '"')
+ {
+ builtin_error (_("%s: first non-whitespace character is not `\"'"), string);
+ return -1;
+ }
+
+ /* We can have delimited strings even if NEED_DQUOTE == 0, like the command
+ string to bind the key sequence to. */
+ delim = (string[i] == '"' || string[i] == '\'') ? string[i] : 0;
+
+ if (startp)
+ *startp = delim ? ++i : i;
+
+ for (passc = 0; c = string[i]; i++)
+ {
+ if (passc)
+ {
+ passc = 0;
+ continue;
+ }
+ if (c == '\\')
+ {
+ passc++;
+ continue;
+ }
+ if (c == delim)
+ break;
+ }
+
+ if (delim && string[i] != delim)
+ {
+ builtin_error (_("no closing `%c' in %s"), delim, string);
+ return -1;
+ }
+
+ return i;
+}
+
+int
+bind_keyseq_to_unix_command (line)
+ char *line;
+{
+ Keymap kmap;
+ char *kseq, *value;
+ int i, kstart;
+
+ if (cmd_xmap == 0)
+ init_unix_command_map ();
+
+ kmap = rl_get_keymap ();
+
+ /* We duplicate some of the work done by rl_parse_and_bind here, but
+ this code only has to handle `"keyseq": ["]command["]' and can
+ generate an error for anything else. */
+ i = isolate_sequence (line, 0, 1, &kstart);
+ if (i < 0)
+ return -1;
+
+ /* Create the key sequence string to pass to rl_generic_bind */
+ kseq = substring (line, kstart, i);
+
+ for ( ; line[i] && line[i] != ':'; i++)
+ ;
+ if (line[i] != ':')
+ {
+ builtin_error (_("%s: missing colon separator"), line);
+ return -1;
+ }
+
+ i = isolate_sequence (line, i + 1, 0, &kstart);
+ if (i < 0)
+ return -1;
+
+ /* Create the value string containing the command to execute. */
+ value = substring (line, kstart, i);
+
+ /* Save the command to execute and the key sequence in the CMD_XMAP */
+ rl_generic_bind (ISMACR, kseq, value, cmd_xmap);
+
+ /* and bind the key sequence in the current keymap to a function that
+ understands how to execute from CMD_XMAP */
+ rl_bind_keyseq_in_map (kseq, bash_execute_unix_command, kmap);
+
+ return 0;
+}
+
+/* Used by the programmable completion code. Complete TEXT as a filename,
+ but return only directories as matches. Dequotes the filename before
+ attempting to find matches. */
+char **
+bash_directory_completion_matches (text)
+ const char *text;
+{
+ char **m1;
+ char *dfn;
+ int qc;
+
+ qc = rl_dispatching ? rl_completion_quote_character : 0;
+ dfn = bash_dequote_filename ((char *)text, qc);
+ m1 = rl_completion_matches (dfn, rl_filename_completion_function);
+ free (dfn);
+
+ if (m1 == 0 || m1[0] == 0)
+ return m1;
+ /* We don't bother recomputing the lcd of the matches, because it will just
+ get thrown away by the programmable completion code and recomputed
+ later. */
+ (void)bash_ignore_filenames (m1);
+ return m1;
+}
+
+char *
+bash_dequote_text (text)
+ const char *text;
+{
+ char *dtxt;
+ int qc;
+
+ qc = (text[0] == '"' || text[0] == '\'') ? text[0] : 0;
+ dtxt = bash_dequote_filename ((char *)text, qc);
+ return (dtxt);
+}
+#endif /* READLINE */
printf ("%s\n", var->name);
else if (function_p (var))
printf ("%s\n", named_function_string (var->name, function_cell (var), FUNC_MULTILINE|FUNC_EXTERNAL));
- else if (invisible_p (var))
+ else if (invisible_p (var) || var_isset (var) == 0)
printf ("%s\n", var->name);
else
{
- x = sh_double_quote (var_isset (var) ? value_cell (var) : "");
+ x = sh_double_quote (value_cell (var));
printf ("%s=%s\n", var->name, x);
free (x);
}
--- /dev/null
+This file is setattr.def, from which is created setattr.c.
+It implements the builtins "export" and "readonly", in Bash.
+
+Copyright (C) 1987-2009 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/>.
+
+$PRODUCES setattr.c
+
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include "../bashansi.h"
+#include "../bashintl.h"
+
+#include "../shell.h"
+#include "common.h"
+#include "bashgetopt.h"
+
+extern int posixly_correct;
+extern int array_needs_making;
+extern char *this_command_name;
+extern sh_builtin_func_t *this_shell_builtin;
+
+#ifdef ARRAY_VARS
+extern int declare_builtin __P((WORD_LIST *));
+#endif
+
+#define READONLY_OR_EXPORT \
+ (this_shell_builtin == readonly_builtin || this_shell_builtin == export_builtin)
+
+$BUILTIN export
+$FUNCTION export_builtin
+$SHORT_DOC export [-fn] [name[=value] ...] or export -p
+Set export attribute for shell variables.
+
+Marks each NAME for automatic export to the environment of subsequently
+executed commands. If VALUE is supplied, assign VALUE before exporting.
+
+Options:
+ -f refer to shell functions
+ -n remove the export property from each NAME
+ -p display a list of all exported variables and functions
+
+An argument of `--' disables further option processing.
+
+Exit Status:
+Returns success unless an invalid option is given or NAME is invalid.
+$END
+
+/* For each variable name in LIST, make that variable appear in the
+ environment passed to simple commands. If there is no LIST, then
+ print all such variables. An argument of `-n' says to remove the
+ exported attribute from variables named in LIST. An argument of
+ -f indicates that the names present in LIST refer to functions. */
+int
+export_builtin (list)
+ register WORD_LIST *list;
+{
+ return (set_or_show_attributes (list, att_exported, 0));
+}
+
+$BUILTIN readonly
+$FUNCTION readonly_builtin
+$SHORT_DOC readonly [-af] [name[=value] ...] or readonly -p
+Mark shell variables as unchangeable.
+
+Mark each NAME as read-only; the values of these NAMEs may not be
+changed by subsequent assignment. If VALUE is supplied, assign VALUE
+before marking as read-only.
+
+Options:
+ -a refer to indexed array variables
+ -A refer to associative array variables
+ -f refer to shell functions
+ -p display a list of all readonly variables and functions
+
+An argument of `--' disables further option processing.
+
+Exit Status:
+Returns success unless an invalid option is given or NAME is invalid.
+$END
+
+/* For each variable name in LIST, make that variable readonly. Given an
+ empty LIST, print out all existing readonly variables. */
+int
+readonly_builtin (list)
+ register WORD_LIST *list;
+{
+ return (set_or_show_attributes (list, att_readonly, 0));
+}
+
+#if defined (ARRAY_VARS)
+# define ATTROPTS "aAfnp"
+#else
+# define ATTROPTS "fnp"
+#endif
+
+/* For each variable name in LIST, make that variable have the specified
+ ATTRIBUTE. An arg of `-n' says to remove the attribute from the the
+ remaining names in LIST (doesn't work for readonly). */
+int
+set_or_show_attributes (list, attribute, nodefs)
+ register WORD_LIST *list;
+ int attribute, nodefs;
+{
+ register SHELL_VAR *var;
+ int assign, undo, any_failed, assign_error, opt;
+ int functions_only, arrays_only, assoc_only;
+ int aflags;
+ char *name;
+#if defined (ARRAY_VARS)
+ WORD_LIST *nlist, *tlist;
+ WORD_DESC *w;
+#endif
+
+ functions_only = arrays_only = assoc_only = 0;
+ undo = any_failed = assign_error = 0;
+ /* Read arguments from the front of the list. */
+ reset_internal_getopt ();
+ while ((opt = internal_getopt (list, ATTROPTS)) != -1)
+ {
+ switch (opt)
+ {
+ case 'n':
+ undo = 1;
+ break;
+ case 'f':
+ functions_only = 1;
+ break;
+#if defined (ARRAY_VARS)
+ case 'a':
+ arrays_only = 1;
+ break;
+ case 'A':
+ assoc_only = 1;
+ break;
+#endif
+ case 'p':
+ break;
+ default:
+ builtin_usage ();
+ return (EX_USAGE);
+ }
+ }
+ list = loptend;
+
+ if (list)
+ {
+ if (attribute & att_exported)
+ array_needs_making = 1;
+
+ /* Cannot undo readonly status, silently disallowed. */
+ if (undo && (attribute & att_readonly))
+ attribute &= ~att_readonly;
+
+ while (list)
+ {
+ name = list->word->word;
+
+ if (functions_only) /* xxx -f name */
+ {
+ var = find_function (name);
+ if (var == 0)
+ {
+ builtin_error (_("%s: not a function"), name);
+ any_failed++;
+ }
+ else
+ SETVARATTR (var, attribute, undo);
+
+ list = list->next;
+ continue;
+ }
+
+ /* xxx [-np] name[=value] */
+ assign = assignment (name, 0);
+
+ aflags = 0;
+ if (assign)
+ {
+ name[assign] = '\0';
+ if (name[assign - 1] == '+')
+ {
+ aflags |= ASS_APPEND;
+ name[assign - 1] = '\0';
+ }
+ }
+
+ if (legal_identifier (name) == 0)
+ {
+ sh_invalidid (name);
+ if (assign)
+ assign_error++;
+ else
+ any_failed++;
+ list = list->next;
+ continue;
+ }
+
+ if (assign) /* xxx [-np] name=value */
+ {
+ name[assign] = '=';
+ if (aflags & ASS_APPEND)
+ name[assign - 1] = '+';
+#if defined (ARRAY_VARS)
+ /* Let's try something here. Turn readonly -a xxx=yyy into
+ declare -ra xxx=yyy and see what that gets us. */
+ if (arrays_only || assoc_only)
+ {
+ tlist = list->next;
+ list->next = (WORD_LIST *)NULL;
+ w = arrays_only ? make_word ("-ra") : make_word ("-rA");
+ nlist = make_word_list (w, list);
+ opt = declare_builtin (nlist);
+ if (opt != EXECUTION_SUCCESS)
+ assign_error++;
+ list->next = tlist;
+ dispose_word (w);
+ free (nlist);
+ }
+ else
+#endif
+ /* This word has already been expanded once with command
+ and parameter expansion. Call do_assignment_no_expand (),
+ which does not do command or parameter substitution. If
+ the assignment is not performed correctly, flag an error. */
+ if (do_assignment_no_expand (name) == 0)
+ assign_error++;
+ name[assign] = '\0';
+ if (aflags & ASS_APPEND)
+ name[assign - 1] = '\0';
+ }
+
+ set_var_attribute (name, attribute, undo);
+ list = list->next;
+ }
+ }
+ else
+ {
+ SHELL_VAR **variable_list;
+ register int i;
+
+ if ((attribute & att_function) || functions_only)
+ {
+ variable_list = all_shell_functions ();
+ if (attribute != att_function)
+ attribute &= ~att_function; /* so declare -xf works, for example */
+ }
+ else
+ variable_list = all_shell_variables ();
+
+#if defined (ARRAY_VARS)
+ if (attribute & att_array)
+ {
+ arrays_only++;
+ if (attribute != att_array)
+ attribute &= ~att_array;
+ }
+ else if (attribute & att_assoc)
+ {
+ assoc_only++;
+ if (attribute != att_assoc)
+ attribute &= ~att_assoc;
+ }
+#endif
+
+ if (variable_list)
+ {
+ for (i = 0; var = variable_list[i]; i++)
+ {
+#if defined (ARRAY_VARS)
+ if (arrays_only && array_p (var) == 0)
+ continue;
+ else if (assoc_only && assoc_p (var) == 0)
+ continue;
+#endif
+ if ((var->attributes & attribute))
+ {
+ show_var_attributes (var, READONLY_OR_EXPORT, nodefs);
+ if (any_failed = sh_chkwrite (any_failed))
+ break;
+ }
+ }
+ free (variable_list);
+ }
+ }
+
+ return (assign_error ? EX_BADASSIGN
+ : ((any_failed == 0) ? EXECUTION_SUCCESS
+ : EXECUTION_FAILURE));
+}
+
+/* Show all variable variables (v == 1) or functions (v == 0) with
+ attributes. */
+int
+show_all_var_attributes (v, nodefs)
+ int v, nodefs;
+{
+ SHELL_VAR **variable_list, *var;
+ int any_failed;
+ register int i;
+
+ variable_list = v ? all_shell_variables () : all_shell_functions ();
+ if (variable_list == 0)
+ return (EXECUTION_SUCCESS);
+
+ for (i = any_failed = 0; var = variable_list[i]; i++)
+ {
+ show_var_attributes (var, READONLY_OR_EXPORT, nodefs);
+ if (any_failed = sh_chkwrite (any_failed))
+ break;
+ }
+ free (variable_list);
+ return (any_failed == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE);
+}
+
+/* Show the attributes for shell variable VAR. If NODEFS is non-zero,
+ don't show function definitions along with the name. If PATTR is
+ non-zero, it indicates we're being called from `export' or `readonly'.
+ In POSIX mode, this prints the name of the calling builtin (`export'
+ or `readonly') instead of `declare', and doesn't print function defs
+ when called by `export' or `readonly'. */
+int
+show_var_attributes (var, pattr, nodefs)
+ SHELL_VAR *var;
+ int pattr, nodefs;
+{
+ char flags[16], *x;
+ int i;
+
+ i = 0;
+
+ /* pattr == 0 means we are called from `declare'. */
+ if (pattr == 0 || posixly_correct == 0)
+ {
+#if defined (ARRAY_VARS)
+ if (array_p (var))
+ flags[i++] = 'a';
+
+ if (assoc_p (var))
+ flags[i++] = 'A';
+#endif
+
+ if (function_p (var))
+ flags[i++] = 'f';
+
+ if (integer_p (var))
+ flags[i++] = 'i';
+
+ if (readonly_p (var))
+ flags[i++] = 'r';
+
+ if (trace_p (var))
+ flags[i++] = 't';
+
+ if (exported_p (var))
+ flags[i++] = 'x';
+
+ if (capcase_p (var))
+ flags[i++] = 'c';
+
+ if (lowercase_p (var))
+ flags[i++] = 'l';
+
+ if (uppercase_p (var))
+ flags[i++] = 'u';
+ }
+ else
+ {
+#if defined (ARRAY_VARS)
+ if (array_p (var))
+ flags[i++] = 'a';
+
+ if (assoc_p (var))
+ flags[i++] = 'A';
+#endif
+
+ if (function_p (var))
+ flags[i++] = 'f';
+ }
+
+ flags[i] = '\0';
+
+ /* If we're printing functions with definitions, print the function def
+ first, then the attributes, instead of printing output that can't be
+ reused as input to recreate the current state. */
+ if (function_p (var) && nodefs == 0 && (pattr == 0 || posixly_correct == 0))
+ {
+ printf ("%s\n", named_function_string (var->name, function_cell (var), FUNC_MULTILINE|FUNC_EXTERNAL));
+ nodefs++;
+ if (pattr == 0 && i == 1 && flags[0] == 'f')
+ return 0; /* don't print `declare -f name' */
+ }
+
+ if (pattr == 0 || posixly_correct == 0)
+ printf ("declare -%s ", i ? flags : "-");
+ else if (i)
+ printf ("%s -%s ", this_command_name, flags);
+ else
+ printf ("%s ", this_command_name);
+
+#if defined (ARRAY_VARS)
+ if (array_p (var))
+ print_array_assignment (var, 1);
+ else if (assoc_p (var))
+ print_assoc_assignment (var, 1);
+ else
+#endif
+ /* force `readonly' and `export' to not print out function definitions
+ when in POSIX mode. */
+ if (nodefs || (function_p (var) && pattr != 0 && posixly_correct))
+ printf ("%s\n", var->name);
+ else if (function_p (var))
+ printf ("%s\n", named_function_string (var->name, function_cell (var), FUNC_MULTILINE|FUNC_EXTERNAL));
+ else if (invisible_p (var) || var_isnull (var))
+ printf ("%s\n", var->name);
+ else
+ {
+ x = sh_double_quote (value_cell (var));
+ printf ("%s=%s\n", var->name, x);
+ free (x);
+ }
+ return (0);
+}
+
+int
+show_name_attributes (name, nodefs)
+ char *name;
+ int nodefs;
+{
+ SHELL_VAR *var;
+
+ var = find_variable_internal (name, 1);
+
+ if (var && invisible_p (var) == 0)
+ {
+ show_var_attributes (var, READONLY_OR_EXPORT, nodefs);
+ return (0);
+ }
+ else
+ return (1);
+}
+
+void
+set_var_attribute (name, attribute, undo)
+ char *name;
+ int attribute, undo;
+{
+ SHELL_VAR *var, *tv;
+ char *tvalue;
+
+ if (undo)
+ var = find_variable (name);
+ else
+ {
+ tv = find_tempenv_variable (name);
+ /* XXX -- need to handle case where tv is a temp variable in a
+ function-scope context, since function_env has been merged into
+ the local variables table. */
+ if (tv && tempvar_p (tv))
+ {
+ tvalue = var_isset (tv) ? savestring (value_cell (tv)) : savestring ("");
+
+ var = bind_variable (tv->name, tvalue, 0);
+ var->attributes |= tv->attributes & ~att_tempvar;
+ VSETATTR (tv, att_propagate);
+ if (var->context != 0)
+ VSETATTR (var, att_propagate);
+ SETVARATTR (tv, attribute, undo); /* XXX */
+
+ stupidly_hack_special_variables (tv->name);
+
+ free (tvalue);
+ }
+ else
+ {
+ var = find_variable_internal (name, 0);
+ if (var == 0)
+ {
+ var = bind_variable (name, (char *)NULL, 0);
+ VSETATTR (var, att_invisible);
+ }
+ else if (var->context != 0)
+ VSETATTR (var, att_propagate);
+ }
+ }
+
+ if (var)
+ SETVARATTR (var, attribute, undo);
+
+ if (var && (exported_p (var) || (attribute & att_exported)))
+ array_needs_making++; /* XXX */
+}
# undef HAVE_RESOURCE
#endif
+#if !defined (HAVE_RESOURCE) && defined (HAVE_ULIMIT_H)
+# include <ulimit.h>
+#endif
+
#if !defined (RLIMTYPE)
# define RLIMTYPE long
# define string_to_rlimtype(s) strtol(s, (char **)NULL, 10)
--- /dev/null
+This file is ulimit.def, from which is created ulimit.c.
+It implements the builtin "ulimit" in Bash.
+
+Copyright (C) 1987-2009 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/>.
+
+$PRODUCES ulimit.c
+
+$BUILTIN ulimit
+$FUNCTION ulimit_builtin
+$DEPENDS_ON !_MINIX
+$SHORT_DOC ulimit [-SHacdefilmnpqrstuvx] [limit]
+Modify shell resource limits.
+
+Provides control over the resources available to the shell and processes
+it creates, on systems that allow such control.
+
+Options:
+ -S use the `soft' resource limit
+ -H use the `hard' resource limit
+ -a all current limits are reported
+ -b the socket buffer size
+ -c the maximum size of core files created
+ -d the maximum size of a process's data segment
+ -e the maximum scheduling priority (`nice')
+ -f the maximum size of files written by the shell and its children
+ -i the maximum number of pending signals
+ -l the maximum size a process may lock into memory
+ -m the maximum resident set size
+ -n the maximum number of open file descriptors
+ -p the pipe buffer size
+ -q the maximum number of bytes in POSIX message queues
+ -r the maximum real-time scheduling priority
+ -s the maximum stack size
+ -t the maximum amount of cpu time in seconds
+ -u the maximum number of user processes
+ -v the size of virtual memory
+ -x the maximum number of file locks
+
+If LIMIT is given, it is the new value of the specified resource; the
+special LIMIT values `soft', `hard', and `unlimited' stand for the
+current soft limit, the current hard limit, and no limit, respectively.
+Otherwise, the current value of the specified resource is printed. If
+no option is given, then -f is assumed.
+
+Values are in 1024-byte increments, except for -t, which is in seconds,
+-p, which is in increments of 512 bytes, and -u, which is an unscaled
+number of processes.
+
+Exit Status:
+Returns success unless an invalid option is supplied or an error occurs.
+$END
+
+#if !defined (_MINIX)
+
+#include <config.h>
+
+#include "../bashtypes.h"
+#ifndef _MINIX
+# include <sys/param.h>
+#endif
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+
+#include "../bashintl.h"
+
+#include "../shell.h"
+#include "common.h"
+#include "bashgetopt.h"
+#include "pipesize.h"
+
+#if !defined (errno)
+extern int errno;
+#endif
+
+/* For some reason, HPUX chose to make these definitions visible only if
+ _KERNEL is defined, so we define _KERNEL before including <sys/resource.h>
+ and #undef it afterward. */
+#if defined (HAVE_RESOURCE)
+# include <sys/time.h>
+# if defined (HPUX) && defined (RLIMIT_NEEDS_KERNEL)
+# define _KERNEL
+# endif
+# include <sys/resource.h>
+# if defined (HPUX) && defined (RLIMIT_NEEDS_KERNEL)
+# undef _KERNEL
+# endif
+#elif defined (HAVE_SYS_TIMES_H)
+# include <sys/times.h>
+#endif
+
+#if defined (HAVE_LIMITS_H)
+# include <limits.h>
+#endif
+
+/* Check for the most basic symbols. If they aren't present, this
+ system's <sys/resource.h> isn't very useful to us. */
+#if !defined (RLIMIT_FSIZE) || !defined (HAVE_GETRLIMIT)
+# undef HAVE_RESOURCE
+#endif
+
+#if !HAVE_RESOURCE && HAVE_ULIMIT_H
+# include <ulimit.h>
+#endif
+
+#if !defined (RLIMTYPE)
+# define RLIMTYPE long
+# define string_to_rlimtype(s) strtol(s, (char **)NULL, 10)
+# define print_rlimtype(num, nl) printf ("%ld%s", num, nl ? "\n" : "")
+#endif
+
+/* Some systems use RLIMIT_NOFILE, others use RLIMIT_OFILE */
+#if defined (HAVE_RESOURCE) && defined (RLIMIT_OFILE) && !defined (RLIMIT_NOFILE)
+# define RLIMIT_NOFILE RLIMIT_OFILE
+#endif /* HAVE_RESOURCE && RLIMIT_OFILE && !RLIMIT_NOFILE */
+
+/* Some systems have these, some do not. */
+#ifdef RLIMIT_FSIZE
+# define RLIMIT_FILESIZE RLIMIT_FSIZE
+#else
+# define RLIMIT_FILESIZE 256
+#endif
+
+#define RLIMIT_PIPESIZE 257
+
+#ifdef RLIMIT_NOFILE
+# define RLIMIT_OPENFILES RLIMIT_NOFILE
+#else
+# define RLIMIT_OPENFILES 258
+#endif
+
+#ifdef RLIMIT_VMEM
+# define RLIMIT_VIRTMEM RLIMIT_VMEM
+# define RLIMIT_VMBLKSZ 1024
+#else
+# ifdef RLIMIT_AS
+# define RLIMIT_VIRTMEM RLIMIT_AS
+# define RLIMIT_VMBLKSZ 1024
+# else
+# define RLIMIT_VIRTMEM 259
+# define RLIMIT_VMBLKSZ 1
+# endif
+#endif
+
+#ifdef RLIMIT_NPROC
+# define RLIMIT_MAXUPROC RLIMIT_NPROC
+#else
+# define RLIMIT_MAXUPROC 260
+#endif
+
+#if !defined (RLIM_INFINITY)
+# define RLIM_INFINITY 0x7fffffff
+#endif
+
+#if !defined (RLIM_SAVED_CUR)
+# define RLIM_SAVED_CUR RLIM_INFINITY
+#endif
+
+#if !defined (RLIM_SAVED_MAX)
+# define RLIM_SAVED_MAX RLIM_INFINITY
+#endif
+
+#define LIMIT_HARD 0x01
+#define LIMIT_SOFT 0x02
+
+/* "Blocks" are defined as 512 bytes when in Posix mode and 1024 bytes
+ otherwise. */
+#define POSIXBLK -2
+
+#define BLOCKSIZE(x) (((x) == POSIXBLK) ? (posixly_correct ? 512 : 1024) : (x))
+
+extern int posixly_correct;
+
+static int _findlim __P((int));
+
+static int ulimit_internal __P((int, char *, int, int));
+
+static int get_limit __P((int, RLIMTYPE *, RLIMTYPE *));
+static int set_limit __P((int, RLIMTYPE, int));
+
+static void printone __P((int, RLIMTYPE, int));
+static void print_all_limits __P((int));
+
+static int set_all_limits __P((int, RLIMTYPE));
+
+static int filesize __P((RLIMTYPE *));
+static int pipesize __P((RLIMTYPE *));
+static int getmaxuprc __P((RLIMTYPE *));
+static int getmaxvm __P((RLIMTYPE *, RLIMTYPE *));
+
+typedef struct {
+ int option; /* The ulimit option for this limit. */
+ int parameter; /* Parameter to pass to get_limit (). */
+ int block_factor; /* Blocking factor for specific limit. */
+ const char * const description; /* Descriptive string to output. */
+ const char * const units; /* scale */
+} RESOURCE_LIMITS;
+
+static RESOURCE_LIMITS limits[] = {
+#ifdef RLIMIT_PTHREAD
+ { 'T', RLIMIT_PTHREAD, 1, "number of threads", (char *)NULL },
+#endif
+#ifdef RLIMIT_SBSIZE
+ { 'b', RLIMIT_SBSIZE, 1, "socket buffer size", "bytes" },
+#endif
+#ifdef RLIMIT_CORE
+ { 'c', RLIMIT_CORE, POSIXBLK, "core file size", "blocks" },
+#endif
+#ifdef RLIMIT_DATA
+ { 'd', RLIMIT_DATA, 1024, "data seg size", "kbytes" },
+#endif
+#ifdef RLIMIT_NICE
+ { 'e', RLIMIT_NICE, 1, "scheduling priority", (char *)NULL },
+#endif
+ { 'f', RLIMIT_FILESIZE, POSIXBLK, "file size", "blocks" },
+#ifdef RLIMIT_SIGPENDING
+ { 'i', RLIMIT_SIGPENDING, 1, "pending signals", (char *)NULL },
+#endif
+#ifdef RLIMIT_MEMLOCK
+ { 'l', RLIMIT_MEMLOCK, 1024, "max locked memory", "kbytes" },
+#endif
+#ifdef RLIMIT_RSS
+ { 'm', RLIMIT_RSS, 1024, "max memory size", "kbytes" },
+#endif /* RLIMIT_RSS */
+ { 'n', RLIMIT_OPENFILES, 1, "open files", (char *)NULL},
+ { 'p', RLIMIT_PIPESIZE, 512, "pipe size", "512 bytes" },
+#ifdef RLIMIT_MSGQUEUE
+ { 'q', RLIMIT_MSGQUEUE, 1, "POSIX message queues", "bytes" },
+#endif
+#ifdef RLIMIT_RTPRIO
+ { 'r', RLIMIT_RTPRIO, 1, "real-time priority", (char *)NULL },
+#endif
+#ifdef RLIMIT_STACK
+ { 's', RLIMIT_STACK, 1024, "stack size", "kbytes" },
+#endif
+#ifdef RLIMIT_CPU
+ { 't', RLIMIT_CPU, 1, "cpu time", "seconds" },
+#endif /* RLIMIT_CPU */
+ { 'u', RLIMIT_MAXUPROC, 1, "max user processes", (char *)NULL },
+#if defined (HAVE_RESOURCE)
+ { 'v', RLIMIT_VIRTMEM, RLIMIT_VMBLKSZ, "virtual memory", "kbytes" },
+#endif
+#ifdef RLIMIT_SWAP
+ { 'w', RLIMIT_SWAP, 1024, "swap size", "kbytes" },
+#endif
+#ifdef RLIMIT_LOCKS
+ { 'x', RLIMIT_LOCKS, 1, "file locks", (char *)NULL },
+#endif
+ { -1, -1, -1, (char *)NULL, (char *)NULL }
+};
+#define NCMDS (sizeof(limits) / sizeof(limits[0]))
+
+typedef struct _cmd {
+ int cmd;
+ char *arg;
+} ULCMD;
+
+static ULCMD *cmdlist;
+static int ncmd;
+static int cmdlistsz;
+
+#if !defined (HAVE_RESOURCE) && !defined (HAVE_ULIMIT)
+long
+ulimit (cmd, newlim)
+ int cmd;
+ long newlim;
+{
+ errno = EINVAL;
+ return -1;
+}
+#endif /* !HAVE_RESOURCE && !HAVE_ULIMIT */
+
+static int
+_findlim (opt)
+ int opt;
+{
+ register int i;
+
+ for (i = 0; limits[i].option > 0; i++)
+ if (limits[i].option == opt)
+ return i;
+ return -1;
+}
+
+static char optstring[4 + 2 * NCMDS];
+
+/* Report or set limits associated with certain per-process resources.
+ See the help documentation in builtins.c for a full description. */
+int
+ulimit_builtin (list)
+ register WORD_LIST *list;
+{
+ register char *s;
+ int c, limind, mode, opt, all_limits;
+
+ mode = 0;
+
+ all_limits = 0;
+
+ /* Idea stolen from pdksh -- build option string the first time called. */
+ if (optstring[0] == 0)
+ {
+ s = optstring;
+ *s++ = 'a'; *s++ = 'S'; *s++ = 'H';
+ for (c = 0; limits[c].option > 0; c++)
+ {
+ *s++ = limits[c].option;
+ *s++ = ';';
+ }
+ *s = '\0';
+ }
+
+ /* Initialize the command list. */
+ if (cmdlistsz == 0)
+ cmdlist = (ULCMD *)xmalloc ((cmdlistsz = 16) * sizeof (ULCMD));
+ ncmd = 0;
+
+ reset_internal_getopt ();
+ while ((opt = internal_getopt (list, optstring)) != -1)
+ {
+ switch (opt)
+ {
+ case 'a':
+ all_limits++;
+ break;
+
+ /* -S and -H are modifiers, not real options. */
+ case 'S':
+ mode |= LIMIT_SOFT;
+ break;
+
+ case 'H':
+ mode |= LIMIT_HARD;
+ break;
+
+ case '?':
+ builtin_usage ();
+ return (EX_USAGE);
+
+ default:
+ if (ncmd >= cmdlistsz)
+ cmdlist = (ULCMD *)xrealloc (cmdlist, (cmdlistsz *= 2) * sizeof (ULCMD));
+ cmdlist[ncmd].cmd = opt;
+ cmdlist[ncmd++].arg = list_optarg;
+ break;
+ }
+ }
+ list = loptend;
+
+ if (all_limits)
+ {
+#ifdef NOTYET
+ if (list) /* setting */
+ {
+ if (STREQ (list->word->word, "unlimited") == 0)
+ {
+ builtin_error (_("%s: invalid limit argument"), list->word->word);
+ return (EXECUTION_FAILURE);
+ }
+ return (set_all_limits (mode == 0 ? LIMIT_SOFT|LIMIT_HARD : mode, RLIM_INFINITY));
+ }
+#endif
+ print_all_limits (mode == 0 ? LIMIT_SOFT : mode);
+ return (sh_chkwrite (EXECUTION_SUCCESS));
+ }
+
+ /* default is `ulimit -f' */
+ if (ncmd == 0)
+ {
+ cmdlist[ncmd].cmd = 'f';
+ /* `ulimit something' is same as `ulimit -f something' */
+ cmdlist[ncmd++].arg = list ? list->word->word : (char *)NULL;
+ if (list)
+ list = list->next;
+ }
+
+ /* verify each command in the list. */
+ for (c = 0; c < ncmd; c++)
+ {
+ limind = _findlim (cmdlist[c].cmd);
+ if (limind == -1)
+ {
+ builtin_error (_("`%c': bad command"), cmdlist[c].cmd);
+ return (EX_USAGE);
+ }
+ }
+
+ for (c = 0; c < ncmd; c++)
+ if (ulimit_internal (cmdlist[c].cmd, cmdlist[c].arg, mode, ncmd > 1) == EXECUTION_FAILURE)
+ return (EXECUTION_FAILURE);
+
+ return (EXECUTION_SUCCESS);
+}
+
+static int
+ulimit_internal (cmd, cmdarg, mode, multiple)
+ int cmd;
+ char *cmdarg;
+ int mode, multiple;
+{
+ int opt, limind, setting;
+ int block_factor;
+ RLIMTYPE soft_limit, hard_limit, real_limit, limit;
+
+ setting = cmdarg != 0;
+ limind = _findlim (cmd);
+ if (mode == 0)
+ mode = setting ? (LIMIT_HARD|LIMIT_SOFT) : LIMIT_SOFT;
+ opt = get_limit (limind, &soft_limit, &hard_limit);
+ if (opt < 0)
+ {
+ builtin_error (_("%s: cannot get limit: %s"), limits[limind].description,
+ strerror (errno));
+ return (EXECUTION_FAILURE);
+ }
+
+ if (setting == 0) /* print the value of the specified limit */
+ {
+ printone (limind, (mode & LIMIT_SOFT) ? soft_limit : hard_limit, multiple);
+ return (EXECUTION_SUCCESS);
+ }
+
+ /* Setting the limit. */
+ if (STREQ (cmdarg, "hard"))
+ real_limit = hard_limit;
+ else if (STREQ (cmdarg, "soft"))
+ real_limit = soft_limit;
+ else if (STREQ (cmdarg, "unlimited"))
+ real_limit = RLIM_INFINITY;
+ else if (all_digits (cmdarg))
+ {
+ limit = string_to_rlimtype (cmdarg);
+ block_factor = BLOCKSIZE(limits[limind].block_factor);
+ real_limit = limit * block_factor;
+
+ if ((real_limit / block_factor) != limit)
+ {
+ sh_erange (cmdarg, _("limit"));
+ return (EXECUTION_FAILURE);
+ }
+ }
+ else
+ {
+ sh_invalidnum (cmdarg);
+ return (EXECUTION_FAILURE);
+ }
+
+ if (set_limit (limind, real_limit, mode) < 0)
+ {
+ builtin_error (_("%s: cannot modify limit: %s"), limits[limind].description,
+ strerror (errno));
+ return (EXECUTION_FAILURE);
+ }
+
+ return (EXECUTION_SUCCESS);
+}
+
+static int
+get_limit (ind, softlim, hardlim)
+ int ind;
+ RLIMTYPE *softlim, *hardlim;
+{
+ RLIMTYPE value;
+#if defined (HAVE_RESOURCE)
+ struct rlimit limit;
+#endif
+
+ if (limits[ind].parameter >= 256)
+ {
+ switch (limits[ind].parameter)
+ {
+ case RLIMIT_FILESIZE:
+ if (filesize (&value) < 0)
+ return -1;
+ break;
+ case RLIMIT_PIPESIZE:
+ if (pipesize (&value) < 0)
+ return -1;
+ break;
+ case RLIMIT_OPENFILES:
+ value = (RLIMTYPE)getdtablesize ();
+ break;
+ case RLIMIT_VIRTMEM:
+ return (getmaxvm (softlim, hardlim));
+ case RLIMIT_MAXUPROC:
+ if (getmaxuprc (&value) < 0)
+ return -1;
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+ *softlim = *hardlim = value;
+ return (0);
+ }
+ else
+ {
+#if defined (HAVE_RESOURCE)
+ if (getrlimit (limits[ind].parameter, &limit) < 0)
+ return -1;
+ *softlim = limit.rlim_cur;
+ *hardlim = limit.rlim_max;
+# if defined (HPUX9)
+ if (limits[ind].parameter == RLIMIT_FILESIZE)
+ {
+ *softlim *= 512;
+ *hardlim *= 512; /* Ugh. */
+ }
+ else
+# endif /* HPUX9 */
+ return 0;
+#else
+ errno = EINVAL;
+ return -1;
+#endif
+ }
+}
+
+static int
+set_limit (ind, newlim, mode)
+ int ind;
+ RLIMTYPE newlim;
+ int mode;
+{
+#if defined (HAVE_RESOURCE)
+ struct rlimit limit;
+ RLIMTYPE val;
+#endif
+
+ if (limits[ind].parameter >= 256)
+ switch (limits[ind].parameter)
+ {
+ case RLIMIT_FILESIZE:
+#if !defined (HAVE_RESOURCE)
+ return (ulimit (2, newlim / 512L));
+#else
+ errno = EINVAL;
+ return -1;
+#endif
+
+ case RLIMIT_OPENFILES:
+#if defined (HAVE_SETDTABLESIZE)
+# if defined (__CYGWIN__)
+ /* Grrr... Cygwin declares setdtablesize as void. */
+ setdtablesize (newlim);
+ return 0;
+# else
+ return (setdtablesize (newlim));
+# endif
+#endif
+ case RLIMIT_PIPESIZE:
+ case RLIMIT_VIRTMEM:
+ case RLIMIT_MAXUPROC:
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+ else
+ {
+#if defined (HAVE_RESOURCE)
+ if (getrlimit (limits[ind].parameter, &limit) < 0)
+ return -1;
+# if defined (HPUX9)
+ if (limits[ind].parameter == RLIMIT_FILESIZE)
+ newlim /= 512; /* Ugh. */
+# endif /* HPUX9 */
+ val = (current_user.euid != 0 && newlim == RLIM_INFINITY &&
+ (mode & LIMIT_HARD) == 0 && /* XXX -- test */
+ (limit.rlim_cur <= limit.rlim_max))
+ ? limit.rlim_max : newlim;
+ if (mode & LIMIT_SOFT)
+ limit.rlim_cur = val;
+ if (mode & LIMIT_HARD)
+ limit.rlim_max = val;
+
+ return (setrlimit (limits[ind].parameter, &limit));
+#else
+ errno = EINVAL;
+ return -1;
+#endif
+ }
+}
+
+static int
+getmaxvm (softlim, hardlim)
+ RLIMTYPE *softlim, *hardlim;
+{
+#if defined (HAVE_RESOURCE)
+ struct rlimit datalim, stacklim;
+
+ if (getrlimit (RLIMIT_DATA, &datalim) < 0)
+ return -1;
+
+ if (getrlimit (RLIMIT_STACK, &stacklim) < 0)
+ return -1;
+
+ /* Protect against overflow. */
+ *softlim = (datalim.rlim_cur / 1024L) + (stacklim.rlim_cur / 1024L);
+ *hardlim = (datalim.rlim_max / 1024L) + (stacklim.rlim_max / 1024L);
+ return 0;
+#else
+ errno = EINVAL;
+ return -1;
+#endif /* HAVE_RESOURCE */
+}
+
+static int
+filesize(valuep)
+ RLIMTYPE *valuep;
+{
+#if !defined (HAVE_RESOURCE)
+ long result;
+ if ((result = ulimit (1, 0L)) < 0)
+ return -1;
+ else
+ *valuep = (RLIMTYPE) result * 512;
+ return 0;
+#else
+ errno = EINVAL;
+ return -1;
+#endif
+}
+
+static int
+pipesize (valuep)
+ RLIMTYPE *valuep;
+{
+#if defined (PIPE_BUF)
+ /* This is defined on Posix systems. */
+ *valuep = (RLIMTYPE) PIPE_BUF;
+ return 0;
+#else
+# if defined (_POSIX_PIPE_BUF)
+ *valuep = (RLIMTYPE) _POSIX_PIPE_BUF;
+ return 0;
+# else
+# if defined (PIPESIZE)
+ /* This is defined by running a program from the Makefile. */
+ *valuep = (RLIMTYPE) PIPESIZE;
+ return 0;
+# else
+ errno = EINVAL;
+ return -1;
+# endif /* PIPESIZE */
+# endif /* _POSIX_PIPE_BUF */
+#endif /* PIPE_BUF */
+}
+
+static int
+getmaxuprc (valuep)
+ RLIMTYPE *valuep;
+{
+ long maxchild;
+
+ maxchild = getmaxchild ();
+ if (maxchild < 0)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ else
+ {
+ *valuep = (RLIMTYPE) maxchild;
+ return 0;
+ }
+}
+
+static void
+print_all_limits (mode)
+ int mode;
+{
+ register int i;
+ RLIMTYPE softlim, hardlim;
+
+ if (mode == 0)
+ mode |= LIMIT_SOFT;
+
+ for (i = 0; limits[i].option > 0; i++)
+ {
+ if (get_limit (i, &softlim, &hardlim) == 0)
+ printone (i, (mode & LIMIT_SOFT) ? softlim : hardlim, 1);
+ else if (errno != EINVAL)
+ builtin_error ("%s: cannot get limit: %s", limits[i].description,
+ strerror (errno));
+ }
+}
+
+static void
+printone (limind, curlim, pdesc)
+ int limind;
+ RLIMTYPE curlim;
+ int pdesc;
+{
+ char unitstr[64];
+ int factor;
+
+ factor = BLOCKSIZE(limits[limind].block_factor);
+ if (pdesc)
+ {
+ if (limits[limind].units)
+ sprintf (unitstr, "(%s, -%c) ", limits[limind].units, limits[limind].option);
+ else
+ sprintf (unitstr, "(-%c) ", limits[limind].option);
+
+ printf ("%-20s %16s", limits[limind].description, unitstr);
+ }
+ if (curlim == RLIM_INFINITY)
+ puts ("unlimited");
+ else if (curlim == RLIM_SAVED_MAX)
+ puts ("hard");
+ else if (curlim == RLIM_SAVED_CUR)
+ puts ("soft");
+ else
+ print_rlimtype ((curlim / factor), 1);
+}
+
+/* Set all limits to NEWLIM. NEWLIM currently must be RLIM_INFINITY, which
+ causes all limits to be set as high as possible depending on mode (like
+ csh `unlimit'). Returns -1 if NEWLIM is invalid, 0 if all limits
+ were set successfully, and 1 if at least one limit could not be set.
+
+ To raise all soft limits to their corresponding hard limits, use
+ ulimit -S -a unlimited
+ To attempt to raise all hard limits to infinity (superuser-only), use
+ ulimit -H -a unlimited
+ To attempt to raise all soft and hard limits to infinity, use
+ ulimit -a unlimited
+*/
+
+static int
+set_all_limits (mode, newlim)
+ int mode;
+ RLIMTYPE newlim;
+{
+ register int i;
+ int retval = 0;
+
+ if (newlim != RLIM_INFINITY)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (mode == 0)
+ mode = LIMIT_SOFT|LIMIT_HARD;
+
+ for (retval = i = 0; limits[i].option > 0; i++)
+ if (set_limit (i, newlim, mode) < 0)
+ {
+ builtin_error (_("%s: cannot modify limit: %s"), limits[i].description,
+ strerror (errno));
+ retval = 1;
+ }
+ return retval;
+}
+
+#endif /* !_MINIX */
/* Define if you have the <termios.h> header file. */
#undef HAVE_TERMIOS_H
+/* Define if you have the <ulimit.h> header file. */
+#undef HAVE_ULIMIT_H
+
/* Define if you have the <unistd.h> header file. */
#undef HAVE_UNISTD_H
--- /dev/null
+/* config.h -- Configuration file for bash. */
+
+/* Copyright (C) 1987-2009 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/>.
+*/
+
+#ifndef _CONFIG_H_
+#define _CONFIG_H_
+
+/* Configuration feature settings controllable by autoconf. */
+
+/* Define JOB_CONTROL if your operating system supports
+ BSD-like job control. */
+#undef JOB_CONTROL
+
+/* Define ALIAS if you want the alias features. */
+#undef ALIAS
+
+/* Define PUSHD_AND_POPD if you want those commands to be compiled in.
+ (Also the `dirs' commands.) */
+#undef PUSHD_AND_POPD
+
+/* Define BRACE_EXPANSION if you want curly brace expansion a la Csh:
+ foo{a,b} -> fooa foob. Even if this is compiled in (the default) you
+ can turn it off at shell startup with `-nobraceexpansion', or during
+ shell execution with `set +o braceexpand'. */
+#undef BRACE_EXPANSION
+
+/* Define READLINE to get the nifty/glitzy editing features.
+ This is on by default. You can turn it off interactively
+ with the -nolineediting flag. */
+#undef READLINE
+
+/* Define BANG_HISTORY if you want to have Csh style "!" history expansion.
+ This is unrelated to READLINE. */
+#undef BANG_HISTORY
+
+/* Define HISTORY if you want to have access to previously typed commands.
+
+ If both HISTORY and READLINE are defined, you can get at the commands
+ with line editing commands, and you can directly manipulate the history
+ from the command line.
+
+ If only HISTORY is defined, the `fc' and `history' builtins are
+ available. */
+#undef HISTORY
+
+/* Define this if you want completion that puts all alternatives into
+ a brace expansion shell expression. */
+#if defined (BRACE_EXPANSION) && defined (READLINE)
+# define BRACE_COMPLETION
+#endif /* BRACE_EXPANSION */
+
+/* Define DEFAULT_ECHO_TO_XPG if you want the echo builtin to interpret
+ the backslash-escape characters by default, like the XPG Single Unix
+ Specification V2 for echo.
+ This requires that V9_ECHO be defined. */
+#undef DEFAULT_ECHO_TO_XPG
+
+/* Define HELP_BUILTIN if you want the `help' shell builtin and the long
+ documentation strings compiled into the shell. */
+#undef HELP_BUILTIN
+
+/* Define RESTRICTED_SHELL if you want the generated shell to have the
+ ability to be a restricted one. The shell thus generated can become
+ restricted by being run with the name "rbash", or by setting the -r
+ flag. */
+#undef RESTRICTED_SHELL
+
+/* Define DISABLED_BUILTINS if you want "builtin foo" to always run the
+ shell builtin "foo", even if it has been disabled with "enable -n foo". */
+#undef DISABLED_BUILTINS
+
+/* Define PROCESS_SUBSTITUTION if you want the K*rn shell-like process
+ substitution features "<(file)". */
+/* Right now, you cannot do this on machines without fully operational
+ FIFO support. This currently include NeXT and Alliant. */
+#undef PROCESS_SUBSTITUTION
+
+/* Define PROMPT_STRING_DECODE if you want the backslash-escaped special
+ characters in PS1 and PS2 expanded. Variable expansion will still be
+ performed. */
+#undef PROMPT_STRING_DECODE
+
+/* Define SELECT_COMMAND if you want the Korn-shell style `select' command:
+ select word in word_list; do command_list; done */
+#undef SELECT_COMMAND
+
+/* Define COMMAND_TIMING of you want the ksh-style `time' reserved word and
+ the ability to time pipelines, functions, and builtins. */
+#undef COMMAND_TIMING
+
+/* Define ARRAY_VARS if you want ksh-style one-dimensional array variables. */
+#undef ARRAY_VARS
+
+/* Define DPAREN_ARITHMETIC if you want the ksh-style ((...)) arithmetic
+ evaluation command. */
+#undef DPAREN_ARITHMETIC
+
+/* Define EXTENDED_GLOB if you want the ksh-style [*+@?!](patlist) extended
+ pattern matching. */
+#undef EXTENDED_GLOB
+
+/* Define EXTGLOB_DEFAULT to the value you'd like the extglob shell option
+ to have by default */
+#undef EXTGLOB_DEFAULT
+
+/* Define COND_COMMAND if you want the ksh-style [[...]] conditional
+ command. */
+#undef COND_COMMAND
+
+/* Define COND_REGEXP if you want extended regular expression matching and the
+ =~ binary operator in the [[...]] conditional command. */
+#define COND_REGEXP
+
+/* Define COPROCESS_SUPPORT if you want support for ksh-like coprocesses and
+ the `coproc' reserved word */
+#define COPROCESS_SUPPORT
+
+/* Define ARITH_FOR_COMMAND if you want the ksh93-style
+ for (( init; test; step )) do list; done
+ arithmetic for command. */
+#undef ARITH_FOR_COMMAND
+
+/* Define NETWORK_REDIRECTIONS if you want /dev/(tcp|udp)/host/port to open
+ socket connections when used in redirections */
+#undef NETWORK_REDIRECTIONS
+
+/* Define PROGRAMMABLE_COMPLETION for the programmable completion features
+ and the complete builtin. */
+#undef PROGRAMMABLE_COMPLETION
+
+/* Define NO_MULTIBYTE_SUPPORT to not compile in support for multibyte
+ characters, even if the OS supports them. */
+#undef NO_MULTIBYTE_SUPPORT
+
+/* Define DEBUGGER if you want to compile in some features used only by the
+ bash debugger. */
+#undef DEBUGGER
+
+/* Define STRICT_POSIX if you want bash to be strictly posix.2 conformant by
+ default (except for echo; that is controlled separately). */
+#undef STRICT_POSIX
+
+/* Define MEMSCRAMBLE if you want the bash malloc and free to scramble
+ memory contents on malloc() and free(). */
+#undef MEMSCRAMBLE
+
+/* Define AFS if you are using Transarc's AFS. */
+#undef AFS
+
+/* Define for case-modifying variable attributes; variables modified on
+ assignment */
+#undef CASEMOD_ATTRS
+
+/* Define for case-modifying word expansions */
+#undef CASEMOD_EXPANSIONS
+
+#undef ENABLE_NLS
+
+/* End of configuration settings controllable by autoconf. */
+/* Other settable options appear in config-top.h. */
+
+#include "config-top.h"
+
+/* Beginning of autoconf additions. */
+
+/* Characteristics of the C compiler */
+#undef const
+
+#undef inline
+
+#undef restrict
+
+#undef volatile
+
+/* Define if cpp supports the ANSI-C stringizing `#' operator */
+#undef HAVE_STRINGIZE
+
+/* Define if the compiler supports `long double' variables. */
+#undef HAVE_LONG_DOUBLE
+
+#undef PROTOTYPES
+
+#undef __CHAR_UNSIGNED__
+
+/* Define if the compiler supports `long long' variables. */
+#undef HAVE_LONG_LONG
+
+#undef HAVE_UNSIGNED_LONG_LONG
+
+/* The number of bytes in a int. */
+#undef SIZEOF_INT
+
+/* The number of bytes in a long. */
+#undef SIZEOF_LONG
+
+/* The number of bytes in a pointer to char. */
+#undef SIZEOF_CHAR_P
+
+/* The number of bytes in a double (hopefully 8). */
+#undef SIZEOF_DOUBLE
+
+/* The number of bytes in a `long long', if we have one. */
+#undef SIZEOF_LONG_LONG
+
+/* System paths */
+
+#define DEFAULT_MAIL_DIRECTORY "/usr/spool/mail"
+
+/* Characteristics of the system's header files and libraries that affect
+ the compilation environment. */
+
+/* Define if the system does not provide POSIX.1 features except
+ with this defined. */
+#undef _POSIX_1_SOURCE
+
+/* Define if you need to in order for stat and other things to work. */
+#undef _POSIX_SOURCE
+
+/* Define to use GNU libc extensions */
+#undef _GNU_SOURCE
+
+/* Define if you have the ANSI C header files. */
+#undef STDC_HEADERS
+
+/* Memory management functions. */
+
+/* Define if using the bash version of malloc in lib/malloc/malloc.c */
+#undef USING_BASH_MALLOC
+
+#undef DISABLE_MALLOC_WRAPPERS
+
+/* Define if using alloca.c. */
+#undef C_ALLOCA
+
+/* Define to one of _getb67, GETB67, getb67 for Cray-2 and Cray-YMP systems.
+ This function is required for alloca.c support on those systems. */
+#undef CRAY_STACKSEG_END
+
+/* Define if you have alloca, as a function or macro. */
+#undef HAVE_ALLOCA
+
+/* Define if you have <alloca.h> and it should be used (not on Ultrix). */
+#undef HAVE_ALLOCA_H
+
+
+/* SYSTEM TYPES */
+
+/* Define to `long' if <sys/types.h> doesn't define. */
+#undef off_t
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+#undef mode_t
+
+/* Define to `int' if <signal.h> doesn't define. */
+#undef sigset_t
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+#undef pid_t
+
+/* Define to `short' if <sys/types.h> doesn't define. */
+#undef bits16_t
+
+/* Define to `unsigned short' if <sys/types.h> doesn't define. */
+#undef u_bits16_t
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+#undef bits32_t
+
+/* Define to `unsigned int' if <sys/types.h> doesn't define. */
+#undef u_bits32_t
+
+/* Define to `double' if <sys/types.h> doesn't define. */
+#undef bits64_t
+
+/* Define to `unsigned int' if <sys/types.h> doesn't define. */
+#undef u_int
+
+/* Define to `unsigned long' if <sys/types.h> doesn't define. */
+#undef u_long
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+#undef ptrdiff_t
+
+/* Define to `unsigned' if <sys/types.h> doesn't define. */
+#undef size_t
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+#undef ssize_t
+
+/* Define to `long' if <stdint.h> doesn't define. */
+#undef intmax_t
+
+/* Define to `unsigned long' if <stdint.h> doesn't define. */
+#undef uintmax_t
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+#undef uid_t
+
+/* Define to `long' if <sys/types.h> doesn't define. */
+#undef clock_t
+
+/* Define to `long' if <sys/types.h> doesn't define. */
+#undef time_t
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+#undef gid_t
+
+/* Define to `unsigned int' if <sys/socket.h> doesn't define. */
+#undef socklen_t
+
+/* Define to `int' if <signal.h> doesn't define. */
+#undef sig_atomic_t
+
+#undef HAVE_MBSTATE_T
+
+/* Define if you have quad_t in <sys/types.h>. */
+#undef HAVE_QUAD_T
+
+/* Define if you have wchar_t in <wctype.h>. */
+#undef HAVE_WCHAR_T
+
+/* Define if you have wctype_t in <wctype.h>. */
+#undef HAVE_WCTYPE_T
+
+/* Define if you have wint_t in <wctype.h>. */
+#undef HAVE_WINT_T
+
+#undef RLIMTYPE
+
+/* Define to the type of elements in the array set by `getgroups'.
+ Usually this is either `int' or `gid_t'. */
+#undef GETGROUPS_T
+
+/* Characteristics of the machine archictecture. */
+
+/* If using the C implementation of alloca, define if you know the
+ direction of stack growth for your system; otherwise it will be
+ automatically deduced at run-time.
+ STACK_DIRECTION > 0 => grows toward higher addresses
+ STACK_DIRECTION < 0 => grows toward lower addresses
+ STACK_DIRECTION = 0 => direction of growth unknown
+ */
+#undef STACK_DIRECTION
+
+/* Define if the machine architecture is big-endian. */
+#undef WORDS_BIGENDIAN
+
+/* Check for the presence of certain non-function symbols in the system
+ libraries. */
+
+/* Define if `sys_siglist' is declared by <signal.h> or <unistd.h>. */
+#undef HAVE_DECL_SYS_SIGLIST
+#undef SYS_SIGLIST_DECLARED
+
+/* Define if `_sys_siglist' is declared by <signal.h> or <unistd.h>. */
+#undef UNDER_SYS_SIGLIST_DECLARED
+
+#undef HAVE_SYS_SIGLIST
+
+#undef HAVE_UNDER_SYS_SIGLIST
+
+#undef HAVE_SYS_ERRLIST
+
+#undef HAVE_TZNAME
+#undef HAVE_DECL_TZNAME
+
+/* Characteristics of some of the system structures. */
+
+#undef HAVE_STRUCT_DIRENT_D_INO
+
+#undef HAVE_STRUCT_DIRENT_D_FILENO
+
+#undef HAVE_STRUCT_DIRENT_D_NAMLEN
+
+#undef TIOCSTAT_IN_SYS_IOCTL
+
+#undef FIONREAD_IN_SYS_IOCTL
+
+#undef GWINSZ_IN_SYS_IOCTL
+
+#undef STRUCT_WINSIZE_IN_SYS_IOCTL
+
+#undef TM_IN_SYS_TIME
+
+#undef STRUCT_WINSIZE_IN_TERMIOS
+
+#undef SPEED_T_IN_SYS_TYPES
+
+#undef TERMIOS_LDISC
+
+#undef TERMIO_LDISC
+
+#undef HAVE_STRUCT_STAT_ST_BLOCKS
+
+#undef HAVE_STRUCT_TM_TM_ZONE
+#undef HAVE_TM_ZONE
+
+#undef HAVE_TIMEVAL
+
+#undef HAVE_STRUCT_TIMEZONE
+
+/* Characteristics of definitions in the system header files. */
+
+#undef HAVE_GETPW_DECLS
+
+#undef HAVE_RESOURCE
+
+#undef HAVE_LIBC_FNM_EXTMATCH
+
+#undef HAVE_DECL_CONFSTR
+
+#undef HAVE_DECL_PRINTF
+
+#undef HAVE_DECL_SBRK
+
+#undef HAVE_DECL_STRCPY
+
+#undef HAVE_DECL_STRSIGNAL
+
+#undef HAVE_DECL_STRTOLD
+
+#undef PRI_MACROS_BROKEN
+
+#undef STRTOLD_BROKEN
+
+/* Define if WCONTINUED is defined in system headers, but rejected by waitpid */
+#undef WCONTINUED_BROKEN
+
+/* These are checked with BASH_CHECK_DECL */
+
+#undef HAVE_DECL_STRTOIMAX
+#undef HAVE_DECL_STRTOL
+#undef HAVE_DECL_STRTOLL
+#undef HAVE_DECL_STRTOUL
+#undef HAVE_DECL_STRTOULL
+#undef HAVE_DECL_STRTOUMAX
+
+/* Characteristics of system calls and C library functions. */
+
+/* Define if the `getpgrp' function takes no argument. */
+#undef GETPGRP_VOID
+
+#undef NAMED_PIPES_MISSING
+
+#undef OPENDIR_NOT_ROBUST
+
+#undef PGRP_PIPE
+
+/* Define if the setvbuf function takes the buffering type as its second
+ argument and the buffer pointer as the third, as on System V
+ before release 3. */
+#undef SETVBUF_REVERSED
+
+#undef STAT_MACROS_BROKEN
+
+#undef ULIMIT_MAXFDS
+
+#undef CAN_REDEFINE_GETENV
+
+#undef HAVE_STD_PUTENV
+
+#undef HAVE_STD_UNSETENV
+
+#undef HAVE_PRINTF_A_FORMAT
+
+#undef CTYPE_NON_ASCII
+
+/* Define if you have <langinfo.h> and nl_langinfo(CODESET). */
+#undef HAVE_LANGINFO_CODESET
+
+/* Characteristics of properties exported by the kernel. */
+
+/* Define if the kernel can exec files beginning with #! */
+#undef HAVE_HASH_BANG_EXEC
+
+/* Define if you have the /dev/fd devices to map open files into the file system. */
+#undef HAVE_DEV_FD
+
+/* Defined to /dev/fd or /proc/self/fd (linux). */
+#undef DEV_FD_PREFIX
+
+/* Define if you have the /dev/stdin device. */
+#undef HAVE_DEV_STDIN
+
+/* The type of iconv's `inbuf' argument */
+#undef ICONV_CONST
+
+/* Type and behavior of signal handling functions. */
+
+/* Define as the return type of signal handlers (int or void). */
+#undef RETSIGTYPE
+
+/* Define if return type of signal handlers is void */
+#undef VOID_SIGHANDLER
+
+#undef MUST_REINSTALL_SIGHANDLERS
+
+#undef HAVE_BSD_SIGNALS
+
+#undef HAVE_POSIX_SIGNALS
+
+#undef HAVE_USG_SIGHOLD
+
+#undef UNUSABLE_RT_SIGNALS
+
+
+/* Presence of system and C library functions. */
+
+/* Define if you have the asprintf function. */
+#undef HAVE_ASPRINTF
+
+/* Define if you have the bcopy function. */
+#undef HAVE_BCOPY
+
+/* Define if you have the bzero function. */
+#undef HAVE_BZERO
+
+/* Define if you have the confstr function. */
+#undef HAVE_CONFSTR
+
+/* Define if you have the dlclose function. */
+#undef HAVE_DLCLOSE
+
+/* Define if you have the dlopen function. */
+#undef HAVE_DLOPEN
+
+/* Define if you have the dlsym function. */
+#undef HAVE_DLSYM
+
+/* Define if you don't have vprintf but do have _doprnt. */
+#undef HAVE_DOPRNT
+
+/* Define if you have the dup2 function. */
+#undef HAVE_DUP2
+
+/* Define if you have the eaccess function. */
+#undef HAVE_EACCESS
+
+/* Define if you have the fcntl function. */
+#undef HAVE_FCNTL
+
+/* Define if you have the fdprintf function. */
+#undef HAVE_FDPRINTF
+
+/* Define if you have the fpurge/__fpurge function. */
+#undef HAVE_FPURGE
+#undef HAVE___FPURGE
+#undef HAVE_DECL_FPURGE
+
+/* Define if you have the getaddrinfo function. */
+#undef HAVE_GETADDRINFO
+
+/* Define if you have the getcwd function. */
+#undef HAVE_GETCWD
+
+/* Define if you have the getdtablesize function. */
+#undef HAVE_GETDTABLESIZE
+
+/* Define if you have the getgroups function. */
+#undef HAVE_GETGROUPS
+
+/* Define if you have the gethostbyname function. */
+#undef HAVE_GETHOSTBYNAME
+
+/* Define if you have the gethostname function. */
+#undef HAVE_GETHOSTNAME
+
+/* Define if you have the getpagesize function. */
+#undef HAVE_GETPAGESIZE
+
+/* Define if you have the getpeername function. */
+#undef HAVE_GETPEERNAME
+
+/* Define if you have the getpwent function. */
+#undef HAVE_GETPWENT
+
+/* Define if you have the getpwnam function. */
+#undef HAVE_GETPWNAM
+
+/* Define if you have the getpwuid function. */
+#undef HAVE_GETPWUID
+
+/* Define if you have the getrlimit function. */
+#undef HAVE_GETRLIMIT
+
+/* Define if you have the getrusage function. */
+#undef HAVE_GETRUSAGE
+
+/* Define if you have the getservbyname function. */
+#undef HAVE_GETSERVBYNAME
+
+/* Define if you have the getservent function. */
+#undef HAVE_GETSERVENT
+
+/* Define if you have the gettimeofday function. */
+#undef HAVE_GETTIMEOFDAY
+
+/* Define if you have the getwd function. */
+#undef HAVE_GETWD
+
+/* Define if you have the iconv function. */
+#undef HAVE_ICONV
+
+/* Define if you have the inet_aton function. */
+#undef HAVE_INET_ATON
+
+/* Define if you have the isascii function. */
+#undef HAVE_ISASCII
+
+/* Define if you have the isblank function. */
+#undef HAVE_ISBLANK
+
+/* Define if you have the isgraph function. */
+#undef HAVE_ISGRAPH
+
+/* Define if you have the isinf function in libc */
+#undef HAVE_ISINF_IN_LIBC
+
+/* Define if you have the isnan function in libc */
+#undef HAVE_ISNAN_IN_LIBC
+
+/* Define if you have the isprint function. */
+#undef HAVE_ISPRINT
+
+/* Define if you have the isspace function. */
+#undef HAVE_ISSPACE
+
+/* Define if you have the iswctype function. */
+#undef HAVE_ISWCTYPE
+
+/* Define if you have the iswlower function. */
+#undef HAVE_ISWLOWER
+
+/* Define if you have the iswupper function. */
+#undef HAVE_ISWUPPER
+
+/* Define if you have the isxdigit function. */
+#undef HAVE_ISXDIGIT
+
+/* Define if you have the kill function. */
+#undef HAVE_KILL
+
+/* Define if you have the killpg function. */
+#undef HAVE_KILLPG
+
+/* Define if you have the lstat function. */
+#undef HAVE_LSTAT
+
+/* Define if you have the locale_charset function. */
+#undef HAVE_LOCALE_CHARSET
+
+/* Define if you have the mbrlen function. */
+#undef HAVE_MBRLEN
+
+/* Define if you have the mbrtowc function. */
+#undef HAVE_MBRTOWC
+
+/* Define if you have the mbscasecmp function. */
+#undef HAVE_MBSCASECMP
+
+/* Define if you have the mbschr function. */
+#undef HAVE_MBSCHR
+
+/* Define if you have the mbscmp function. */
+#undef HAVE_MBSCMP
+
+/* Define if you have the mbsrtowcs function. */
+#undef HAVE_MBSRTOWCS
+
+/* Define if you have the memmove function. */
+#undef HAVE_MEMMOVE
+
+/* Define if you have the memset function. */
+#undef HAVE_MEMSET
+
+/* Define if you have the mkfifo function. */
+#undef HAVE_MKFIFO
+
+/* Define if you have the pathconf function. */
+#undef HAVE_PATHCONF
+
+/* Define if you have the putenv function. */
+#undef HAVE_PUTENV
+
+/* Define if you have the raise function. */
+#undef HAVE_RAISE
+
+/* Define if you have the readlink function. */
+#undef HAVE_READLINK
+
+/* Define if you have the regcomp function. */
+#undef HAVE_REGCOMP
+
+/* Define if you have the regexec function. */
+#undef HAVE_REGEXEC
+
+/* Define if you have the rename function. */
+#undef HAVE_RENAME
+
+/* Define if you have the sbrk function. */
+#undef HAVE_SBRK
+
+/* Define if you have the select function. */
+#undef HAVE_SELECT
+
+/* Define if you have the setdtablesize function. */
+#undef HAVE_SETDTABLESIZE
+
+/* Define if you have the setenv function. */
+#undef HAVE_SETENV
+
+/* Define if you have the setitimer function. */
+#undef HAVE_SETITIMER
+
+/* Define if you have the setlinebuf function. */
+#undef HAVE_SETLINEBUF
+
+/* Define if you have the setlocale function. */
+#undef HAVE_SETLOCALE
+
+/* Define if you have the setostype function. */
+#undef HAVE_SETOSTYPE
+
+/* Define if you have the setregid function. */
+#undef HAVE_SETREGID
+#undef HAVE_DECL_SETREGID
+
+/* Define if you have the setvbuf function. */
+#undef HAVE_SETVBUF
+
+/* Define if you have the siginterrupt function. */
+#undef HAVE_SIGINTERRUPT
+
+/* Define if you have the POSIX.1-style sigsetjmp function. */
+#undef HAVE_POSIX_SIGSETJMP
+
+/* Define if you have the snprintf function. */
+#undef HAVE_SNPRINTF
+
+/* Define if you have the strcasecmp function. */
+#undef HAVE_STRCASECMP
+
+/* Define if you have the strcasestr function. */
+#undef HAVE_STRCASESTR
+
+/* Define if you have the strchr function. */
+#undef HAVE_STRCHR
+
+/* Define if you have the strcoll function. */
+#undef HAVE_STRCOLL
+
+/* Define if you have the strerror function. */
+#undef HAVE_STRERROR
+
+/* Define if you have the strftime function. */
+#undef HAVE_STRFTIME
+
+/* Define if you have the strnlen function. */
+#undef HAVE_STRNLEN
+
+/* Define if you have the strpbrk function. */
+#undef HAVE_STRPBRK
+
+/* Define if you have the strstr function. */
+#undef HAVE_STRSTR
+
+/* Define if you have the strtod function. */
+#undef HAVE_STRTOD
+
+/* Define if you have the strtoimax function. */
+#undef HAVE_STRTOIMAX
+
+/* Define if you have the strtol function. */
+#undef HAVE_STRTOL
+
+/* Define if you have the strtoll function. */
+#undef HAVE_STRTOLL
+
+/* Define if you have the strtoul function. */
+#undef HAVE_STRTOUL
+
+/* Define if you have the strtoull function. */
+#undef HAVE_STRTOULL
+
+/* Define if you have the strtoumax function. */
+#undef HAVE_STRTOUMAX
+
+/* Define if you have the strsignal function or macro. */
+#undef HAVE_STRSIGNAL
+
+/* Define if you have the sysconf function. */
+#undef HAVE_SYSCONF
+
+/* Define if you have the syslog function. */
+#undef HAVE_SYSLOG
+
+/* Define if you have the tcgetattr function. */
+#undef HAVE_TCGETATTR
+
+/* Define if you have the tcgetpgrp function. */
+#undef HAVE_TCGETPGRP
+
+/* Define if you have the times function. */
+#undef HAVE_TIMES
+
+/* Define if you have the towlower function. */
+#undef HAVE_TOWLOWER
+
+/* Define if you have the towupper function. */
+#undef HAVE_TOWUPPER
+
+/* Define if you have the ttyname function. */
+#undef HAVE_TTYNAME
+
+/* Define if you have the tzset function. */
+#undef HAVE_TZSET
+
+/* Define if you have the ulimit function. */
+#undef HAVE_ULIMIT
+
+/* Define if you have the uname function. */
+#undef HAVE_UNAME
+
+/* Define if you have the unsetenv function. */
+#undef HAVE_UNSETENV
+
+/* Define if you have the vasprintf function. */
+#undef HAVE_VASPRINTF
+
+/* Define if you have the vprintf function. */
+#undef HAVE_VPRINTF
+
+/* Define if you have the vsnprintf function. */
+#undef HAVE_VSNPRINTF
+
+/* Define if you have the waitpid function. */
+#undef HAVE_WAITPID
+
+/* Define if you have the wait3 function. */
+#undef HAVE_WAIT3
+
+/* Define if you have the wcrtomb function. */
+#undef HAVE_WCRTOMB
+
+/* Define if you have the wcscoll function. */
+#undef HAVE_WCSCOLL
+
+/* Define if you have the wcsdup function. */
+#undef HAVE_WCSDUP
+
+/* Define if you have the wctype function. */
+#undef HAVE_WCTYPE
+
+/* Define if you have the wcwidth function. */
+#undef HAVE_WCWIDTH
+
+/* Presence of certain system include files. */
+
+/* Define if you have the <arpa/inet.h> header file. */
+#undef HAVE_ARPA_INET_H
+
+/* Define if you have the <dirent.h> header file. */
+#undef HAVE_DIRENT_H
+
+/* Define if you have the <dlfcn.h> header file. */
+#undef HAVE_DLFCN_H
+
+/* Define if you have the <grp.h> header file. */
+#undef HAVE_GRP_H
+
+/* Define if you have the <inttypes.h> header file. */
+#undef HAVE_INTTYPES_H
+
+/* Define if you have the <langinfo.h> header file. */
+#undef HAVE_LANGINFO_H
+
+/* Define if you have the <libintl.h> header file. */
+#undef HAVE_LIBINTL_H
+
+/* Define if you have the <limits.h> header file. */
+#undef HAVE_LIMITS_H
+
+/* Define if you have the <locale.h> header file. */
+#undef HAVE_LOCALE_H
+
+/* Define if you have the <ndir.h> header file. */
+#undef HAVE_NDIR_H
+
+/* Define if you have the <netdh.h> header file. */
+#undef HAVE_NETDB_H
+
+/* Define if you have the <netinet/in.h> header file. */
+#undef HAVE_NETINET_IN_H
+
+/* Define if you have the <pwd.h> header file. */
+#undef HAVE_PWD_H
+
+/* Define if you have the <regex.h> header file. */
+#undef HAVE_REGEX_H
+
+/* Define if you have the <stdlib.h> header file. */
+#undef HAVE_STDLIB_H
+
+/* Define if you have the <stdarg.h> header file. */
+#undef HAVE_STDARG_H
+
+/* Define if you have the <string.h> header file. */
+#undef HAVE_STRING_H
+
+/* Define if you have the <strings.h> header file. */
+#undef HAVE_STRINGS_H
+
+/* Define if you have the <memory.h> header file. */
+#undef HAVE_MEMORY_H
+
+/* Define if you have the <stddef.h> header file. */
+#undef HAVE_STDDEF_H
+
+/* Define if you have the <stdint.h> header file. */
+#undef HAVE_STDINT_H
+
+/* Define if you have the <syslog.h> header file. */
+#undef HAVE_SYSLOG_H
+
+/* Define if you have the <sys/dir.h> header file. */
+#undef HAVE_SYS_DIR_H
+
+/* Define if you have the <sys/file.h> header file. */
+#undef HAVE_SYS_FILE_H
+
+/* Define if you have the <sys/ndir.h> header file. */
+#undef HAVE_SYS_NDIR_H
+
+/* Define if you have the <sys/param.h> header file. */
+#undef HAVE_SYS_PARAM_H
+
+/* Define if you have the <sys/pte.h> header file. */
+#undef HAVE_SYS_PTE_H
+
+/* Define if you have the <sys/ptem.h> header file. */
+#undef HAVE_SYS_PTEM_H
+
+/* Define if you have the <sys/resource.h> header file. */
+#undef HAVE_SYS_RESOURCE_H
+
+/* Define if you have the <sys/select.h> header file. */
+#undef HAVE_SYS_SELECT_H
+
+/* Define if you have the <sys/socket.h> header file. */
+#undef HAVE_SYS_SOCKET_H
+
+/* Define if you have the <sys/stat.h> header file. */
+#undef HAVE_SYS_STAT_H
+
+/* Define if you have the <sys/stream.h> header file. */
+#undef HAVE_SYS_STREAM_H
+
+/* Define if you have <sys/time.h> */
+#undef HAVE_SYS_TIME_H
+
+#undef TIME_WITH_SYS_TIME
+
+/* Define if you have <sys/times.h> */
+#undef HAVE_SYS_TIMES_H
+
+/* Define if you have the <sys/types.h> header file. */
+#undef HAVE_SYS_TYPES_H
+
+/* Define if you have <sys/wait.h> that is POSIX.1 compatible. */
+#undef HAVE_SYS_WAIT_H
+
+/* Define if you have the <termcap.h> header file. */
+#undef HAVE_TERMCAP_H
+
+/* Define if you have the <termio.h> header file. */
+#undef HAVE_TERMIO_H
+
+/* Define if you have the <termios.h> header file. */
+#undef HAVE_TERMIOS_H
+
+/* Define if you have the <unistd.h> header file. */
+#undef HAVE_UNISTD_H
+
+/* Define if you have the <varargs.h> header file. */
+#undef HAVE_VARARGS_H
+
+/* Define if you have the <wchar.h> header file. */
+#undef HAVE_WCHAR_H
+
+/* Define if you have the <varargs.h> header file. */
+#undef HAVE_WCTYPE_H
+
+/* Presence of certain system libraries. */
+
+#undef HAVE_LIBDL
+
+#undef HAVE_LIBSUN
+
+#undef HAVE_LIBSOCKET
+
+
+/* Define if on MINIX. */
+#undef _MINIX
+
+/* Are we running SVR5 (UnixWare 7)? */
+#undef SVR5
+
+/* Are we running SVR4.2? */
+#undef SVR4_2
+
+/* Are we running some version of SVR4? */
+#undef SVR4
+
+/* Define if job control is unusable or unsupported. */
+#undef JOB_CONTROL_MISSING
+
+/* Do we need to define _KERNEL to get the RLIMIT_* defines from
+ <sys/resource.h>? */
+#undef RLIMIT_NEEDS_KERNEL
+
+/* Number of bits in a file offset, on hosts where this is settable. */
+#undef _FILE_OFFSET_BITS
+
+/* Define for large files on AIX-style hosts. */
+#undef _LARGE_FILES
+
+/* Do strcoll(3) and strcmp(3) give different results in the default locale? */
+#undef STRCOLL_BROKEN
+
+#undef DUP2_BROKEN
+
+#undef GETCWD_BROKEN
+
+/* Additional defines for configuring lib/intl, maintained by autoscan/autoheader */
+
+/* Define if you have the <argz.h> header file. */
+#undef HAVE_ARGZ_H
+
+/* Define if you have the <errno.h> header file. */
+#undef HAVE_ERRNO_H
+
+/* Define if you have the <fcntl.h> header file. */
+#undef HAVE_FCNTL_H
+
+/* Define if you have the <malloc.h> header file. */
+#undef HAVE_MALLOC_H
+
+/* Define if you have the <stdio_ext.h> header file. */
+#undef HAVE_STDIO_EXT_H
+
+/* Define if you have the `dcgettext' function. */
+#undef HAVE_DCGETTEXT
+
+/* Define if you have the `localeconv' function. */
+#undef HAVE_LOCALECONV
+
+/* Define if your system has a working `malloc' function. */
+/* #undef HAVE_MALLOC */
+
+/* Define if you have the `mempcpy' function. */
+#undef HAVE_MEMPCPY
+
+/* Define if you have a working `mmap' system call. */
+#undef HAVE_MMAP
+
+/* Define if you have the `munmap' function. */
+#undef HAVE_MUNMAP
+
+/* Define if you have the `nl_langinfo' function. */
+#undef HAVE_NL_LANGINFO
+
+/* Define if you have the `stpcpy' function. */
+#undef HAVE_STPCPY
+
+/* Define if you have the `strcspn' function. */
+#undef HAVE_STRCSPN
+
+/* Define if you have the `strdup' function. */
+#undef HAVE_STRDUP
+
+/* Define if you have the `__argz_count' function. */
+#undef HAVE___ARGZ_COUNT
+
+/* Define if you have the `__argz_next' function. */
+#undef HAVE___ARGZ_NEXT
+
+/* Define if you have the `__argz_stringify' function. */
+#undef HAVE___ARGZ_STRINGIFY
+
+/* End additions for lib/intl */
+
+#include "config-bot.h"
+
+#endif /* _CONFIG_H_ */
#! /bin/sh
-# From configure.in for Bash 4.1, version 4.019.
+# From configure.in for Bash 4.1, version 4.020.
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.63 for bash 4.1-release.
+# Generated by GNU Autoconf 2.63 for bash 4.1-maint.
#
# Report bugs to <bug-bash@gnu.org>.
#
# Identity of this package.
PACKAGE_NAME='bash'
PACKAGE_TARNAME='bash'
-PACKAGE_VERSION='4.1-release'
-PACKAGE_STRING='bash 4.1-release'
+PACKAGE_VERSION='4.1-maint'
+PACKAGE_STRING='bash 4.1-maint'
PACKAGE_BUGREPORT='bug-bash@gnu.org'
ac_unique_file="shell.h"
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-\`configure' configures bash 4.1-release to adapt to many kinds of systems.
+\`configure' configures bash 4.1-maint to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of bash 4.1-release:";;
+ short | recursive ) echo "Configuration of bash 4.1-maint:";;
esac
cat <<\_ACEOF
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-bash configure 4.1-release
+bash configure 4.1-maint
generated by GNU Autoconf 2.63
Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by bash $as_me 4.1-release, which was
+It was created by bash $as_me 4.1-maint, which was
generated by GNU Autoconf 2.63. Invocation command line was
$ $0 $@
BASHVERS=4.1
-RELSTATUS=release
+RELSTATUS=maint
case "$RELSTATUS" in
alp*|bet*|dev*|rc*|maint*) DEBUG='-DDEBUG' MALLOC_DEBUG='-DMALLOC_DEBUG' ;;
+
for ac_header in unistd.h stdlib.h stdarg.h varargs.h limits.h string.h \
memory.h locale.h termcap.h termio.h termios.h dlfcn.h \
stddef.h stdint.h netdb.h pwd.h grp.h strings.h regex.h \
- syslog.h
+ syslog.h ulimit.h
do
as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by bash $as_me 4.1-release, which was
+This file was extended by bash $as_me 4.1-maint, which was
generated by GNU Autoconf 2.63. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
_ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_version="\\
-bash config.status 4.1-release
+bash config.status 4.1-maint
configured by $0, generated by GNU Autoconf 2.63,
with options \\"`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\"
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-AC_REVISION([for Bash 4.1, version 4.019])dnl
+AC_REVISION([for Bash 4.1, version 4.020])dnl
define(bashvers, 4.1)
-define(relstatus, release)
+define(relstatus, maint)
AC_INIT([bash], bashvers-relstatus, [bug-bash@gnu.org])
AC_CHECK_HEADERS(unistd.h stdlib.h stdarg.h varargs.h limits.h string.h \
memory.h locale.h termcap.h termio.h termios.h dlfcn.h \
stddef.h stdint.h netdb.h pwd.h grp.h strings.h regex.h \
- syslog.h)
+ syslog.h ulimit.h)
AC_CHECK_HEADERS(sys/pte.h sys/stream.h sys/select.h sys/file.h \
sys/resource.h sys/param.h sys/socket.h sys/stat.h \
sys/time.h sys/times.h sys/types.h sys/wait.h)
--- /dev/null
+dnl
+dnl Configure script for bash-4.1
+dnl
+dnl report bugs to chet@po.cwru.edu
+dnl
+dnl Process this file with autoconf to produce a configure script.
+
+# Copyright (C) 1987-2009 Free Software Foundation, Inc.
+
+#
+# This program 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.
+#
+# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+
+AC_REVISION([for Bash 4.1, version 4.019])dnl
+
+define(bashvers, 4.1)
+define(relstatus, maint)
+
+AC_INIT([bash], bashvers-relstatus, [bug-bash@gnu.org])
+
+dnl make sure we are using a recent autoconf version
+AC_PREREQ(2.50)
+
+AC_CONFIG_SRCDIR(shell.h)
+dnl where to find install.sh, config.sub, and config.guess
+AC_CONFIG_AUX_DIR(./support)
+AC_CONFIG_HEADERS(config.h)
+
+dnl checks for version info
+BASHVERS=bashvers
+RELSTATUS=relstatus
+
+dnl defaults for debug settings
+case "$RELSTATUS" in
+alp*|bet*|dev*|rc*|maint*) DEBUG='-DDEBUG' MALLOC_DEBUG='-DMALLOC_DEBUG' ;;
+*) DEBUG= MALLOC_DEBUG= ;;
+esac
+
+dnl canonicalize the host and os so we can do some tricky things before
+dnl parsing options
+AC_CANONICAL_HOST
+
+dnl configure defaults
+opt_bash_malloc=yes
+opt_purify=no
+opt_purecov=no
+opt_afs=no
+opt_curses=no
+opt_with_installed_readline=no
+
+#htmldir=
+
+dnl some systems should be configured without the bash malloc by default
+dnl and some need a special compiler or loader
+dnl look in the NOTES file for more
+case "${host_cpu}-${host_os}" in
+alpha*-*) opt_bash_malloc=no ;; # alpha running osf/1 or linux
+*[[Cc]]ray*-*) opt_bash_malloc=no ;; # Crays
+*-osf1*) opt_bash_malloc=no ;; # other osf/1 machines
+sparc-svr4*) opt_bash_malloc=no ;; # sparc SVR4, SVR4.2
+sparc-netbsd*) opt_bash_malloc=no ;; # needs 8-byte alignment
+mips-irix6*) opt_bash_malloc=no ;; # needs 8-byte alignment
+m68k-sysv) opt_bash_malloc=no ;; # fixes file descriptor leak in closedir
+sparc-linux*) opt_bash_malloc=no ;; # sparc running linux; requires ELF
+#*-freebsd*-gnu) opt_bash_malloc=no ;; # there's some undetermined problem here
+#*-freebsd*) opt_bash_malloc=no ;; # they claim it's better; I disagree
+*-openbsd*) opt_bash_malloc=no ;; # they claim it needs eight-bit alignment
+*-aix*) opt_bash_malloc=no ;; # AIX machines
+*-nextstep*) opt_bash_malloc=no ;; # NeXT machines running NeXTstep
+*-macos*) opt_bash_malloc=no ;; # Apple MacOS X
+*-rhapsody*) opt_bash_malloc=no ;; # Apple Rhapsody (MacOS X)
+*-darwin*) opt_bash_malloc=no ;; # Apple Darwin (MacOS X)
+*-dgux*) opt_bash_malloc=no ;; # DG/UX machines
+*-qnx*) opt_bash_malloc=no ;; # QNX 4.2, QNX 6.x
+*-machten4) opt_bash_malloc=no ;; # MachTen 4.x
+*-bsdi2.1|*-bsdi3.?) opt_bash_malloc=no ; : ${CC:=shlicc2} ;; # for loadable builtins
+*-beos*) opt_bash_malloc=no ;; # they say it's suitable
+*-cygwin*) opt_bash_malloc=no ;; # Cygnus's CYGWIN environment
+*-opennt*|*-interix*) opt_bash_malloc=no ;; # Interix, now owned by Microsoft
+esac
+
+# memory scrambling on free()
+case "${host_os}" in
+sco3.2v5*|sco3.2v4*) opt_memscramble=no ;;
+*) opt_memscramble=yes ;;
+esac
+
+dnl
+dnl macros for the bash debugger
+dnl
+AM_PATH_LISPDIR
+AC_ARG_VAR(DEBUGGER_START_FILE, [location of bash debugger initialization file])
+
+dnl arguments to configure
+dnl packages
+AC_ARG_WITH(afs, AC_HELP_STRING([--with-afs], [if you are running AFS]), opt_afs=$withval)
+AC_ARG_WITH(bash-malloc, AC_HELP_STRING([--with-bash-malloc], [use the Bash version of malloc]), opt_bash_malloc=$withval)
+AC_ARG_WITH(curses, AC_HELP_STRING([--with-curses], [use the curses library instead of the termcap library]), opt_curses=$withval)
+AC_ARG_WITH(gnu-malloc, AC_HELP_STRING([--with-gnu-malloc], [synonym for --with-bash-malloc]), opt_bash_malloc=$withval)
+AC_ARG_WITH(installed-readline, AC_HELP_STRING([--with-installed-readline], [use a version of the readline library that is already installed]), opt_with_installed_readline=$withval)
+AC_ARG_WITH(purecov, AC_HELP_STRING([--with-purecov], [configure to postprocess with pure coverage]), opt_purecov=$withval)
+AC_ARG_WITH(purify, AC_HELP_STRING([--with-purify], [configure to postprocess with purify]), opt_purify=$withval)
+
+if test "$opt_bash_malloc" = yes; then
+ MALLOC_TARGET=malloc
+ MALLOC_SRC=malloc.c
+
+ MALLOC_LIB='-lmalloc'
+ MALLOC_LIBRARY='$(ALLOC_LIBDIR)/libmalloc.a'
+ MALLOC_LDFLAGS='-L$(ALLOC_LIBDIR)'
+ MALLOC_DEP='$(MALLOC_LIBRARY)'
+
+ AC_DEFINE(USING_BASH_MALLOC)
+else
+ MALLOC_LIB=
+ MALLOC_LIBRARY=
+ MALLOC_LDFLAGS=
+ MALLOC_DEP=
+fi
+
+if test "$opt_purify" = yes; then
+ PURIFY="purify "
+ AC_DEFINE(DISABLE_MALLOC_WRAPPERS)
+else
+ PURIFY=
+fi
+
+if test "$opt_purecov" = yes; then
+ PURIFY="${PURIFY}purecov"
+fi
+
+if test "$opt_afs" = yes; then
+ AC_DEFINE(AFS)
+fi
+
+if test "$opt_curses" = yes; then
+ prefer_curses=yes
+fi
+
+if test -z "${DEBUGGER_START_FILE}"; then
+ DEBUGGER_START_FILE='${datadir}/bashdb/bashdb-main.inc'
+fi
+
+dnl optional shell features in config.h.in
+opt_minimal_config=no
+
+opt_job_control=yes
+opt_alias=yes
+opt_readline=yes
+opt_history=yes
+opt_bang_history=yes
+opt_dirstack=yes
+opt_restricted=yes
+opt_process_subst=yes
+opt_prompt_decoding=yes
+opt_select=yes
+opt_help=yes
+opt_array_variables=yes
+opt_dparen_arith=yes
+opt_extended_glob=yes
+opt_brace_expansion=yes
+opt_disabled_builtins=no
+opt_command_timing=yes
+opt_xpg_echo=no
+opt_strict_posix=no
+opt_cond_command=yes
+opt_cond_regexp=yes
+opt_coproc=yes
+opt_arith_for_command=yes
+opt_net_redirs=yes
+opt_progcomp=yes
+opt_separate_help=no
+opt_multibyte=yes
+opt_debugger=yes
+opt_single_longdoc_strings=yes
+opt_casemod_attrs=yes
+opt_casemod_expansions=yes
+opt_extglob_default=no
+
+dnl options that affect how bash is compiled and linked
+opt_static_link=no
+opt_profiling=no
+
+dnl argument parsing for optional features
+AC_ARG_ENABLE(minimal-config, AC_HELP_STRING([--enable-minimal-config], [a minimal sh-like configuration]), opt_minimal_config=$enableval)
+
+dnl a minimal configuration turns everything off, but features can be
+dnl added individually
+if test $opt_minimal_config = yes; then
+ opt_job_control=no opt_alias=no opt_readline=no
+ opt_history=no opt_bang_history=no opt_dirstack=no
+ opt_restricted=no opt_process_subst=no opt_prompt_decoding=no
+ opt_select=no opt_help=no opt_array_variables=no opt_dparen_arith=no
+ opt_brace_expansion=no opt_disabled_builtins=no opt_command_timing=no
+ opt_extended_glob=no opt_cond_command=no opt_arith_for_command=no
+ opt_net_redirs=no opt_progcomp=no opt_separate_help=no
+ opt_multibyte=yes opt_cond_regexp=no opt_coproc=no
+ opt_casemod_attrs=no opt_casemod_expansions=no opt_extglob_default=no
+fi
+
+AC_ARG_ENABLE(alias, AC_HELP_STRING([--enable-alias], [enable shell aliases]), opt_alias=$enableval)
+AC_ARG_ENABLE(arith-for-command, AC_HELP_STRING([--enable-arith-for-command], [enable arithmetic for command]), opt_arith_for_command=$enableval)
+AC_ARG_ENABLE(array-variables, AC_HELP_STRING([--enable-array-variables], [include shell array variables]), opt_array_variables=$enableval)
+AC_ARG_ENABLE(bang-history, AC_HELP_STRING([--enable-bang-history], [turn on csh-style history substitution]), opt_bang_history=$enableval)
+AC_ARG_ENABLE(brace-expansion, AC_HELP_STRING([--enable-brace-expansion], [include brace expansion]), opt_brace_expansion=$enableval)
+AC_ARG_ENABLE(casemod-attributes, AC_HELP_STRING([--enable-casemod-attributes], [include case-modifying variable attributes]), opt_casemod_attrs=$enableval)
+AC_ARG_ENABLE(casemod-expansions, AC_HELP_STRING([--enable-casemod-expansions], [include case-modifying word expansions]), opt_casemod_expansions=$enableval)
+AC_ARG_ENABLE(command-timing, AC_HELP_STRING([--enable-command-timing], [enable the time reserved word and command timing]), opt_command_timing=$enableval)
+AC_ARG_ENABLE(cond-command, AC_HELP_STRING([--enable-cond-command], [enable the conditional command]), opt_cond_command=$enableval)
+AC_ARG_ENABLE(cond-regexp, AC_HELP_STRING([--enable-cond-regexp], [enable extended regular expression matching in conditional commands]), opt_cond_regexp=$enableval)
+AC_ARG_ENABLE(coprocesses, AC_HELP_STRING([--enable-coprocesses], [enable coprocess support and the coproc reserved word]), opt_coproc=$enableval)
+AC_ARG_ENABLE(debugger, AC_HELP_STRING([--enable-debugger], [enable support for bash debugger]), opt_debugger=$enableval)
+AC_ARG_ENABLE(directory-stack, AC_HELP_STRING([--enable-directory-stack], [enable builtins pushd/popd/dirs]), opt_dirstack=$enableval)
+AC_ARG_ENABLE(disabled-builtins, AC_HELP_STRING([--enable-disabled-builtins], [allow disabled builtins to still be invoked]), opt_disabled_builtins=$enableval)
+AC_ARG_ENABLE(dparen-arithmetic, AC_HELP_STRING([--enable-dparen-arithmetic], [include ((...)) command]), opt_dparen_arith=$enableval)
+AC_ARG_ENABLE(extended-glob, AC_HELP_STRING([--enable-extended-glob], [include ksh-style extended pattern matching]), opt_extended_glob=$enableval)
+AC_ARG_ENABLE(extended-glob-default, AC_HELP_STRING([--enable-extended-glob-default], [force extended pattern matching to be enabled by default]), opt_extglob_default=$enableval)
+AC_ARG_ENABLE(help-builtin, AC_HELP_STRING([--enable-help-builtin], [include the help builtin]), opt_help=$enableval)
+AC_ARG_ENABLE(history, AC_HELP_STRING([--enable-history], [turn on command history]), opt_history=$enableval)
+AC_ARG_ENABLE(job-control, AC_HELP_STRING([--enable-job-control], [enable job control features]), opt_job_control=$enableval)
+AC_ARG_ENABLE(multibyte, AC_HELP_STRING([--enable-multibyte], [enable multibyte characters if OS supports them]), opt_multibyte=$enableval)
+AC_ARG_ENABLE(net-redirections, AC_HELP_STRING([--enable-net-redirections], [enable /dev/tcp/host/port redirection]), opt_net_redirs=$enableval)
+AC_ARG_ENABLE(process-substitution, AC_HELP_STRING([--enable-process-substitution], [enable process substitution]), opt_process_subst=$enableval)
+AC_ARG_ENABLE(progcomp, AC_HELP_STRING([--enable-progcomp], [enable programmable completion and the complete builtin]), opt_progcomp=$enableval)
+AC_ARG_ENABLE(prompt-string-decoding, AC_HELP_STRING([--enable-prompt-string-decoding], [turn on escape character decoding in prompts]), opt_prompt_decoding=$enableval)
+AC_ARG_ENABLE(readline, AC_HELP_STRING([--enable-readline], [turn on command line editing]), opt_readline=$enableval)
+AC_ARG_ENABLE(restricted, AC_HELP_STRING([--enable-restricted], [enable a restricted shell]), opt_restricted=$enableval)
+AC_ARG_ENABLE(select, AC_HELP_STRING([--enable-select], [include select command]), opt_select=$enableval)
+AC_ARG_ENABLE(separate-helpfiles, AC_HELP_STRING([--enable-separate-helpfiles], [use external files for help builtin documentation]), opt_separate_help=$enableval)
+AC_ARG_ENABLE(single-help-strings, AC_HELP_STRING([--enable-single-help-strings], [store help documentation as a single string to ease translation]), opt_single_longdoc_strings=$enableval)
+AC_ARG_ENABLE(strict-posix-default, AC_HELP_STRING([--enable-strict-posix-default], [configure bash to be posix-conformant by default]), opt_strict_posix=$enableval)
+AC_ARG_ENABLE(usg-echo-default, AC_HELP_STRING([--enable-usg-echo-default], [a synonym for --enable-xpg-echo-default]), opt_xpg_echo=$enableval)
+AC_ARG_ENABLE(xpg-echo-default, AC_HELP_STRING([--enable-xpg-echo-default], [make the echo builtin expand escape sequences by default]), opt_xpg_echo=$enableval)
+
+dnl options that alter how bash is compiled and linked
+AC_ARG_ENABLE(mem-scramble, AC_HELP_STRING([--enable-mem-scramble], [scramble memory on calls to malloc and free]), opt_memscramble=$enableval)
+AC_ARG_ENABLE(profiling, AC_HELP_STRING([--enable-profiling], [allow profiling with gprof]), opt_profiling=$enableval)
+AC_ARG_ENABLE(static-link, AC_HELP_STRING([--enable-static-link], [link bash statically, for use as a root shell]), opt_static_link=$enableval)
+
+dnl opt_job_control is handled later, after BASH_JOB_CONTROL_MISSING runs
+
+dnl opt_readline and opt_history are handled later, because AC_PROG_CC needs
+dnl to be run before we can check the version of an already-installed readline
+dnl library
+
+if test $opt_alias = yes; then
+AC_DEFINE(ALIAS)
+fi
+if test $opt_dirstack = yes; then
+AC_DEFINE(PUSHD_AND_POPD)
+fi
+if test $opt_restricted = yes; then
+AC_DEFINE(RESTRICTED_SHELL)
+fi
+if test $opt_process_subst = yes; then
+AC_DEFINE(PROCESS_SUBSTITUTION)
+fi
+if test $opt_prompt_decoding = yes; then
+AC_DEFINE(PROMPT_STRING_DECODE)
+fi
+if test $opt_select = yes; then
+AC_DEFINE(SELECT_COMMAND)
+fi
+if test $opt_help = yes; then
+AC_DEFINE(HELP_BUILTIN)
+fi
+if test $opt_array_variables = yes; then
+AC_DEFINE(ARRAY_VARS)
+fi
+if test $opt_dparen_arith = yes; then
+AC_DEFINE(DPAREN_ARITHMETIC)
+fi
+if test $opt_brace_expansion = yes; then
+AC_DEFINE(BRACE_EXPANSION)
+fi
+if test $opt_disabled_builtins = yes; then
+AC_DEFINE(DISABLED_BUILTINS)
+fi
+if test $opt_command_timing = yes; then
+AC_DEFINE(COMMAND_TIMING)
+fi
+if test $opt_xpg_echo = yes ; then
+AC_DEFINE(DEFAULT_ECHO_TO_XPG)
+fi
+if test $opt_strict_posix = yes; then
+AC_DEFINE(STRICT_POSIX)
+fi
+if test $opt_extended_glob = yes ; then
+AC_DEFINE(EXTENDED_GLOB)
+fi
+if test $opt_extglob_default = yes; then
+AC_DEFINE(EXTGLOB_DEFAULT, 1)
+else
+AC_DEFINE(EXTGLOB_DEFAULT, 0)
+fi
+if test $opt_cond_command = yes ; then
+AC_DEFINE(COND_COMMAND)
+fi
+if test $opt_cond_regexp = yes ; then
+AC_DEFINE(COND_REGEXP)
+fi
+if test $opt_coproc = yes; then
+AC_DEFINE(COPROCESS_SUPPORT)
+fi
+if test $opt_arith_for_command = yes; then
+AC_DEFINE(ARITH_FOR_COMMAND)
+fi
+if test $opt_net_redirs = yes; then
+AC_DEFINE(NETWORK_REDIRECTIONS)
+fi
+if test $opt_progcomp = yes; then
+AC_DEFINE(PROGRAMMABLE_COMPLETION)
+fi
+if test $opt_multibyte = no; then
+AC_DEFINE(NO_MULTIBYTE_SUPPORT)
+fi
+if test $opt_debugger = yes; then
+AC_DEFINE(DEBUGGER)
+fi
+if test $opt_casemod_attrs = yes; then
+AC_DEFINE(CASEMOD_ATTRS)
+fi
+if test $opt_casemod_expansions = yes; then
+AC_DEFINE(CASEMOD_EXPANSIONS)
+fi
+
+if test $opt_memscramble = yes; then
+AC_DEFINE(MEMSCRAMBLE)
+fi
+
+if test "$opt_minimal_config" = yes; then
+ TESTSCRIPT=run-minimal
+else
+ TESTSCRIPT=run-all
+fi
+
+HELPDIR= HELPDIRDEFINE= HELPINSTALL=
+if test "$opt_separate_help" != no; then
+ if test "$opt_separate_help" = "yes" ; then
+ HELPDIR='${datadir}/bash'
+ else
+ HELPDIR=$opt_separate_help
+ fi
+ HELPDIRDEFINE='-H ${HELPDIR}'
+ HELPINSTALL='install-help'
+fi
+HELPSTRINGS=
+if test "$opt_single_longdoc_strings" != "yes"; then
+ HELPSTRINGS='-S'
+fi
+
+dnl now substitute in the values generated by arguments
+AC_SUBST(TESTSCRIPT)
+AC_SUBST(PURIFY)
+AC_SUBST(MALLOC_TARGET)
+AC_SUBST(MALLOC_SRC)
+
+AC_SUBST(MALLOC_LIB)
+AC_SUBST(MALLOC_LIBRARY)
+AC_SUBST(MALLOC_LDFLAGS)
+AC_SUBST(MALLOC_DEP)
+
+AC_SUBST(htmldir)
+
+AC_SUBST(HELPDIR)
+AC_SUBST(HELPDIRDEFINE)
+AC_SUBST(HELPINSTALL)
+AC_SUBST(HELPSTRINGS)
+
+echo ""
+echo "Beginning configuration for bash-$BASHVERS-$RELSTATUS for ${host_cpu}-${host_vendor}-${host_os}"
+echo ""
+
+dnl compilation checks
+dnl AC_PROG_CC sets $cross_compiling to `yes' if cross-compiling for a
+dnl different environment
+AC_PROG_CC
+
+dnl test for Unix variants
+AC_ISC_POSIX
+AC_MINIX
+
+AC_SYS_LARGEFILE
+
+dnl BEGIN changes for cross-building (currently cygwin, minGW, and
+dnl (obsolete) BeOS)
+
+SIGNAMES_O=
+SIGNAMES_H=lsignames.h
+
+dnl load up the cross-building cache file -- add more cases and cache
+dnl files as necessary
+
+dnl Note that host and target machine are the same, and different than the
+dnl build machine.
+dnl Set SIGNAMES_H based on whether or not we're cross-compiling.
+
+CROSS_COMPILE=
+if test "x$cross_compiling" = "xyes"; then
+ case "${host}" in
+ *-cygwin*)
+ cross_cache=${srcdir}/cross-build/cygwin32.cache
+ ;;
+ *-mingw*)
+ cross_cache=${srcdir}/cross-build/cygwin32.cache
+ ;;
+ i[[3456]]86-*-beos*)
+ cross_cache=${srcdir}/cross-build/x86-beos.cache
+ ;;
+ *) echo "configure: cross-compiling for $host is not supported" >&2
+ ;;
+ esac
+ if test -n "${cross_cache}" && test -r "${cross_cache}"; then
+ echo "loading cross-build cache file ${cross_cache}"
+ . ${cross_cache}
+ fi
+ unset cross_cache
+ SIGNAMES_O='signames.o'
+ CROSS_COMPILE='-DCROSS_COMPILING'
+ AC_SUBST(CROSS_COMPILE)
+fi
+AC_SUBST(SIGNAMES_H)
+AC_SUBST(SIGNAMES_O)
+
+if test -z "$CC_FOR_BUILD"; then
+ if test "x$cross_compiling" = "xno"; then
+ CC_FOR_BUILD='$(CC)'
+ else
+ CC_FOR_BUILD=gcc
+ fi
+fi
+AC_SUBST(CC_FOR_BUILD)
+
+dnl END changes for cross-building
+
+dnl We want these before the checks, so the checks can modify their values.
+test -z "$CFLAGS" && CFLAGS=-g auto_cflags=1
+
+dnl If we're using gcc and the user hasn't specified CFLAGS, add -O2 to CFLAGS.
+test -n "$GCC" && test -n "$auto_cflags" && CFLAGS="$CFLAGS -O2"
+
+dnl handle options that alter how bash is compiled and linked
+dnl these must come after the test for cc/gcc
+if test "$opt_profiling" = "yes"; then
+ PROFILE_FLAGS=-pg
+ case "$host_os" in
+ solaris2*) ;;
+ *) opt_static_link=yes ;;
+ esac
+ DEBUG= MALLOC_DEBUG=
+fi
+
+if test "$opt_static_link" = yes; then
+ # if we're using gcc, add `-static' to LDFLAGS, except on Solaris >= 2
+ if test -n "$GCC" || test "$ac_cv_prog_gcc" = "yes"; then
+ STATIC_LD="-static"
+ case "$host_os" in
+ solaris2*) ;;
+ *) LDFLAGS="$LDFLAGS -static" ;; # XXX experimental
+ esac
+ fi
+fi
+
+if test "X$cross_compiling" = "Xno"; then
+ CPPFLAGS_FOR_BUILD=${CPPFLAGS_FOR_BUILD-"$CPPFLAGS"}
+ LDFLAGS_FOR_BUILD=${LDFLAGS_FOR_BUILD-'$(LDFLAGS)'}
+else
+ CPPFLAGS_FOR_BUILD=${CPPFLAGS_FOR_BUILD-""}
+ LDFLAGS_FOR_BUILD=${LDFLAGS_FOR_BUILD-""}
+fi
+
+test -z "$CFLAGS_FOR_BUILD" && CFLAGS_FOR_BUILD="-g"
+
+AC_SUBST(CFLAGS)
+AC_SUBST(CPPFLAGS)
+AC_SUBST(LDFLAGS)
+AC_SUBST(STATIC_LD)
+
+AC_SUBST(CFLAGS_FOR_BUILD)
+AC_SUBST(CPPFLAGS_FOR_BUILD)
+AC_SUBST(LDFLAGS_FOR_BUILD)
+
+AC_PROG_GCC_TRADITIONAL
+
+dnl BEGIN READLINE and HISTORY LIBRARY SECTION
+dnl prepare to allow bash to be linked against an already-installed readline
+
+dnl first test that the readline version is new enough to link bash against
+if test "$opt_readline" = yes && test "$opt_with_installed_readline" != "no"
+then
+ # If the user specified --with-installed-readline=PREFIX and PREFIX
+ # is not `yes', set ac_cv_rl_prefix to PREFIX
+ test $opt_with_installed_readline != "yes" && ac_cv_rl_prefix=$opt_with_installed_readline
+
+ RL_LIB_READLINE_VERSION
+
+ case "$ac_cv_rl_version" in
+ 5*|6*|7*|8*|9*) ;;
+ *) opt_with_installed_readline=no
+ AC_MSG_WARN([installed readline library is too old to be linked with bash])
+ AC_MSG_WARN([using private bash version])
+ ;;
+ esac
+fi
+
+TILDE_LIB=-ltilde
+if test $opt_readline = yes; then
+ AC_DEFINE(READLINE)
+ if test "$opt_with_installed_readline" != "no" ; then
+ case "$opt_with_installed_readline" in
+ yes) RL_INCLUDE= ;;
+ *) case "$RL_INCLUDEDIR" in
+ /usr/include) ;;
+ *) RL_INCLUDE='-I${RL_INCLUDEDIR}' ;;
+ esac
+ ;;
+ esac
+ READLINE_DEP=
+ READLINE_LIB=-lreadline
+ # section for OS versions that don't allow unresolved symbols
+ # to be compiled into dynamic libraries.
+ case "$host_os" in
+ cygwin*) TILDE_LIB= ;;
+ esac
+ else
+ RL_LIBDIR='$(dot)/$(LIBSUBDIR)/readline'
+ READLINE_DEP='$(READLINE_LIBRARY)'
+ # section for OS versions that ship an older/broken version of
+ # readline as a standard dynamic library and don't allow a
+ # static version specified as -llibname to override the
+ # dynamic version
+ case "${host_os}" in
+ darwin[[89]]*|darwin10*) READLINE_LIB='${READLINE_LIBRARY}' ;;
+ *) READLINE_LIB=-lreadline ;;
+ esac
+ fi
+else
+ RL_LIBDIR='$(dot)/$(LIBSUBDIR)/readline'
+ READLINE_LIB= READLINE_DEP=
+fi
+if test $opt_history = yes || test $opt_bang_history = yes; then
+ if test $opt_history = yes; then
+ AC_DEFINE(HISTORY)
+ fi
+ if test $opt_bang_history = yes; then
+ AC_DEFINE(BANG_HISTORY)
+ fi
+ if test "$opt_with_installed_readline" != "no"; then
+ HIST_LIBDIR=$RL_LIBDIR
+ HISTORY_DEP=
+ HISTORY_LIB=-lhistory
+ case "$opt_with_installed_readline" in
+ yes) RL_INCLUDE= ;;
+ *) case "$RL_INCLUDEDIR" in
+ /usr/include) ;;
+ *) RL_INCLUDE='-I${RL_INCLUDEDIR}' ;;
+ esac
+ ;;
+ esac
+ else
+ HIST_LIBDIR='$(dot)/$(LIBSUBDIR)/readline'
+ HISTORY_DEP='$(HISTORY_LIBRARY)'
+ # section for OS versions that ship an older version of
+ # readline as a standard dynamic library and don't allow a
+ # static version specified as -llibname to override the
+ # dynamic version
+ case "${host_os}" in
+ darwin[[89]]*|darwin10*) HISTORY_LIB='${HISTORY_LIBRARY}' ;;
+ *) HISTORY_LIB=-lhistory ;;
+ esac
+ fi
+else
+ HIST_LIBDIR='$(dot)/$(LIBSUBDIR)/readline'
+ HISTORY_LIB= HISTORY_DEP=
+fi
+AC_SUBST(READLINE_LIB)
+AC_SUBST(READLINE_DEP)
+AC_SUBST(RL_LIBDIR)
+AC_SUBST(RL_INCLUDEDIR)
+AC_SUBST(RL_INCLUDE)
+AC_SUBST(HISTORY_LIB)
+AC_SUBST(HISTORY_DEP)
+AC_SUBST(HIST_LIBDIR)
+AC_SUBST(TILDE_LIB)
+
+dnl END READLINE and HISTORY LIBRARY SECTION
+
+dnl programs needed by the build and install process
+AC_PROG_INSTALL
+AC_CHECK_PROG(AR, ar, , ar)
+dnl Set default for ARFLAGS, since autoconf does not have a macro for it.
+dnl This allows people to set it when running configure or make
+test -n "$ARFLAGS" || ARFLAGS="cr"
+AC_PROG_RANLIB
+AC_PROG_YACC
+AC_PROG_MAKE_SET
+
+case "$host_os" in
+opennt*|interix*) MAKE_SHELL="$INTERIX_ROOT/bin/sh" ;;
+*) MAKE_SHELL=/bin/sh ;;
+esac
+AC_SUBST(MAKE_SHELL)
+
+dnl this is similar to the expanded AC_PROG_RANLIB
+if test x$SIZE = x; then
+ if test x$ac_tool_prefix = x; then
+ SIZE=size
+ else
+ SIZE=${ac_tool_prefix}size
+ save_IFS=$IFS ; IFS=:
+ size_found=0
+ for dir in $PATH; do
+ if test -x $dir/$SIZE ; then
+ size_found=1
+ break
+ fi
+ done
+ if test $size_found -eq 0; then
+ SIZE=:
+ fi
+ IFS=$save_IFS
+ fi
+fi
+AC_SUBST(SIZE)
+
+dnl Turn on any extensions available in the GNU C library.
+AC_DEFINE(_GNU_SOURCE, 1)
+
+dnl C compiler characteristics
+AC_C_CONST
+AC_C_INLINE
+AC_C_BIGENDIAN
+AC_C_STRINGIZE
+AC_C_LONG_DOUBLE
+AC_C_PROTOTYPES
+AC_C_CHAR_UNSIGNED
+AC_C_VOLATILE
+AC_C_RESTRICT
+
+dnl initialize GNU gettext
+AM_GNU_GETTEXT([no-libtool], [need-ngettext], [lib/intl])
+
+dnl header files
+AC_HEADER_DIRENT
+AC_HEADER_TIME
+
+BASH_HEADER_INTTYPES
+
+AC_CHECK_HEADERS(unistd.h stdlib.h stdarg.h varargs.h limits.h string.h \
+ memory.h locale.h termcap.h termio.h termios.h dlfcn.h \
+ stddef.h stdint.h netdb.h pwd.h grp.h strings.h regex.h \
+ syslog.h ulimit.h)
+AC_CHECK_HEADERS(sys/pte.h sys/stream.h sys/select.h sys/file.h \
+ sys/resource.h sys/param.h sys/socket.h sys/stat.h \
+ sys/time.h sys/times.h sys/types.h sys/wait.h)
+AC_CHECK_HEADERS(netinet/in.h arpa/inet.h)
+
+dnl sys/ptem.h requires definitions from sys/stream.h on systems where it
+dnl exists
+AC_CHECK_HEADER(sys/ptem.h, , ,[[
+#if HAVE_SYS_STREAM_H
+# include <sys/stream.h>
+#endif
+]])
+
+dnl special checks for libc functions
+AC_FUNC_ALLOCA
+AC_FUNC_GETPGRP
+AC_FUNC_SETVBUF_REVERSED
+AC_FUNC_VPRINTF
+AC_FUNC_STRCOLL
+
+dnl if we're not using the bash malloc but require the C alloca, set things
+dnl up to build a libmalloc.a containing only alloca.o
+
+if test "$ac_cv_func_alloca_works" = "no" && test "$opt_bash_malloc" = "no"; then
+ MALLOC_TARGET=alloca
+ MALLOC_SRC=alloca.c
+
+ MALLOC_LIB='-lmalloc'
+ MALLOC_LIBRARY='$(ALLOC_LIBDIR)/libmalloc.a'
+ MALLOC_LDFLAGS='-L$(ALLOC_LIBDIR)'
+ MALLOC_DEP='$(MALLOC_LIBRARY)'
+fi
+
+dnl if vprintf is not in libc, see if it's defined in stdio.h
+if test "$ac_cv_func_vprintf" = no; then
+ AC_MSG_CHECKING(for declaration of vprintf in stdio.h)
+ AC_EGREP_HEADER([[int[ ]*vprintf[^a-zA-Z0-9]]],stdio.h,ac_cv_func_vprintf=yes)
+ AC_MSG_RESULT($ac_cv_func_vprintf)
+ if test $ac_cv_func_vprintf = yes; then
+ AC_DEFINE(HAVE_VPRINTF)
+ fi
+fi
+
+if test "$ac_cv_func_vprintf" = no && test "$ac_cv_func__doprnt" = "yes"; then
+ AC_LIBOBJ(vprint)
+fi
+
+dnl signal stuff
+AC_TYPE_SIGNAL
+
+dnl checks for certain version-specific system calls and libc functions
+AC_CHECK_FUNC(__setostype, AC_DEFINE(HAVE_SETOSTYPE))
+AC_CHECK_FUNC(wait3, AC_DEFINE(HAVE_WAIT3))
+AC_CHECK_FUNC(isinf, AC_DEFINE(HAVE_ISINF_IN_LIBC))
+AC_CHECK_FUNC(isnan, AC_DEFINE(HAVE_ISNAN_IN_LIBC))
+
+dnl checks for missing libc functions
+AC_CHECK_FUNC(mkfifo,AC_DEFINE(HAVE_MKFIFO),AC_DEFINE(MKFIFO_MISSING))
+
+dnl checks for system calls
+AC_CHECK_FUNCS(dup2 eaccess fcntl getdtablesize getgroups gethostname \
+ getpagesize getpeername getrlimit getrusage gettimeofday \
+ kill killpg lstat readlink sbrk select setdtablesize \
+ setitimer tcgetpgrp uname ulimit waitpid)
+AC_REPLACE_FUNCS(rename)
+
+dnl checks for c library functions
+AC_CHECK_FUNCS(bcopy bzero confstr fnmatch \
+ getaddrinfo gethostbyname getservbyname getservent inet_aton \
+ memmove pathconf putenv raise regcomp regexec \
+ setenv setlinebuf setlocale setvbuf siginterrupt strchr \
+ sysconf syslog tcgetattr times ttyname tzset unsetenv)
+
+AC_CHECK_FUNCS(vasprintf asprintf)
+AC_CHECK_FUNCS(isascii isblank isgraph isprint isspace isxdigit)
+AC_CHECK_FUNCS(getpwent getpwnam getpwuid)
+AC_REPLACE_FUNCS(getcwd memset)
+AC_REPLACE_FUNCS(strcasecmp strcasestr strerror strftime strnlen strpbrk strstr)
+AC_REPLACE_FUNCS(strtod strtol strtoul strtoll strtoull strtoimax strtoumax)
+AC_REPLACE_FUNCS(fdprintf)
+
+AC_CHECK_DECLS([confstr])
+AC_CHECK_DECLS([printf])
+AC_CHECK_DECLS([sbrk])
+AC_CHECK_DECLS([setregid])
+AC_CHECK_DECLS([strcpy])
+AC_CHECK_DECLS([strsignal])
+
+dnl Extra test to detect the horribly broken HP/UX 11.00 strtold(3)
+AC_CHECK_DECLS([strtold], [
+ AC_MSG_CHECKING([for broken strtold])
+ AC_CACHE_VAL(bash_cv_strtold_broken,
+ [AC_TRY_COMPILE(
+ [#include <stdlib.h>],
+ [int main() { long double r; char *foo, bar; r = strtold(foo, &bar);}],
+ bash_cv_strtold_broken=no, bash_cv_strtold_broken=yes,
+ [AC_MSG_WARN(cannot check for broken strtold if cross-compiling, defaulting to no)])
+ ]
+ )
+ AC_MSG_RESULT($bash_cv_strtold_broken)
+ if test "$bash_cv_strtold_broken" = "yes" ; then
+ AC_DEFINE(STRTOLD_BROKEN)
+ fi
+])
+
+
+BASH_CHECK_DECL(strtoimax)
+BASH_CHECK_DECL(strtol)
+BASH_CHECK_DECL(strtoll)
+BASH_CHECK_DECL(strtoul)
+BASH_CHECK_DECL(strtoull)
+BASH_CHECK_DECL(strtoumax)
+
+AC_FUNC_MKTIME
+
+dnl
+dnl Checks for lib/intl and related code (uses some of the output from
+dnl AM_GNU_GETTEXT)
+dnl
+
+AC_CHECK_HEADERS([argz.h errno.h fcntl.h malloc.h stdio_ext.h])
+
+dnl AC_FUNC_MALLOC
+AC_FUNC_MMAP
+AC_CHECK_FUNCS([__argz_count __argz_next __argz_stringify dcgettext mempcpy \
+ munmap stpcpy strcspn strdup])
+
+INTL_DEP= INTL_INC= LIBINTL_H=
+if test "x$USE_INCLUDED_LIBINTL" = "xyes"; then
+ INTL_DEP='${INTL_LIBDIR}/libintl.a'
+ INTL_INC='-I${INTL_LIBSRC} -I${INTL_BUILDDIR}'
+ LIBINTL_H='${INTL_BUILDDIR}/libintl.h'
+fi
+AC_SUBST(INTL_DEP)
+AC_SUBST(INTL_INC)
+AC_SUBST(LIBINTL_H)
+
+dnl
+dnl End of checks needed by files in lib/intl
+dnl
+
+BASH_CHECK_MULTIBYTE
+
+dnl checks for the dynamic loading library functions in libc and libdl
+if test "$opt_static_link" != yes; then
+AC_CHECK_LIB(dl, dlopen)
+AC_CHECK_FUNCS(dlopen dlclose dlsym)
+fi
+
+dnl this defines HAVE_DECL_SYS_SIGLIST
+AC_DECL_SYS_SIGLIST
+
+dnl network functions -- check for inet_aton again
+if test "$ac_cv_func_inet_aton" != 'yes'; then
+BASH_FUNC_INET_ATON
+fi
+
+dnl libraries
+dnl this is reportedly no longer necessary for irix[56].?
+case "$host_os" in
+irix4*) AC_CHECK_LIB(sun, getpwent) ;;
+esac
+
+dnl check for getpeername in the socket library only if it's not in libc
+if test "$ac_cv_func_getpeername" = no; then
+ BASH_CHECK_LIB_SOCKET
+fi
+dnl check for gethostbyname in socket libraries if it's not in libc
+if test "$ac_cv_func_gethostbyname" = no; then
+ BASH_FUNC_GETHOSTBYNAME
+fi
+
+dnl system types
+AC_TYPE_GETGROUPS
+AC_TYPE_OFF_T
+AC_TYPE_MODE_T
+AC_TYPE_UID_T
+AC_TYPE_PID_T
+AC_TYPE_SIZE_T
+AC_CHECK_TYPE(ssize_t, int)
+AC_CHECK_TYPE(time_t, long)
+
+BASH_TYPE_LONG_LONG
+BASH_TYPE_UNSIGNED_LONG_LONG
+
+AC_TYPE_SIGNAL
+BASH_TYPE_SIG_ATOMIC_T
+
+AC_CHECK_SIZEOF(char, 1)
+AC_CHECK_SIZEOF(short, 2)
+AC_CHECK_SIZEOF(int, 4)
+AC_CHECK_SIZEOF(long, 4)
+AC_CHECK_SIZEOF(char *, 4)
+AC_CHECK_SIZEOF(double, 8)
+AC_CHECK_SIZEOF([long long], 8)
+
+AC_CHECK_TYPE(u_int, [unsigned int])
+AC_CHECK_TYPE(u_long, [unsigned long])
+
+BASH_TYPE_BITS16_T
+BASH_TYPE_U_BITS16_T
+BASH_TYPE_BITS32_T
+BASH_TYPE_U_BITS32_T
+BASH_TYPE_BITS64_T
+
+BASH_TYPE_PTRDIFF_T
+
+dnl structures
+AC_HEADER_STAT
+
+dnl system services
+AC_SYS_INTERPRETER
+if test $ac_cv_sys_interpreter = yes; then
+AC_DEFINE(HAVE_HASH_BANG_EXEC)
+fi
+
+dnl Miscellaneous Bash tests
+if test "$ac_cv_func_lstat" = "no"; then
+BASH_FUNC_LSTAT
+fi
+
+dnl behavior of system calls and library functions
+BASH_FUNC_CTYPE_NONASCII
+BASH_FUNC_DUP2_CLOEXEC_CHECK
+BASH_SYS_PGRP_SYNC
+BASH_SYS_SIGNAL_VINTAGE
+
+dnl checking for the presence of certain library symbols
+BASH_SYS_ERRLIST
+BASH_SYS_SIGLIST
+BASH_UNDER_SYS_SIGLIST
+
+dnl various system types
+BASH_TYPE_SIGHANDLER
+BASH_CHECK_TYPE(clock_t, [#include <sys/times.h>], long)
+BASH_CHECK_TYPE(sigset_t, [#include <signal.h>], int)
+BASH_CHECK_TYPE(quad_t, , long, HAVE_QUAD_T)
+BASH_CHECK_TYPE(intmax_t, , $bash_cv_type_long_long)
+BASH_CHECK_TYPE(uintmax_t, , $bash_cv_type_unsigned_long_long)
+if test "$ac_cv_header_sys_socket_h" = "yes"; then
+BASH_CHECK_TYPE(socklen_t, [#include <sys/socket.h>], int, HAVE_SOCKLEN_T)
+fi
+BASH_TYPE_RLIMIT
+
+dnl presence and contents of structures used by system calls
+BASH_STRUCT_TERMIOS_LDISC
+BASH_STRUCT_TERMIO_LDISC
+BASH_STRUCT_DIRENT_D_INO
+BASH_STRUCT_DIRENT_D_FILENO
+BASH_STRUCT_DIRENT_D_NAMLEN
+BASH_STRUCT_WINSIZE
+BASH_STRUCT_TIMEVAL
+AC_CHECK_MEMBERS([struct stat.st_blocks])
+AC_STRUCT_TM
+AC_STRUCT_TIMEZONE
+BASH_STRUCT_TIMEZONE
+
+dnl presence and behavior of C library functions
+BASH_FUNC_STRSIGNAL
+BASH_FUNC_OPENDIR_CHECK
+BASH_FUNC_ULIMIT_MAXFDS
+BASH_FUNC_FPURGE
+BASH_FUNC_GETENV
+if test "$ac_cv_func_getcwd" = "yes"; then
+BASH_FUNC_GETCWD
+fi
+BASH_FUNC_POSIX_SETJMP
+BASH_FUNC_STRCOLL
+BASH_FUNC_SNPRINTF
+BASH_FUNC_VSNPRINTF
+
+dnl If putenv or unsetenv is not present, set the right define so the
+dnl prototype and declaration in lib/sh/getenv.c will be standard-conformant
+
+if test "$ac_cv_func_putenv" = "yes"; then
+BASH_FUNC_STD_PUTENV
+else
+AC_DEFINE(HAVE_STD_PUTENV)
+fi
+if test "$ac_cv_func_unsetenv" = "yes"; then
+BASH_FUNC_STD_UNSETENV
+else
+AC_DEFINE(HAVE_STD_UNSETENV)
+fi
+
+BASH_FUNC_PRINTF_A_FORMAT
+
+dnl presence and behavior of OS functions
+BASH_SYS_REINSTALL_SIGHANDLERS
+BASH_SYS_JOB_CONTROL_MISSING
+BASH_SYS_NAMED_PIPES
+
+dnl presence of certain CPP defines
+AC_HEADER_TIOCGWINSZ
+BASH_HAVE_TIOCSTAT
+BASH_HAVE_FIONREAD
+
+BASH_CHECK_WCONTINUED
+
+dnl miscellaneous
+BASH_CHECK_SPEED_T
+BASH_CHECK_GETPW_FUNCS
+BASH_CHECK_RTSIGS
+BASH_CHECK_SYS_SIGLIST
+
+dnl special checks
+case "$host_os" in
+hpux*) BASH_CHECK_KERNEL_RLIMIT ;;
+esac
+
+if test "$opt_readline" = yes; then
+dnl yuck
+case "$host_os" in
+aix*) prefer_curses=yes ;;
+esac
+BASH_CHECK_LIB_TERMCAP
+fi
+AC_SUBST(TERMCAP_LIB)
+AC_SUBST(TERMCAP_DEP)
+
+BASH_CHECK_DEV_FD
+BASH_CHECK_DEV_STDIN
+BASH_SYS_DEFAULT_MAIL_DIR
+
+if test "$bash_cv_job_control_missing" = missing; then
+ opt_job_control=no
+fi
+
+if test "$opt_job_control" = yes; then
+AC_DEFINE(JOB_CONTROL)
+JOBS_O=jobs.o
+else
+JOBS_O=nojobs.o
+fi
+
+AC_SUBST(JOBS_O)
+
+dnl Defines that we want to propagate to the Makefiles in subdirectories,
+dnl like glob and readline
+
+LOCAL_DEFS=-DSHELL
+
+dnl use this section to possibly define more cpp variables, specify local
+dnl libraries, and specify any additional local cc or ld flags
+dnl
+dnl this should really go away someday
+
+case "${host_os}" in
+sysv4.2*) AC_DEFINE(SVR4_2)
+ AC_DEFINE(SVR4) ;;
+sysv4*) AC_DEFINE(SVR4) ;;
+sysv5*) AC_DEFINE(SVR5) ;;
+hpux9*) LOCAL_CFLAGS="-DHPUX9 -DHPUX" ;;
+hpux*) LOCAL_CFLAGS=-DHPUX ;;
+dgux*) LOCAL_CFLAGS=-D_DGUX_SOURCE; LOCAL_LIBS=-ldgc ;;
+isc*) LOCAL_CFLAGS=-Disc386 ;;
+rhapsody*) LOCAL_CFLAGS=-DRHAPSODY ;;
+darwin*) LOCAL_CFLAGS=-DMACOSX ;;
+sco3.2v5*) LOCAL_CFLAGS="-b elf -DWAITPID_BROKEN -DPATH_MAX=1024" ;;
+sco3.2v4*) LOCAL_CFLAGS="-DMUST_UNBLOCK_CHLD -DPATH_MAX=1024" ;;
+sco3.2*) LOCAL_CFLAGS=-DMUST_UNBLOCK_CHLD ;;
+sunos4*) LOCAL_CFLAGS=-DSunOS4 ;;
+solaris2.5*) LOCAL_CFLAGS="-DSunOS5 -DSOLARIS" ;;
+solaris2.8*) LOCAL_CFLAGS=-DSOLARIS ;;
+solaris2.9*) LOCAL_CFLAGS=-DSOLARIS ;;
+solaris2.10*) LOCAL_CFLAGS=-DSOLARIS ;;
+solaris2*) LOCAL_CFLAGS=-DSOLARIS ;;
+lynxos*) LOCAL_CFLAGS=-DRECYCLES_PIDS ;;
+linux*) LOCAL_LDFLAGS=-rdynamic # allow dynamic loading
+ case "`uname -r`" in
+ 2.[[456789]]*|3*) AC_DEFINE(PGRP_PIPE) ;;
+ esac ;;
+*qnx6*) LOCAL_CFLAGS="-Dqnx -Dqnx6" LOCAL_LIBS="-lncurses" ;;
+*qnx*) LOCAL_CFLAGS="-Dqnx -F -3s" LOCAL_LDFLAGS="-3s" LOCAL_LIBS="-lunix -lncurses" ;;
+powerux*) LOCAL_LIBS="-lgen" ;;
+cygwin*) LOCAL_CFLAGS=-DRECYCLES_PIDS ;;
+opennt*|interix*) LOCAL_CFLAGS="-DNO_MAIN_ENV_ARG -DBROKEN_DIRENT_D_INO -D_POSIX_SOURCE -D_ALL_SOURCE" ;;
+esac
+
+dnl Stanza for OS/compiler pair-specific flags
+case "${host_os}-${CC}" in
+aix4.2*-*gcc*) LOCAL_LDFLAGS="-Xlinker -bexpall -Xlinker -brtl" ;;
+aix4.2*) LOCAL_LDFLAGS="-bexpall -brtl" ;;
+bsdi4*-*gcc*) LOCAL_LDFLAGS="-rdynamic" ;; # allow dynamic loading, like Linux
+esac
+
+dnl FreeBSD-3.x can have either a.out or ELF
+case "${host_os}" in
+freebsd[[3-9]]*)
+ if test -x /usr/bin/objformat && test "`/usr/bin/objformat`" = "elf" ; then
+ LOCAL_LDFLAGS=-rdynamic # allow dynamic loading
+ fi ;;
+freebsdelf*) LOCAL_LDFLAGS=-rdynamic ;; # allow dynamic loading
+dragonfly*) LOCAL_LDFLAGS=-rdynamic ;; # allow dynamic loading
+esac
+
+case "$host_cpu" in
+*cray*) LOCAL_CFLAGS="-DCRAY" ;; # shell var so config.h can use it
+esac
+
+case "$host_cpu-$host_os" in
+ibmrt-*bsd4*) LOCAL_CFLAGS="-ma -U__STDC__" ;;
+esac
+
+case "$host_cpu-$host_vendor-$host_os" in
+m88k-motorola-sysv3) LOCAL_CFLAGS=-DWAITPID_BROKEN ;;
+mips-pyramid-sysv4) LOCAL_CFLAGS=-Xa ;;
+esac
+
+#
+# Shared object configuration section. These values are generated by
+# ${srcdir}/support/shobj-conf
+#
+if test "$ac_cv_func_dlopen" = "yes" && test -f ${srcdir}/support/shobj-conf
+then
+ AC_MSG_CHECKING(shared object configuration for loadable builtins)
+ eval `${CONFIG_SHELL-/bin/sh} ${srcdir}/support/shobj-conf -C "${CC}" -c "${host_cpu}" -o "${host_os}" -v "${host_vendor}"`
+ AC_SUBST(SHOBJ_CC)
+ AC_SUBST(SHOBJ_CFLAGS)
+ AC_SUBST(SHOBJ_LD)
+ AC_SUBST(SHOBJ_LDFLAGS)
+ AC_SUBST(SHOBJ_XLDFLAGS)
+ AC_SUBST(SHOBJ_LIBS)
+ AC_SUBST(SHOBJ_STATUS)
+ AC_MSG_RESULT($SHOBJ_STATUS)
+fi
+
+# try to create a directory tree if the source is elsewhere
+# this should be packaged into a script accessible via ${srcdir}/support
+case "$srcdir" in
+.) ;;
+*) for d in doc tests support lib examples; do # dirs
+ test -d $d || mkdir $d
+ done
+ for ld in readline glob tilde malloc sh termcap; do # libdirs
+ test -d lib/$ld || mkdir lib/$ld
+ done
+ test -d examples/loadables || mkdir examples/loadables # loadable builtins
+ test -d examples/loadables/perl || mkdir examples/loadables/perl
+ ;;
+esac
+
+BUILD_DIR=`pwd`
+case "$BUILD_DIR" in
+*\ *) BUILD_DIR=`echo "$BUILD_DIR" | sed 's: :\\\\ :g'` ;;
+*) ;;
+esac
+
+if test -z "$localedir"; then
+ localedir='${datarootdir}/locale'
+fi
+if test -z "$datarootdir"; then
+ datarootdir='${prefix}/share'
+fi
+
+AC_SUBST(PROFILE_FLAGS)
+
+AC_SUBST(incdir)
+AC_SUBST(BUILD_DIR)
+
+# Some versions of autoconf don't substitute these automatically
+AC_SUBST(datarootdir)
+AC_SUBST(localedir)
+
+AC_SUBST(YACC)
+AC_SUBST(AR)
+AC_SUBST(ARFLAGS)
+
+AC_SUBST(BASHVERS)
+AC_SUBST(RELSTATUS)
+AC_SUBST(DEBUG)
+AC_SUBST(MALLOC_DEBUG)
+
+AC_SUBST(host_cpu)
+AC_SUBST(host_vendor)
+AC_SUBST(host_os)
+
+AC_SUBST(LOCAL_LIBS)
+AC_SUBST(LOCAL_CFLAGS)
+AC_SUBST(LOCAL_LDFLAGS)
+AC_SUBST(LOCAL_DEFS)
+
+#AC_SUBST(ALLOCA_SOURCE)
+#AC_SUBST(ALLOCA_OBJECT)
+
+AC_OUTPUT([Makefile builtins/Makefile lib/readline/Makefile lib/glob/Makefile \
+ lib/intl/Makefile \
+ lib/malloc/Makefile lib/sh/Makefile lib/termcap/Makefile \
+ lib/tilde/Makefile doc/Makefile support/Makefile po/Makefile.in \
+ examples/loadables/Makefile examples/loadables/perl/Makefile],
+[
+# Makefile uses this timestamp file to record whether config.h is up to date.
+echo timestamp > stamp-h
+])
The maximum number of processes available to a single user
.TP
.B \-v
-The maximum amount of virtual memory available to the shell
+The maximum amount of virtual memory available to the shell and, on
+some systems, to its children
.TP
.B \-x
The maximum number of file locks
The maximum number of processes available to a single user.
@item -v
-The maximum amount of virtual memory available to the process.
+The maximum amount of virtual memory available to the shell, and, on
+some systems, to its children.
@item -x
The maximum number of file locks.
highest precedence. */
#define EXP_HIGHEST expcomma
+#ifndef MAX_INT_LEN
+# define MAX_INT_LEN 32
+#endif
+
+struct lvalue
+{
+ char *tokstr; /* possibly-rewritten lvalue if not NULL */
+ intmax_t tokval; /* expression evaluated value */
+ SHELL_VAR *tokvar; /* variable described by array or var reference */
+ intmax_t ind; /* array index if not -1 */
+};
+
+/* A structure defining a single expression context. */
+typedef struct {
+ int curtok, lasttok;
+ char *expression, *tp, *lasttp;
+ intmax_t tokval;
+ char *tokstr;
+ int noeval;
+ struct lvalue lval;
+} EXPR_CONTEXT;
+
static char *expression; /* The current expression */
static char *tp; /* token lexical position */
static char *lasttp; /* pointer to last token position */
static int noeval; /* set to 1 if no assignment to be done */
static procenv_t evalbuf;
+static struct lvalue curlval = {0, 0, 0, -1};
+static struct lvalue lastlval = {0, 0, 0, -1};
+
static int _is_arithop __P((int));
static void readtok __P((void)); /* lexical analyzer */
-static intmax_t expr_streval __P((char *, int));
+static void init_lvalue __P((struct lvalue *));
+static struct lvalue *alloc_lvalue __P((void));
+static void free_lvalue __P((struct lvalue *));
+
+static intmax_t expr_streval __P((char *, int, struct lvalue *));
static intmax_t strlong __P((char *));
static void evalerror __P((const char *));
static void popexp __P((void));
static void expr_unwind __P((void));
static void expr_bind_variable __P((char *, char *));
+static void expr_bind_array_element __P((char *, arrayind_t, char *));
static intmax_t subexpr __P((char *));
static intmax_t exp1 __P((void));
static intmax_t exp0 __P((void));
-/* A structure defining a single expression context. */
-typedef struct {
- int curtok, lasttok;
- char *expression, *tp, *lasttp;
- intmax_t tokval;
- char *tokstr;
- int noeval;
-} EXPR_CONTEXT;
-
-#ifdef INCLUDE_UNUSED
-/* Not used yet. */
-typedef struct {
- char *tokstr;
- intmax_t tokval;
-} LVALUE;
-#endif
-
/* Global var which contains the stack of expression contexts. */
static EXPR_CONTEXT **expr_stack;
static int expr_depth; /* Location in the stack. */
(X)->tokval = tokval; \
(X)->tokstr = tokstr; \
(X)->noeval = noeval; \
+ (X)->lval = curlval; \
} while (0)
#define RESTORETOK(X) \
tokval = (X)->tokval; \
tokstr = (X)->tokstr; \
noeval = (X)->noeval; \
+ curlval = (X)->lval; \
} while (0)
/* Push and save away the contents of the globals describing the
stupidly_hack_special_variables (lhs);
}
+/* Rewrite tok, which is of the form vname[expression], to vname[ind], where
+ IND is the already-calculated value of expression. */
+static void
+expr_bind_array_element (tok, ind, rhs)
+ char *tok;
+ arrayind_t ind;
+ char *rhs;
+{
+ char *lhs, *vname;
+ size_t llen;
+ char ibuf[INT_STRLEN_BOUND (arrayind_t) + 1], *istr;
+
+ istr = fmtumax (ind, 10, ibuf, sizeof (ibuf), 0);
+ vname = array_variable_name (tok, (char **)NULL, (int *)NULL);
+
+ llen = strlen (vname) + sizeof (ibuf) + 3;
+ lhs = xmalloc (llen);
+
+ sprintf (lhs, "%s[%s]", vname, istr); /* XXX */
+
+ expr_bind_variable (lhs, rhs);
+/*itrace("expr_bind_array_element: %s=%s", lhs, rhs);*/
+ free (vname);
+ free (lhs);
+}
+
/* Evaluate EXPR, and return the arithmetic result. If VALIDP is
non-null, a zero is stored into the location to which it points
if the expression is invalid, non-zero otherwise. If a non-zero
return (0);
pushexp ();
- curtok = lasttok = 0;
expression = savestring (expr);
tp = expression;
+ curtok = lasttok = 0;
tokstr = (char *)NULL;
tokval = 0;
+ init_lvalue (&curlval);
+ lastlval = curlval;
readtok ();
{
register intmax_t value;
char *lhs, *rhs;
+ arrayind_t lind;
value = expcond ();
if (curtok == EQ || curtok == OP_ASSIGN)
}
lhs = savestring (tokstr);
+ /* save ind in case rhs is string var and evaluation overwrites it */
+ lind = curlval.ind;
readtok ();
value = expassign ();
rhs = itos (value);
if (noeval == 0)
- expr_bind_variable (lhs, rhs);
+ {
+ if (lind != -1)
+ expr_bind_array_element (lhs, lind, rhs);
+ else
+ expr_bind_variable (lhs, rhs);
+ }
free (rhs);
free (lhs);
FREE (tokstr);
v2 = tokval + ((stok == PREINC) ? 1 : -1);
vincdec = itos (v2);
if (noeval == 0)
- expr_bind_variable (tokstr, vincdec);
+ {
+ if (curlval.ind != -1)
+ expr_bind_array_element (curlval.tokstr, curlval.ind, vincdec);
+ else
+ expr_bind_variable (tokstr, vincdec);
+ }
free (vincdec);
val = v2;
/* restore certain portions of EC */
tokstr = ec.tokstr;
noeval = ec.noeval;
+ curlval = ec.lval;
lasttok = STR; /* ec.curtok */
v2 = val + ((stok == POSTINC) ? 1 : -1);
vincdec = itos (v2);
if (noeval == 0)
- expr_bind_variable (tokstr, vincdec);
+ {
+ if (curlval.ind != -1)
+ expr_bind_array_element (curlval.tokstr, curlval.ind, vincdec);
+ else
+ expr_bind_variable (tokstr, vincdec);
+ }
free (vincdec);
curtok = NUM; /* make sure x++=7 is flagged as an error */
}
return (val);
}
+static void
+init_lvalue (lv)
+ struct lvalue *lv;
+{
+ lv->tokstr = 0;
+ lv->tokvar = 0;
+ lv->tokval = lv->ind = -1;
+}
+
+static struct lvalue *
+alloc_lvalue ()
+{
+ struct lvalue *lv;
+
+ lv = xmalloc (sizeof (struct lvalue));
+ init_lvalue (lv);
+ return (lv);
+}
+
+static void
+free_lvalue (lv)
+ struct lvalue *lv;
+{
+ free (lv); /* should be inlined */
+}
+
static intmax_t
-expr_streval (tok, e)
+expr_streval (tok, e, lvalue)
char *tok;
int e;
+ struct lvalue *lvalue;
{
SHELL_VAR *v;
char *value;
intmax_t tval;
+#if defined (ARRAY_VARS)
+ arrayind_t ind;
+#endif
/* [[[[[ */
#if defined (ARRAY_VARS)
jump_to_top_level (FORCE_EOF);
}
+ ind = -1;
#if defined (ARRAY_VARS)
/* Second argument of 0 to get_array_value means that we don't allow
references like array[@]. In this case, get_array_value is just
like get_variable_value in that it does not return newly-allocated
memory or quote the results. */
- value = (e == ']') ? get_array_value (tok, 0, (int *)NULL) : get_variable_value (v);
+ value = (e == ']') ? get_array_value (tok, 0, (int *)NULL, &ind) : get_variable_value (v);
#else
value = get_variable_value (v);
#endif
tval = (value && *value) ? subexpr (value) : 0;
+ if (lvalue)
+ {
+ lvalue->tokstr = tok; /* XXX */
+ lvalue->tokval = tval;
+ lvalue->tokvar = v; /* XXX */
+ lvalue->ind = ind;
+ }
+
return (tval);
}
register char *cp, *xp;
register unsigned char c, c1;
register int e;
+ struct lvalue lval;
/* Skip leading whitespace. */
cp = tp;
tokstr = savestring (tp);
*cp = c;
+ /* XXX - make peektok part of saved token state? */
SAVETOK (&ec);
tokstr = (char *)NULL; /* keep it from being freed */
tp = savecp = cp;
/* The tests for PREINC and PREDEC aren't strictly correct, but they
preserve old behavior if a construct like --x=9 is given. */
if (lasttok == PREINC || lasttok == PREDEC || peektok != EQ)
- tokval = expr_streval (tokstr, e);
+ {
+ lastlval = curlval;
+ tokval = expr_streval (tokstr, e, &curlval);
+ }
else
tokval = 0;
--- /dev/null
+/* expr.c -- arithmetic expression evaluation. */
+
+/* Copyright (C) 1990-2009 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/>.
+*/
+
+/*
+ All arithmetic is done as intmax_t integers with no checking for overflow
+ (though division by 0 is caught and flagged as an error).
+
+ The following operators are handled, grouped into a set of levels in
+ order of decreasing precedence.
+
+ "id++", "id--" [post-increment and post-decrement]
+ "++id", "--id" [pre-increment and pre-decrement]
+ "-", "+" [(unary operators)]
+ "!", "~"
+ "**" [(exponentiation)]
+ "*", "/", "%"
+ "+", "-"
+ "<<", ">>"
+ "<=", ">=", "<", ">"
+ "==", "!="
+ "&"
+ "^"
+ "|"
+ "&&"
+ "||"
+ "expr ? expr : expr"
+ "=", "*=", "/=", "%=", "+=", "-=", "<<=", ">>=", "&=", "^=", "|="
+ , [comma]
+
+ (Note that most of these operators have special meaning to bash, and an
+ entire expression should be quoted, e.g. "a=$a+1" or "a=a+1" to ensure
+ that it is passed intact to the evaluator when using `let'. When using
+ the $[] or $(( )) forms, the text between the `[' and `]' or `((' and `))'
+ is treated as if in double quotes.)
+
+ Sub-expressions within parentheses have a precedence level greater than
+ all of the above levels and are evaluated first. Within a single prece-
+ dence group, evaluation is left-to-right, except for the arithmetic
+ assignment operator (`='), which is evaluated right-to-left (as in C).
+
+ The expression evaluator returns the value of the expression (assignment
+ statements have as a value what is returned by the RHS). The `let'
+ builtin, on the other hand, returns 0 if the last expression evaluates to
+ a non-zero, and 1 otherwise.
+
+ Implementation is a recursive-descent parser.
+
+ Chet Ramey
+ chet@ins.CWRU.Edu
+*/
+
+#include "config.h"
+
+#include <stdio.h>
+#include "bashansi.h"
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include "chartypes.h"
+#include "bashintl.h"
+
+#include "shell.h"
+
+/* Because of the $((...)) construct, expressions may include newlines.
+ Here is a macro which accepts newlines, tabs and spaces as whitespace. */
+#define cr_whitespace(c) (whitespace(c) || ((c) == '\n'))
+
+/* Size be which the expression stack grows when neccessary. */
+#define EXPR_STACK_GROW_SIZE 10
+
+/* Maximum amount of recursion allowed. This prevents a non-integer
+ variable such as "num=num+2" from infinitely adding to itself when
+ "let num=num+2" is given. */
+#define MAX_EXPR_RECURSION_LEVEL 1024
+
+/* The Tokens. Singing "The Lion Sleeps Tonight". */
+
+#define EQEQ 1 /* "==" */
+#define NEQ 2 /* "!=" */
+#define LEQ 3 /* "<=" */
+#define GEQ 4 /* ">=" */
+#define STR 5 /* string */
+#define NUM 6 /* number */
+#define LAND 7 /* "&&" Logical AND */
+#define LOR 8 /* "||" Logical OR */
+#define LSH 9 /* "<<" Left SHift */
+#define RSH 10 /* ">>" Right SHift */
+#define OP_ASSIGN 11 /* op= expassign as in Posix.2 */
+#define COND 12 /* exp1 ? exp2 : exp3 */
+#define POWER 13 /* exp1**exp2 */
+#define PREINC 14 /* ++var */
+#define PREDEC 15 /* --var */
+#define POSTINC 16 /* var++ */
+#define POSTDEC 17 /* var-- */
+#define EQ '='
+#define GT '>'
+#define LT '<'
+#define PLUS '+'
+#define MINUS '-'
+#define MUL '*'
+#define DIV '/'
+#define MOD '%'
+#define NOT '!'
+#define LPAR '('
+#define RPAR ')'
+#define BAND '&' /* Bitwise AND */
+#define BOR '|' /* Bitwise OR. */
+#define BXOR '^' /* Bitwise eXclusive OR. */
+#define BNOT '~' /* Bitwise NOT; Two's complement. */
+#define QUES '?'
+#define COL ':'
+#define COMMA ','
+
+/* This should be the function corresponding to the operator with the
+ highest precedence. */
+#define EXP_HIGHEST expcomma
+
+#ifndef MAX_INT_LEN
+# define MAX_INT_LEN 32
+#endif
+
+struct lvalue
+{
+ char *tokstr; /* possibly-rewritten lvalue if not NULL */
+ intmax_t tokval; /* expression evaluated value */
+ SHELL_VAR *tokvar; /* variable described by array or var reference */
+ intmax_t ind; /* array index if not -1 */
+};
+
+/* A structure defining a single expression context. */
+typedef struct {
+ int curtok, lasttok;
+ char *expression, *tp, *lasttp;
+ intmax_t tokval;
+ char *tokstr;
+ int noeval;
+ struct lvalue lval;
+} EXPR_CONTEXT;
+
+static char *expression; /* The current expression */
+static char *tp; /* token lexical position */
+static char *lasttp; /* pointer to last token position */
+static int curtok; /* the current token */
+static int lasttok; /* the previous token */
+static int assigntok; /* the OP in OP= */
+static char *tokstr; /* current token string */
+static intmax_t tokval; /* current token value */
+static int noeval; /* set to 1 if no assignment to be done */
+static procenv_t evalbuf;
+
+static struct lvalue curlval = {0, 0, 0, -1};
+static struct lvalue lastlval = {0, 0, 0, -1};
+
+static int _is_arithop __P((int));
+static void readtok __P((void)); /* lexical analyzer */
+
+static intmax_t expr_streval __P((char *, int, struct lvalue *));
+static intmax_t strlong __P((char *));
+static void evalerror __P((const char *));
+
+static void pushexp __P((void));
+static void popexp __P((void));
+static void expr_unwind __P((void));
+static void expr_bind_variable __P((char *, char *));
+static void expr_bind_array_element __P((char *, arrayind_t, char *));
+
+static intmax_t subexpr __P((char *));
+
+static intmax_t expcomma __P((void));
+static intmax_t expassign __P((void));
+static intmax_t expcond __P((void));
+static intmax_t explor __P((void));
+static intmax_t expland __P((void));
+static intmax_t expbor __P((void));
+static intmax_t expbxor __P((void));
+static intmax_t expband __P((void));
+static intmax_t exp5 __P((void));
+static intmax_t exp4 __P((void));
+static intmax_t expshift __P((void));
+static intmax_t exp3 __P((void));
+static intmax_t exp2 __P((void));
+static intmax_t exppower __P((void));
+static intmax_t exp1 __P((void));
+static intmax_t exp0 __P((void));
+
+/* Global var which contains the stack of expression contexts. */
+static EXPR_CONTEXT **expr_stack;
+static int expr_depth; /* Location in the stack. */
+static int expr_stack_size; /* Number of slots already allocated. */
+
+extern char *this_command_name;
+extern int unbound_vars_is_error, last_command_exit_value;
+
+#if defined (ARRAY_VARS)
+extern const char * const bash_badsub_errmsg;
+#endif
+
+#define SAVETOK(X) \
+ do { \
+ (X)->curtok = curtok; \
+ (X)->lasttok = lasttok; \
+ (X)->tp = tp; \
+ (X)->lasttp = lasttp; \
+ (X)->tokval = tokval; \
+ (X)->tokstr = tokstr; \
+ (X)->noeval = noeval; \
+ (X)->lval = curlval; \
+ } while (0)
+
+#define RESTORETOK(X) \
+ do { \
+ curtok = (X)->curtok; \
+ lasttok = (X)->lasttok; \
+ tp = (X)->tp; \
+ lasttp = (X)->lasttp; \
+ tokval = (X)->tokval; \
+ tokstr = (X)->tokstr; \
+ noeval = (X)->noeval; \
+ curlval = (X)->lval; \
+ } while (0)
+
+/* Push and save away the contents of the globals describing the
+ current expression context. */
+static void
+pushexp ()
+{
+ EXPR_CONTEXT *context;
+
+ if (expr_depth >= MAX_EXPR_RECURSION_LEVEL)
+ evalerror (_("expression recursion level exceeded"));
+
+ if (expr_depth >= expr_stack_size)
+ {
+ expr_stack_size += EXPR_STACK_GROW_SIZE;
+ expr_stack = (EXPR_CONTEXT **)xrealloc (expr_stack, expr_stack_size * sizeof (EXPR_CONTEXT *));
+ }
+
+ context = (EXPR_CONTEXT *)xmalloc (sizeof (EXPR_CONTEXT));
+
+ context->expression = expression;
+ SAVETOK(context);
+
+ expr_stack[expr_depth++] = context;
+}
+
+/* Pop the the contents of the expression context stack into the
+ globals describing the current expression context. */
+static void
+popexp ()
+{
+ EXPR_CONTEXT *context;
+
+ if (expr_depth == 0)
+ evalerror (_("recursion stack underflow"));
+
+ context = expr_stack[--expr_depth];
+
+ expression = context->expression;
+ RESTORETOK (context);
+
+ free (context);
+}
+
+static void
+expr_unwind ()
+{
+ while (--expr_depth > 0)
+ {
+ if (expr_stack[expr_depth]->tokstr)
+ free (expr_stack[expr_depth]->tokstr);
+
+ if (expr_stack[expr_depth]->expression)
+ free (expr_stack[expr_depth]->expression);
+
+ free (expr_stack[expr_depth]);
+ }
+ free (expr_stack[expr_depth]); /* free the allocated EXPR_CONTEXT */
+
+ noeval = 0; /* XXX */
+}
+
+static void
+expr_bind_variable (lhs, rhs)
+ char *lhs, *rhs;
+{
+ (void)bind_int_variable (lhs, rhs);
+ stupidly_hack_special_variables (lhs);
+}
+
+/* Rewrite tok, which is of the form vname[expression], to vname[ind], where
+ IND is the already-calculated value of expression. */
+static void
+expr_bind_array_element (tok, ind, rhs)
+ char *tok;
+ arrayind_t ind;
+ char *rhs;
+{
+ char *lhs, *vname;
+ size_t llen;
+ char ibuf[INT_STRLEN_BOUND (arrayind_t) + 1], *istr;
+
+ istr = fmtulong (ind, 10, ibuf, sizeof (ibuf), 0);
+ vname = array_variable_name (tok, (char **)NULL, (int *)NULL);
+
+ llen = strlen (vname) + sizeof (ibuf) + 3;
+ lhs = xmalloc (llen);
+
+ sprintf (lhs, "%s[%s]", vname, istr); /* XXX */
+
+ expr_bind_variable (lhs, rhs);
+/*itrace("expr_bind_array_element: %s=%s", lhs, rhs);*/
+ free (vname);
+ free (lhs);
+}
+
+/* Evaluate EXPR, and return the arithmetic result. If VALIDP is
+ non-null, a zero is stored into the location to which it points
+ if the expression is invalid, non-zero otherwise. If a non-zero
+ value is returned in *VALIDP, the return value of evalexp() may
+ be used.
+
+ The `while' loop after the longjmp is caught relies on the above
+ implementation of pushexp and popexp leaving in expr_stack[0] the
+ values that the variables had when the program started. That is,
+ the first things saved are the initial values of the variables that
+ were assigned at program startup or by the compiler. Therefore, it is
+ safe to let the loop terminate when expr_depth == 0, without freeing up
+ any of the expr_depth[0] stuff. */
+intmax_t
+evalexp (expr, validp)
+ char *expr;
+ int *validp;
+{
+ intmax_t val;
+ int c;
+ procenv_t oevalbuf;
+
+ val = 0;
+ noeval = 0;
+
+ FASTCOPY (evalbuf, oevalbuf, sizeof (evalbuf));
+
+ c = setjmp (evalbuf);
+
+ if (c)
+ {
+ FREE (tokstr);
+ FREE (expression);
+ tokstr = expression = (char *)NULL;
+
+ expr_unwind ();
+
+ if (validp)
+ *validp = 0;
+ return (0);
+ }
+
+ val = subexpr (expr);
+
+ if (validp)
+ *validp = 1;
+
+ FASTCOPY (oevalbuf, evalbuf, sizeof (evalbuf));
+
+ return (val);
+}
+
+static intmax_t
+subexpr (expr)
+ char *expr;
+{
+ intmax_t val;
+ char *p;
+
+ for (p = expr; p && *p && cr_whitespace (*p); p++)
+ ;
+
+ if (p == NULL || *p == '\0')
+ return (0);
+
+ pushexp ();
+ curtok = lasttok = 0;
+ expression = savestring (expr);
+ tp = expression;
+
+ tokstr = (char *)NULL;
+ tokval = 0;
+
+ readtok ();
+
+ val = EXP_HIGHEST ();
+
+ if (curtok != 0)
+ evalerror (_("syntax error in expression"));
+
+ FREE (tokstr);
+ FREE (expression);
+
+ popexp ();
+
+ return val;
+}
+
+static intmax_t
+expcomma ()
+{
+ register intmax_t value;
+
+ value = expassign ();
+ while (curtok == COMMA)
+ {
+ readtok ();
+ value = expassign ();
+ }
+
+ return value;
+}
+
+static intmax_t
+expassign ()
+{
+ register intmax_t value;
+ char *lhs, *rhs;
+ arrayind_t lind;
+
+ value = expcond ();
+ if (curtok == EQ || curtok == OP_ASSIGN)
+ {
+ int special, op;
+ intmax_t lvalue;
+
+ special = curtok == OP_ASSIGN;
+
+ if (lasttok != STR)
+ evalerror (_("attempted assignment to non-variable"));
+
+ if (special)
+ {
+ op = assigntok; /* a OP= b */
+ lvalue = value;
+ }
+
+ lhs = savestring (tokstr);
+ /* save ind in case rhs is string var and evaluation overwrites it */
+ lind = curlval.ind;
+ readtok ();
+ value = expassign ();
+
+ if (special)
+ {
+ switch (op)
+ {
+ case MUL:
+ lvalue *= value;
+ break;
+ case DIV:
+ if (value == 0)
+ evalerror (_("division by 0"));
+ lvalue /= value;
+ break;
+ case MOD:
+ if (value == 0)
+ evalerror (_("division by 0"));
+ lvalue %= value;
+ break;
+ case PLUS:
+ lvalue += value;
+ break;
+ case MINUS:
+ lvalue -= value;
+ break;
+ case LSH:
+ lvalue <<= value;
+ break;
+ case RSH:
+ lvalue >>= value;
+ break;
+ case BAND:
+ lvalue &= value;
+ break;
+ case BOR:
+ lvalue |= value;
+ break;
+ case BXOR:
+ lvalue ^= value;
+ break;
+ default:
+ free (lhs);
+ evalerror (_("bug: bad expassign token"));
+ break;
+ }
+ value = lvalue;
+ }
+
+ rhs = itos (value);
+ if (noeval == 0)
+ {
+ if (lind != -1)
+ expr_bind_array_element (lhs, lind, rhs);
+ else
+ expr_bind_variable (lhs, rhs);
+ }
+ free (rhs);
+ free (lhs);
+ FREE (tokstr);
+ tokstr = (char *)NULL; /* For freeing on errors. */
+ }
+ return (value);
+}
+
+/* Conditional expression (expr?expr:expr) */
+static intmax_t
+expcond ()
+{
+ intmax_t cval, val1, val2, rval;
+ int set_noeval;
+
+ set_noeval = 0;
+ rval = cval = explor ();
+ if (curtok == QUES) /* found conditional expr */
+ {
+ readtok ();
+ if (curtok == 0 || curtok == COL)
+ evalerror (_("expression expected"));
+ if (cval == 0)
+ {
+ set_noeval = 1;
+ noeval++;
+ }
+
+ val1 = EXP_HIGHEST ();
+
+ if (set_noeval)
+ noeval--;
+ if (curtok != COL)
+ evalerror (_("`:' expected for conditional expression"));
+ readtok ();
+ if (curtok == 0)
+ evalerror (_("expression expected"));
+ set_noeval = 0;
+ if (cval)
+ {
+ set_noeval = 1;
+ noeval++;
+ }
+
+ val2 = expcond ();
+ if (set_noeval)
+ noeval--;
+ rval = cval ? val1 : val2;
+ lasttok = COND;
+ }
+ return rval;
+}
+
+/* Logical OR. */
+static intmax_t
+explor ()
+{
+ register intmax_t val1, val2;
+ int set_noeval;
+
+ val1 = expland ();
+
+ while (curtok == LOR)
+ {
+ set_noeval = 0;
+ if (val1 != 0)
+ {
+ noeval++;
+ set_noeval = 1;
+ }
+ readtok ();
+ val2 = expland ();
+ if (set_noeval)
+ noeval--;
+ val1 = val1 || val2;
+ lasttok = LOR;
+ }
+
+ return (val1);
+}
+
+/* Logical AND. */
+static intmax_t
+expland ()
+{
+ register intmax_t val1, val2;
+ int set_noeval;
+
+ val1 = expbor ();
+
+ while (curtok == LAND)
+ {
+ set_noeval = 0;
+ if (val1 == 0)
+ {
+ set_noeval = 1;
+ noeval++;
+ }
+ readtok ();
+ val2 = expbor ();
+ if (set_noeval)
+ noeval--;
+ val1 = val1 && val2;
+ lasttok = LAND;
+ }
+
+ return (val1);
+}
+
+/* Bitwise OR. */
+static intmax_t
+expbor ()
+{
+ register intmax_t val1, val2;
+
+ val1 = expbxor ();
+
+ while (curtok == BOR)
+ {
+ readtok ();
+ val2 = expbxor ();
+ val1 = val1 | val2;
+ }
+
+ return (val1);
+}
+
+/* Bitwise XOR. */
+static intmax_t
+expbxor ()
+{
+ register intmax_t val1, val2;
+
+ val1 = expband ();
+
+ while (curtok == BXOR)
+ {
+ readtok ();
+ val2 = expband ();
+ val1 = val1 ^ val2;
+ }
+
+ return (val1);
+}
+
+/* Bitwise AND. */
+static intmax_t
+expband ()
+{
+ register intmax_t val1, val2;
+
+ val1 = exp5 ();
+
+ while (curtok == BAND)
+ {
+ readtok ();
+ val2 = exp5 ();
+ val1 = val1 & val2;
+ }
+
+ return (val1);
+}
+
+static intmax_t
+exp5 ()
+{
+ register intmax_t val1, val2;
+
+ val1 = exp4 ();
+
+ while ((curtok == EQEQ) || (curtok == NEQ))
+ {
+ int op = curtok;
+
+ readtok ();
+ val2 = exp4 ();
+ if (op == EQEQ)
+ val1 = (val1 == val2);
+ else if (op == NEQ)
+ val1 = (val1 != val2);
+ }
+ return (val1);
+}
+
+static intmax_t
+exp4 ()
+{
+ register intmax_t val1, val2;
+
+ val1 = expshift ();
+ while ((curtok == LEQ) ||
+ (curtok == GEQ) ||
+ (curtok == LT) ||
+ (curtok == GT))
+ {
+ int op = curtok;
+
+ readtok ();
+ val2 = expshift ();
+
+ if (op == LEQ)
+ val1 = val1 <= val2;
+ else if (op == GEQ)
+ val1 = val1 >= val2;
+ else if (op == LT)
+ val1 = val1 < val2;
+ else /* (op == GT) */
+ val1 = val1 > val2;
+ }
+ return (val1);
+}
+
+/* Left and right shifts. */
+static intmax_t
+expshift ()
+{
+ register intmax_t val1, val2;
+
+ val1 = exp3 ();
+
+ while ((curtok == LSH) || (curtok == RSH))
+ {
+ int op = curtok;
+
+ readtok ();
+ val2 = exp3 ();
+
+ if (op == LSH)
+ val1 = val1 << val2;
+ else
+ val1 = val1 >> val2;
+ }
+
+ return (val1);
+}
+
+static intmax_t
+exp3 ()
+{
+ register intmax_t val1, val2;
+
+ val1 = exp2 ();
+
+ while ((curtok == PLUS) || (curtok == MINUS))
+ {
+ int op = curtok;
+
+ readtok ();
+ val2 = exp2 ();
+
+ if (op == PLUS)
+ val1 += val2;
+ else if (op == MINUS)
+ val1 -= val2;
+ }
+ return (val1);
+}
+
+static intmax_t
+exp2 ()
+{
+ register intmax_t val1, val2;
+
+ val1 = exppower ();
+
+ while ((curtok == MUL) ||
+ (curtok == DIV) ||
+ (curtok == MOD))
+ {
+ int op = curtok;
+
+ readtok ();
+
+ val2 = exppower ();
+
+ if (((op == DIV) || (op == MOD)) && (val2 == 0))
+ evalerror (_("division by 0"));
+
+ if (op == MUL)
+ val1 *= val2;
+ else if (op == DIV)
+ val1 /= val2;
+ else if (op == MOD)
+ val1 %= val2;
+ }
+ return (val1);
+}
+
+static intmax_t
+exppower ()
+{
+ register intmax_t val1, val2, c;
+
+ val1 = exp1 ();
+ while (curtok == POWER)
+ {
+ readtok ();
+ val2 = exppower (); /* exponentiation is right-associative */
+ if (val2 == 0)
+ return (1);
+ if (val2 < 0)
+ evalerror (_("exponent less than 0"));
+ for (c = 1; val2--; c *= val1)
+ ;
+ val1 = c;
+ }
+ return (val1);
+}
+
+static intmax_t
+exp1 ()
+{
+ register intmax_t val;
+
+ if (curtok == NOT)
+ {
+ readtok ();
+ val = !exp1 ();
+ }
+ else if (curtok == BNOT)
+ {
+ readtok ();
+ val = ~exp1 ();
+ }
+ else
+ val = exp0 ();
+
+ return (val);
+}
+
+static intmax_t
+exp0 ()
+{
+ register intmax_t val = 0, v2;
+ char *vincdec;
+ int stok;
+ EXPR_CONTEXT ec;
+
+ /* XXX - might need additional logic here to decide whether or not
+ pre-increment or pre-decrement is legal at this point. */
+ if (curtok == PREINC || curtok == PREDEC)
+ {
+ stok = lasttok = curtok;
+ readtok ();
+ if (curtok != STR)
+ /* readtok() catches this */
+ evalerror (_("identifier expected after pre-increment or pre-decrement"));
+
+ v2 = tokval + ((stok == PREINC) ? 1 : -1);
+ vincdec = itos (v2);
+ if (noeval == 0)
+ {
+ if (curlval.ind != -1)
+ expr_bind_array_element (curlval.tokstr, curlval.ind, vincdec);
+ else
+ expr_bind_variable (tokstr, vincdec);
+ }
+ free (vincdec);
+ val = v2;
+
+ curtok = NUM; /* make sure --x=7 is flagged as an error */
+ readtok ();
+ }
+ else if (curtok == MINUS)
+ {
+ readtok ();
+ val = - exp0 ();
+ }
+ else if (curtok == PLUS)
+ {
+ readtok ();
+ val = exp0 ();
+ }
+ else if (curtok == LPAR)
+ {
+ readtok ();
+ val = EXP_HIGHEST ();
+
+ if (curtok != RPAR) /* ( */
+ evalerror (_("missing `)'"));
+
+ /* Skip over closing paren. */
+ readtok ();
+ }
+ else if ((curtok == NUM) || (curtok == STR))
+ {
+ val = tokval;
+ if (curtok == STR)
+ {
+ SAVETOK (&ec);
+ tokstr = (char *)NULL; /* keep it from being freed */
+ noeval = 1;
+ readtok ();
+ stok = curtok;
+
+ /* post-increment or post-decrement */
+ if (stok == POSTINC || stok == POSTDEC)
+ {
+ /* restore certain portions of EC */
+ tokstr = ec.tokstr;
+ noeval = ec.noeval;
+ curlval = ec.lval;
+ lasttok = STR; /* ec.curtok */
+
+ v2 = val + ((stok == POSTINC) ? 1 : -1);
+ vincdec = itos (v2);
+ if (noeval == 0)
+ {
+ if (curlval.ind != -1)
+ expr_bind_array_element (curlval.tokstr, curlval.ind, vincdec);
+ else
+ expr_bind_variable (tokstr, vincdec);
+ }
+ free (vincdec);
+ curtok = NUM; /* make sure x++=7 is flagged as an error */
+ }
+ else
+ {
+ if (stok == STR) /* free new tokstr before old one is restored */
+ FREE (tokstr);
+ RESTORETOK (&ec);
+ }
+
+ }
+
+ readtok ();
+ }
+ else
+ evalerror (_("syntax error: operand expected"));
+
+ return (val);
+}
+
+static struct lvalue *
+alloc_lvalue ()
+{
+ struct lvalue *lv;
+
+ lv = xmalloc (sizeof (struct lvalue));
+ lv->tokstr = 0;
+ lv->tokvar = 0;
+ lv->tokval = lv->ind = -1;
+ return (lv);
+}
+
+static void
+free_lvalue (lv)
+ struct lvalue *lv;
+{
+ free (lv); /* should be inlined */
+}
+
+static intmax_t
+expr_streval (tok, e, lvalue)
+ char *tok;
+ int e;
+ struct lvalue *lvalue;
+{
+ SHELL_VAR *v;
+ char *value;
+ intmax_t tval;
+#if defined (ARRAY_VARS)
+ arrayind_t ind;
+#endif
+
+ /* [[[[[ */
+#if defined (ARRAY_VARS)
+ v = (e == ']') ? array_variable_part (tok, (char **)0, (int *)0) : find_variable (tok);
+#else
+ v = find_variable (tok);
+#endif
+
+ if ((v == 0 || invisible_p (v)) && unbound_vars_is_error)
+ {
+#if defined (ARRAY_VARS)
+ value = (e == ']') ? array_variable_name (tok, (char **)0, (int *)0) : tok;
+#else
+ value = tok;
+#endif
+
+ last_command_exit_value = EXECUTION_FAILURE;
+ err_unboundvar (value);
+
+#if defined (ARRAY_VARS)
+ if (e == ']')
+ FREE (value); /* array_variable_name returns new memory */
+#endif
+
+ if (interactive_shell)
+ {
+ expr_unwind ();
+ top_level_cleanup ();
+ jump_to_top_level (DISCARD);
+ }
+ else
+ jump_to_top_level (FORCE_EOF);
+ }
+
+ ind = -1;
+#if defined (ARRAY_VARS)
+ /* Second argument of 0 to get_array_value means that we don't allow
+ references like array[@]. In this case, get_array_value is just
+ like get_variable_value in that it does not return newly-allocated
+ memory or quote the results. */
+ value = (e == ']') ? get_array_value (tok, 0, (int *)NULL, &ind) : get_variable_value (v);
+#else
+ value = get_variable_value (v);
+#endif
+
+ tval = (value && *value) ? subexpr (value) : 0;
+
+ if (lvalue)
+ {
+ lvalue->tokstr = tok; /* XXX */
+ lvalue->tokval = tval;
+ lvalue->tokvar = v; /* XXX */
+ lvalue->ind = ind;
+ }
+
+ return (tval);
+}
+
+static int
+_is_multiop (c)
+ int c;
+{
+ switch (c)
+ {
+ case EQEQ:
+ case NEQ:
+ case LEQ:
+ case GEQ:
+ case LAND:
+ case LOR:
+ case LSH:
+ case RSH:
+ case OP_ASSIGN:
+ case COND:
+ case POWER:
+ case PREINC:
+ case PREDEC:
+ case POSTINC:
+ case POSTDEC:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static int
+_is_arithop (c)
+ int c;
+{
+ switch (c)
+ {
+ case EQ:
+ case GT:
+ case LT:
+ case PLUS:
+ case MINUS:
+ case MUL:
+ case DIV:
+ case MOD:
+ case NOT:
+ case LPAR:
+ case RPAR:
+ case BAND:
+ case BOR:
+ case BXOR:
+ case BNOT:
+ return 1; /* operator tokens */
+ case QUES:
+ case COL:
+ case COMMA:
+ return 1; /* questionable */
+ default:
+ return 0; /* anything else is invalid */
+ }
+}
+
+/* Lexical analyzer/token reader for the expression evaluator. Reads the
+ next token and puts its value into curtok, while advancing past it.
+ Updates value of tp. May also set tokval (for number) or tokstr (for
+ string). */
+static void
+readtok ()
+{
+ register char *cp, *xp;
+ register unsigned char c, c1;
+ register int e;
+ struct lvalue lval;
+
+ /* Skip leading whitespace. */
+ cp = tp;
+ c = e = 0;
+ while (cp && (c = *cp) && (cr_whitespace (c)))
+ cp++;
+
+ if (c)
+ cp++;
+
+ if (c == '\0')
+ {
+ lasttok = curtok;
+ curtok = 0;
+ tp = cp;
+ return;
+ }
+ lasttp = tp = cp - 1;
+
+ if (legal_variable_starter (c))
+ {
+ /* variable names not preceded with a dollar sign are shell variables. */
+ char *savecp;
+ EXPR_CONTEXT ec;
+ int peektok;
+
+ while (legal_variable_char (c))
+ c = *cp++;
+
+ c = *--cp;
+
+#if defined (ARRAY_VARS)
+ if (c == '[')
+ {
+ e = skipsubscript (cp, 0, 0);
+ if (cp[e] == ']')
+ {
+ cp += e + 1;
+ c = *cp;
+ e = ']';
+ }
+ else
+ evalerror (bash_badsub_errmsg);
+ }
+#endif /* ARRAY_VARS */
+
+ *cp = '\0';
+ FREE (tokstr);
+ tokstr = savestring (tp);
+ *cp = c;
+
+ /* XXX - make peektok part of saved token state? */
+ SAVETOK (&ec);
+ tokstr = (char *)NULL; /* keep it from being freed */
+ tp = savecp = cp;
+ noeval = 1;
+ curtok = STR;
+ readtok ();
+ peektok = curtok;
+ if (peektok == STR) /* free new tokstr before old one is restored */
+ FREE (tokstr);
+ RESTORETOK (&ec);
+ cp = savecp;
+
+ /* The tests for PREINC and PREDEC aren't strictly correct, but they
+ preserve old behavior if a construct like --x=9 is given. */
+ if (lasttok == PREINC || lasttok == PREDEC || peektok != EQ)
+ {
+ lastlval = curlval;
+ tokval = expr_streval (tokstr, e, &curlval);
+ }
+ else
+ tokval = 0;
+
+ lasttok = curtok;
+ curtok = STR;
+ }
+ else if (DIGIT(c))
+ {
+ while (ISALNUM (c) || c == '#' || c == '@' || c == '_')
+ c = *cp++;
+
+ c = *--cp;
+ *cp = '\0';
+
+ tokval = strlong (tp);
+ *cp = c;
+ lasttok = curtok;
+ curtok = NUM;
+ }
+ else
+ {
+ c1 = *cp++;
+ if ((c == EQ) && (c1 == EQ))
+ c = EQEQ;
+ else if ((c == NOT) && (c1 == EQ))
+ c = NEQ;
+ else if ((c == GT) && (c1 == EQ))
+ c = GEQ;
+ else if ((c == LT) && (c1 == EQ))
+ c = LEQ;
+ else if ((c == LT) && (c1 == LT))
+ {
+ if (*cp == '=') /* a <<= b */
+ {
+ assigntok = LSH;
+ c = OP_ASSIGN;
+ cp++;
+ }
+ else
+ c = LSH;
+ }
+ else if ((c == GT) && (c1 == GT))
+ {
+ if (*cp == '=')
+ {
+ assigntok = RSH; /* a >>= b */
+ c = OP_ASSIGN;
+ cp++;
+ }
+ else
+ c = RSH;
+ }
+ else if ((c == BAND) && (c1 == BAND))
+ c = LAND;
+ else if ((c == BOR) && (c1 == BOR))
+ c = LOR;
+ else if ((c == '*') && (c1 == '*'))
+ c = POWER;
+ else if ((c == '-' || c == '+') && c1 == c && curtok == STR)
+ c = (c == '-') ? POSTDEC : POSTINC;
+ else if ((c == '-' || c == '+') && c1 == c)
+ {
+ /* Quickly scan forward to see if this is followed by optional
+ whitespace and an identifier. */
+ xp = cp;
+ while (xp && *xp && cr_whitespace (*xp))
+ xp++;
+ if (legal_variable_starter ((unsigned char)*xp))
+ c = (c == '-') ? PREDEC : PREINC;
+ else
+ cp--; /* not preinc or predec, so unget the character */
+ }
+ else if (c1 == EQ && member (c, "*/%+-&^|"))
+ {
+ assigntok = c; /* a OP= b */
+ c = OP_ASSIGN;
+ }
+ else if (_is_arithop (c) == 0)
+ {
+ cp--;
+ /* use curtok, since it hasn't been copied to lasttok yet */
+ if (curtok == 0 || _is_arithop (curtok) || _is_multiop (curtok))
+ evalerror (_("syntax error: operand expected"));
+ else
+ evalerror (_("syntax error: invalid arithmetic operator"));
+ }
+ else
+ cp--; /* `unget' the character */
+
+ /* Should check here to make sure that the current character is one
+ of the recognized operators and flag an error if not. Could create
+ a character map the first time through and check it on subsequent
+ calls. */
+ lasttok = curtok;
+ curtok = c;
+ }
+ tp = cp;
+}
+
+static void
+evalerror (msg)
+ const char *msg;
+{
+ char *name, *t;
+
+ name = this_command_name;
+ for (t = expression; whitespace (*t); t++)
+ ;
+ internal_error (_("%s%s%s: %s (error token is \"%s\")"),
+ name ? name : "", name ? ": " : "", t,
+ msg, (lasttp && *lasttp) ? lasttp : "");
+ longjmp (evalbuf, 1);
+}
+
+/* Convert a string to an intmax_t integer, with an arbitrary base.
+ 0nnn -> base 8
+ 0[Xx]nn -> base 16
+ Anything else: [base#]number (this is implemented to match ksh93)
+
+ Base may be >=2 and <=64. If base is <= 36, the numbers are drawn
+ from [0-9][a-zA-Z], and lowercase and uppercase letters may be used
+ interchangably. If base is > 36 and <= 64, the numbers are drawn
+ from [0-9][a-z][A-Z]_@ (a = 10, z = 35, A = 36, Z = 61, @ = 62, _ = 63 --
+ you get the picture). */
+
+static intmax_t
+strlong (num)
+ char *num;
+{
+ register char *s;
+ register unsigned char c;
+ int base, foundbase;
+ intmax_t val;
+
+ s = num;
+
+ base = 10;
+ foundbase = 0;
+ if (*s == '0')
+ {
+ s++;
+
+ if (*s == '\0')
+ return 0;
+
+ /* Base 16? */
+ if (*s == 'x' || *s == 'X')
+ {
+ base = 16;
+ s++;
+ }
+ else
+ base = 8;
+ foundbase++;
+ }
+
+ val = 0;
+ for (c = *s++; c; c = *s++)
+ {
+ if (c == '#')
+ {
+ if (foundbase)
+ evalerror (_("invalid number"));
+
+ /* Illegal base specifications raise an evaluation error. */
+ if (val < 2 || val > 64)
+ evalerror (_("invalid arithmetic base"));
+
+ base = val;
+ val = 0;
+ foundbase++;
+ }
+ else if (ISALNUM(c) || (c == '_') || (c == '@'))
+ {
+ if (DIGIT(c))
+ c = TODIGIT(c);
+ else if (c >= 'a' && c <= 'z')
+ c -= 'a' - 10;
+ else if (c >= 'A' && c <= 'Z')
+ c -= 'A' - ((base <= 36) ? 10 : 36);
+ else if (c == '@')
+ c = 62;
+ else if (c == '_')
+ c = 63;
+
+ if (c >= base)
+ evalerror (_("value too great for base"));
+
+ val = (val * base) + c;
+ }
+ else
+ break;
+ }
+
+ return (val);
+}
+
+#if defined (EXPR_TEST)
+void *
+xmalloc (n)
+ int n;
+{
+ return (malloc (n));
+}
+
+void *
+xrealloc (s, n)
+ char *s;
+ int n;
+{
+ return (realloc (s, n));
+}
+
+SHELL_VAR *find_variable () { return 0;}
+SHELL_VAR *bind_variable () { return 0; }
+
+char *get_string_value () { return 0; }
+
+procenv_t top_level;
+
+main (argc, argv)
+ int argc;
+ char **argv;
+{
+ register int i;
+ intmax_t v;
+ int expok;
+
+ if (setjmp (top_level))
+ exit (0);
+
+ for (i = 1; i < argc; i++)
+ {
+ v = evalexp (argv[i], &expok);
+ if (expok == 0)
+ fprintf (stderr, _("%s: expression error\n"), argv[i]);
+ else
+ printf ("'%s' -> %ld\n", argv[i], v);
+ }
+ exit (0);
+}
+
+int
+builtin_error (format, arg1, arg2, arg3, arg4, arg5)
+ char *format;
+{
+ fprintf (stderr, "expr: ");
+ fprintf (stderr, format, arg1, arg2, arg3, arg4, arg5);
+ fprintf (stderr, "\n");
+ return 0;
+}
+
+char *
+itos (n)
+ intmax_t n;
+{
+ return ("42");
+}
+
+#endif /* EXPR_TEST */
extern int errno;
#endif /* !errno */
+#if !defined (HAVE_KILLPG)
+extern int killpg __P((pid_t, int));
+#endif
+
#define DEFAULT_CHILD_MAX 32
#if !defined (DEBUG)
#define MAX_JOBS_IN_ARRAY 4096 /* production */
--- /dev/null
+/* jobs.c - functions that make children, remember them, and handle their termination. */
+
+/* This file works with both POSIX and BSD systems. It implements job
+ control. */
+
+/* Copyright (C) 1989-2009 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 "trap.h"
+#include <stdio.h>
+#include <signal.h>
+#include <errno.h>
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#include "posixtime.h"
+
+#if defined (HAVE_SYS_RESOURCE_H) && defined (HAVE_WAIT3) && !defined (_POSIX_VERSION) && !defined (RLIMTYPE)
+# include <sys/resource.h>
+#endif /* !_POSIX_VERSION && HAVE_SYS_RESOURCE_H && HAVE_WAIT3 && !RLIMTYPE */
+
+#if defined (HAVE_SYS_FILE_H)
+# include <sys/file.h>
+#endif
+
+#include "filecntl.h"
+#include <sys/ioctl.h>
+#include <sys/param.h>
+
+#if defined (BUFFERED_INPUT)
+# include "input.h"
+#endif
+
+/* Need to include this up here for *_TTY_DRIVER definitions. */
+#include "shtty.h"
+
+/* Define this if your output is getting swallowed. It's a no-op on
+ machines with the termio or termios tty drivers. */
+/* #define DRAIN_OUTPUT */
+
+/* For the TIOCGPGRP and TIOCSPGRP ioctl parameters on HP-UX */
+#if defined (hpux) && !defined (TERMIOS_TTY_DRIVER)
+# include <bsdtty.h>
+#endif /* hpux && !TERMIOS_TTY_DRIVER */
+
+#include "bashansi.h"
+#include "bashintl.h"
+#include "shell.h"
+#include "jobs.h"
+#include "execute_cmd.h"
+#include "flags.h"
+
+#include "builtins/builtext.h"
+#include "builtins/common.h"
+
+#if !defined (errno)
+extern int errno;
+#endif /* !errno */
+
+#define DEFAULT_CHILD_MAX 32
+#if !defined (DEBUG)
+#define MAX_JOBS_IN_ARRAY 4096 /* production */
+#else
+#define MAX_JOBS_IN_ARRAY 128 /* testing */
+#endif
+
+/* Flag values for second argument to delete_job */
+#define DEL_WARNSTOPPED 1 /* warn about deleting stopped jobs */
+#define DEL_NOBGPID 2 /* don't add pgrp leader to bgpids */
+
+/* Take care of system dependencies that must be handled when waiting for
+ children. The arguments to the WAITPID macro match those to the Posix.1
+ waitpid() function. */
+
+#if defined (ultrix) && defined (mips) && defined (_POSIX_VERSION)
+# define WAITPID(pid, statusp, options) \
+ wait3 ((union wait *)statusp, options, (struct rusage *)0)
+#else
+# if defined (_POSIX_VERSION) || defined (HAVE_WAITPID)
+# define WAITPID(pid, statusp, options) \
+ waitpid ((pid_t)pid, statusp, options)
+# else
+# if defined (HAVE_WAIT3)
+# define WAITPID(pid, statusp, options) \
+ wait3 (statusp, options, (struct rusage *)0)
+# else
+# define WAITPID(pid, statusp, options) \
+ wait3 (statusp, options, (int *)0)
+# endif /* HAVE_WAIT3 */
+# endif /* !_POSIX_VERSION && !HAVE_WAITPID*/
+#endif /* !(Ultrix && mips && _POSIX_VERSION) */
+
+/* getpgrp () varies between systems. Even systems that claim to be
+ Posix.1 compatible lie sometimes (Ultrix, SunOS4, apollo). */
+#if defined (GETPGRP_VOID)
+# define getpgid(p) getpgrp ()
+#else
+# define getpgid(p) getpgrp (p)
+#endif /* !GETPGRP_VOID */
+
+/* If the system needs it, REINSTALL_SIGCHLD_HANDLER will reinstall the
+ handler for SIGCHLD. */
+#if defined (MUST_REINSTALL_SIGHANDLERS)
+# define REINSTALL_SIGCHLD_HANDLER signal (SIGCHLD, sigchld_handler)
+#else
+# define REINSTALL_SIGCHLD_HANDLER
+#endif /* !MUST_REINSTALL_SIGHANDLERS */
+
+/* Some systems let waitpid(2) tell callers about stopped children. */
+#if !defined (WCONTINUED) || defined (WCONTINUED_BROKEN)
+# undef WCONTINUED
+# define WCONTINUED 0
+#endif
+#if !defined (WIFCONTINUED)
+# define WIFCONTINUED(s) (0)
+#endif
+
+/* The number of additional slots to allocate when we run out. */
+#define JOB_SLOTS 8
+
+typedef int sh_job_map_func_t __P((JOB *, int, int, int));
+
+/* Variables used here but defined in other files. */
+extern int subshell_environment, line_number;
+extern int posixly_correct, shell_level;
+extern int last_command_exit_value, last_command_exit_signal;
+extern int loop_level, breaking;
+extern int executing_list;
+extern int sourcelevel;
+extern int running_trap;
+extern sh_builtin_func_t *this_shell_builtin;
+extern char *shell_name, *this_command_name;
+extern sigset_t top_level_mask;
+extern procenv_t wait_intr_buf;
+extern int wait_signal_received;
+extern WORD_LIST *subst_assign_varlist;
+
+static struct jobstats zerojs = { -1L, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NO_JOB, NO_JOB, 0, 0 };
+struct jobstats js = { -1L, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NO_JOB, NO_JOB, 0, 0 };
+
+struct bgpids bgpids = { 0, 0, 0 };
+
+/* The array of known jobs. */
+JOB **jobs = (JOB **)NULL;
+
+#if 0
+/* The number of slots currently allocated to JOBS. */
+int job_slots = 0;
+#endif
+
+/* The controlling tty for this shell. */
+int shell_tty = -1;
+
+/* The shell's process group. */
+pid_t shell_pgrp = NO_PID;
+
+/* The terminal's process group. */
+pid_t terminal_pgrp = NO_PID;
+
+/* The process group of the shell's parent. */
+pid_t original_pgrp = NO_PID;
+
+/* The process group of the pipeline currently being made. */
+pid_t pipeline_pgrp = (pid_t)0;
+
+#if defined (PGRP_PIPE)
+/* Pipes which each shell uses to communicate with the process group leader
+ until all of the processes in a pipeline have been started. Then the
+ process leader is allowed to continue. */
+int pgrp_pipe[2] = { -1, -1 };
+#endif
+
+#if 0
+/* The job which is current; i.e. the one that `%+' stands for. */
+int current_job = NO_JOB;
+
+/* The previous job; i.e. the one that `%-' stands for. */
+int previous_job = NO_JOB;
+#endif
+
+/* Last child made by the shell. */
+pid_t last_made_pid = NO_PID;
+
+/* Pid of the last asynchronous child. */
+pid_t last_asynchronous_pid = NO_PID;
+
+/* The pipeline currently being built. */
+PROCESS *the_pipeline = (PROCESS *)NULL;
+
+/* If this is non-zero, do job control. */
+int job_control = 1;
+
+/* Call this when you start making children. */
+int already_making_children = 0;
+
+/* If this is non-zero, $LINES and $COLUMNS are reset after every process
+ exits from get_tty_state(). */
+int check_window_size;
+
+/* Functions local to this file. */
+
+static sighandler wait_sigint_handler __P((int));
+static sighandler sigchld_handler __P((int));
+static sighandler sigcont_sighandler __P((int));
+static sighandler sigstop_sighandler __P((int));
+
+static int waitchld __P((pid_t, int));
+
+static PROCESS *find_pipeline __P((pid_t, int, int *));
+static PROCESS *find_process __P((pid_t, int, int *));
+
+static char *current_working_directory __P((void));
+static char *job_working_directory __P((void));
+static char *j_strsignal __P((int));
+static char *printable_job_status __P((int, PROCESS *, int));
+
+static PROCESS *find_last_proc __P((int, int));
+static pid_t find_last_pid __P((int, int));
+
+static int set_new_line_discipline __P((int));
+static int map_over_jobs __P((sh_job_map_func_t *, int, int));
+static int job_last_stopped __P((int));
+static int job_last_running __P((int));
+static int most_recent_job_in_state __P((int, JOB_STATE));
+static int find_job __P((pid_t, int, PROCESS **));
+static int print_job __P((JOB *, int, int, int));
+static int process_exit_status __P((WAIT));
+static int process_exit_signal __P((WAIT));
+static int job_exit_status __P((int));
+static int job_exit_signal __P((int));
+static int set_job_status_and_cleanup __P((int));
+
+static WAIT job_signal_status __P((int));
+static WAIT raw_job_exit_status __P((int));
+
+static void notify_of_job_status __P((void));
+static void reset_job_indices __P((void));
+static void cleanup_dead_jobs __P((void));
+static int processes_in_job __P((int));
+static void realloc_jobs_list __P((void));
+static int compact_jobs_list __P((int));
+static int discard_pipeline __P((PROCESS *));
+static void add_process __P((char *, pid_t));
+static void print_pipeline __P((PROCESS *, int, int, FILE *));
+static void pretty_print_job __P((int, int, FILE *));
+static void set_current_job __P((int));
+static void reset_current __P((void));
+static void set_job_running __P((int));
+static void setjstatus __P((int));
+static int maybe_give_terminal_to __P((pid_t, pid_t, int));
+static void mark_all_jobs_as_dead __P((void));
+static void mark_dead_jobs_as_notified __P((int));
+static void restore_sigint_handler __P((void));
+#if defined (PGRP_PIPE)
+static void pipe_read __P((int *));
+#endif
+
+static struct pidstat *bgp_alloc __P((pid_t, int));
+static struct pidstat *bgp_add __P((pid_t, int));
+static int bgp_delete __P((pid_t));
+static void bgp_clear __P((void));
+static int bgp_search __P((pid_t));
+static void bgp_prune __P((void));
+
+#if defined (ARRAY_VARS)
+static int *pstatuses; /* list of pipeline statuses */
+static int statsize;
+#endif
+
+/* Used to synchronize between wait_for and other functions and the SIGCHLD
+ signal handler. */
+static int sigchld;
+static int queue_sigchld;
+
+#define QUEUE_SIGCHLD(os) (os) = sigchld, queue_sigchld++
+
+#define UNQUEUE_SIGCHLD(os) \
+ do { \
+ queue_sigchld--; \
+ if (queue_sigchld == 0 && os != sigchld) \
+ waitchld (-1, 0); \
+ } while (0)
+
+static SigHandler *old_tstp, *old_ttou, *old_ttin;
+static SigHandler *old_cont = (SigHandler *)SIG_DFL;
+
+/* A place to temporarily save the current pipeline. */
+static PROCESS *saved_pipeline;
+static int saved_already_making_children;
+
+/* Set this to non-zero whenever you don't want the jobs list to change at
+ all: no jobs deleted and no status change notifications. This is used,
+ for example, when executing SIGCHLD traps, which may run arbitrary
+ commands. */
+static int jobs_list_frozen;
+
+static char retcode_name_buffer[64];
+
+/* flags to detect pid wraparound */
+static pid_t first_pid = NO_PID;
+static int pid_wrap = -1;
+
+#if !defined (_POSIX_VERSION)
+
+/* These are definitions to map POSIX 1003.1 functions onto existing BSD
+ library functions and system calls. */
+#define setpgid(pid, pgrp) setpgrp (pid, pgrp)
+#define tcsetpgrp(fd, pgrp) ioctl ((fd), TIOCSPGRP, &(pgrp))
+
+pid_t
+tcgetpgrp (fd)
+ int fd;
+{
+ pid_t pgrp;
+
+ /* ioctl will handle setting errno correctly. */
+ if (ioctl (fd, TIOCGPGRP, &pgrp) < 0)
+ return (-1);
+ return (pgrp);
+}
+
+#endif /* !_POSIX_VERSION */
+
+/* Initialize the global job stats structure and other bookkeeping variables */
+void
+init_job_stats ()
+{
+ js = zerojs;
+ first_pid = NO_PID;
+ pid_wrap = -1;
+}
+
+/* Return the working directory for the current process. Unlike
+ job_working_directory, this does not call malloc (), nor do any
+ of the functions it calls. This is so that it can safely be called
+ from a signal handler. */
+static char *
+current_working_directory ()
+{
+ char *dir;
+ static char d[PATH_MAX];
+
+ dir = get_string_value ("PWD");
+
+ if (dir == 0 && the_current_working_directory && no_symbolic_links)
+ dir = the_current_working_directory;
+
+ if (dir == 0)
+ {
+ dir = getcwd (d, sizeof(d));
+ if (dir)
+ dir = d;
+ }
+
+ return (dir == 0) ? "<unknown>" : dir;
+}
+
+/* Return the working directory for the current process. */
+static char *
+job_working_directory ()
+{
+ char *dir;
+
+ dir = get_string_value ("PWD");
+ if (dir)
+ return (savestring (dir));
+
+ dir = get_working_directory ("job-working-directory");
+ if (dir)
+ return (dir);
+
+ return (savestring ("<unknown>"));
+}
+
+void
+making_children ()
+{
+ if (already_making_children)
+ return;
+
+ already_making_children = 1;
+ start_pipeline ();
+}
+
+void
+stop_making_children ()
+{
+ already_making_children = 0;
+}
+
+void
+cleanup_the_pipeline ()
+{
+ PROCESS *disposer;
+ sigset_t set, oset;
+
+ BLOCK_CHILD (set, oset);
+ disposer = the_pipeline;
+ the_pipeline = (PROCESS *)NULL;
+ UNBLOCK_CHILD (oset);
+
+ if (disposer)
+ discard_pipeline (disposer);
+}
+
+void
+save_pipeline (clear)
+ int clear;
+{
+ saved_pipeline = the_pipeline;
+ if (clear)
+ the_pipeline = (PROCESS *)NULL;
+ saved_already_making_children = already_making_children;
+}
+
+void
+restore_pipeline (discard)
+ int discard;
+{
+ PROCESS *old_pipeline;
+
+ old_pipeline = the_pipeline;
+ the_pipeline = saved_pipeline;
+ already_making_children = saved_already_making_children;
+ if (discard && old_pipeline)
+ discard_pipeline (old_pipeline);
+}
+
+/* Start building a pipeline. */
+void
+start_pipeline ()
+{
+ if (the_pipeline)
+ {
+ cleanup_the_pipeline ();
+ pipeline_pgrp = 0;
+#if defined (PGRP_PIPE)
+ sh_closepipe (pgrp_pipe);
+#endif
+ }
+
+#if defined (PGRP_PIPE)
+ if (job_control)
+ {
+ if (pipe (pgrp_pipe) == -1)
+ sys_error (_("start_pipeline: pgrp pipe"));
+ }
+#endif
+}
+
+/* Stop building a pipeline. Install the process list in the job array.
+ This returns the index of the newly installed job.
+ DEFERRED is a command structure to be executed upon satisfactory
+ execution exit of this pipeline. */
+int
+stop_pipeline (async, deferred)
+ int async;
+ COMMAND *deferred;
+{
+ register int i, j;
+ JOB *newjob;
+ sigset_t set, oset;
+
+ BLOCK_CHILD (set, oset);
+
+#if defined (PGRP_PIPE)
+ /* The parent closes the process group synchronization pipe. */
+ sh_closepipe (pgrp_pipe);
+#endif
+
+ cleanup_dead_jobs ();
+
+ if (js.j_jobslots == 0)
+ {
+ js.j_jobslots = JOB_SLOTS;
+ jobs = (JOB **)xmalloc (js.j_jobslots * sizeof (JOB *));
+
+ /* Now blank out these new entries. */
+ for (i = 0; i < js.j_jobslots; i++)
+ jobs[i] = (JOB *)NULL;
+
+ js.j_firstj = js.j_lastj = js.j_njobs = 0;
+ }
+
+ /* Scan from the last slot backward, looking for the next free one. */
+ /* XXX - revisit this interactive assumption */
+ /* XXX - this way for now */
+ if (interactive)
+ {
+ for (i = js.j_jobslots; i; i--)
+ if (jobs[i - 1])
+ break;
+ }
+ else
+ {
+#if 0
+ /* This wraps around, but makes it inconvenient to extend the array */
+ for (i = js.j_lastj+1; i != js.j_lastj; i++)
+ {
+ if (i >= js.j_jobslots)
+ i = 0;
+ if (jobs[i] == 0)
+ break;
+ }
+ if (i == js.j_lastj)
+ i = js.j_jobslots;
+#else
+ /* This doesn't wrap around yet. */
+ for (i = js.j_lastj ? js.j_lastj + 1 : js.j_lastj; i < js.j_jobslots; i++)
+ if (jobs[i] == 0)
+ break;
+#endif
+ }
+
+ /* Do we need more room? */
+
+ /* First try compaction */
+ if ((interactive_shell == 0 || subshell_environment) && i == js.j_jobslots && js.j_jobslots >= MAX_JOBS_IN_ARRAY)
+ i = compact_jobs_list (0);
+
+ /* If we can't compact, reallocate */
+ if (i == js.j_jobslots)
+ {
+ js.j_jobslots += JOB_SLOTS;
+ jobs = (JOB **)xrealloc (jobs, (js.j_jobslots * sizeof (JOB *)));
+
+ for (j = i; j < js.j_jobslots; j++)
+ jobs[j] = (JOB *)NULL;
+ }
+
+ /* Add the current pipeline to the job list. */
+ if (the_pipeline)
+ {
+ register PROCESS *p;
+ int any_running, any_stopped, n;
+
+ newjob = (JOB *)xmalloc (sizeof (JOB));
+
+ for (n = 1, p = the_pipeline; p->next != the_pipeline; n++, p = p->next)
+ ;
+ p->next = (PROCESS *)NULL;
+ newjob->pipe = REVERSE_LIST (the_pipeline, PROCESS *);
+ for (p = newjob->pipe; p->next; p = p->next)
+ ;
+ p->next = newjob->pipe;
+
+ the_pipeline = (PROCESS *)NULL;
+ newjob->pgrp = pipeline_pgrp;
+ pipeline_pgrp = 0;
+
+ newjob->flags = 0;
+
+ /* Flag to see if in another pgrp. */
+ if (job_control)
+ newjob->flags |= J_JOBCONTROL;
+
+ /* Set the state of this pipeline. */
+ p = newjob->pipe;
+ any_running = any_stopped = 0;
+ do
+ {
+ any_running |= PRUNNING (p);
+ any_stopped |= PSTOPPED (p);
+ p = p->next;
+ }
+ while (p != newjob->pipe);
+
+ newjob->state = any_running ? JRUNNING : (any_stopped ? JSTOPPED : JDEAD);
+ newjob->wd = job_working_directory ();
+ newjob->deferred = deferred;
+
+ newjob->j_cleanup = (sh_vptrfunc_t *)NULL;
+ newjob->cleanarg = (PTR_T) NULL;
+
+ jobs[i] = newjob;
+ if (newjob->state == JDEAD && (newjob->flags & J_FOREGROUND))
+ setjstatus (i);
+ if (newjob->state == JDEAD)
+ {
+ js.c_reaped += n; /* wouldn't have been done since this was not part of a job */
+ js.j_ndead++;
+ }
+ js.c_injobs += n;
+
+ js.j_lastj = i;
+ js.j_njobs++;
+ }
+ else
+ newjob = (JOB *)NULL;
+
+ if (newjob)
+ js.j_lastmade = newjob;
+
+ if (async)
+ {
+ if (newjob)
+ {
+ newjob->flags &= ~J_FOREGROUND;
+ newjob->flags |= J_ASYNC;
+ js.j_lastasync = newjob;
+ }
+ reset_current ();
+ }
+ else
+ {
+ if (newjob)
+ {
+ newjob->flags |= J_FOREGROUND;
+ /*
+ * !!!!! NOTE !!!!! (chet@ins.cwru.edu)
+ *
+ * The currently-accepted job control wisdom says to set the
+ * terminal's process group n+1 times in an n-step pipeline:
+ * once in the parent and once in each child. This is where
+ * the parent gives it away.
+ *
+ * Don't give the terminal away if this shell is an asynchronous
+ * subshell.
+ *
+ */
+ if (job_control && newjob->pgrp && (subshell_environment&SUBSHELL_ASYNC) == 0)
+ maybe_give_terminal_to (shell_pgrp, newjob->pgrp, 0);
+ }
+ }
+
+ stop_making_children ();
+ UNBLOCK_CHILD (oset);
+ return (js.j_current);
+}
+
+/* Functions to manage the list of exited background pids whose status has
+ been saved. */
+
+static struct pidstat *
+bgp_alloc (pid, status)
+ pid_t pid;
+ int status;
+{
+ struct pidstat *ps;
+
+ ps = (struct pidstat *)xmalloc (sizeof (struct pidstat));
+ ps->pid = pid;
+ ps->status = status;
+ ps->next = (struct pidstat *)0;
+ return ps;
+}
+
+static struct pidstat *
+bgp_add (pid, status)
+ pid_t pid;
+ int status;
+{
+ struct pidstat *ps;
+
+ ps = bgp_alloc (pid, status);
+
+ if (bgpids.list == 0)
+ {
+ bgpids.list = bgpids.end = ps;
+ bgpids.npid = 0; /* just to make sure */
+ }
+ else
+ {
+ bgpids.end->next = ps;
+ bgpids.end = ps;
+ }
+ bgpids.npid++;
+
+ if (bgpids.npid > js.c_childmax)
+ bgp_prune ();
+
+ return ps;
+}
+
+static int
+bgp_delete (pid)
+ pid_t pid;
+{
+ struct pidstat *prev, *p;
+
+ for (prev = p = bgpids.list; p; prev = p, p = p->next)
+ if (p->pid == pid)
+ {
+ prev->next = p->next; /* remove from list */
+ break;
+ }
+
+ if (p == 0)
+ return 0; /* not found */
+
+#if defined (DEBUG)
+ itrace("bgp_delete: deleting %d", pid);
+#endif
+
+ /* Housekeeping in the border cases. */
+ if (p == bgpids.list)
+ bgpids.list = bgpids.list->next;
+ else if (p == bgpids.end)
+ bgpids.end = prev;
+
+ bgpids.npid--;
+ if (bgpids.npid == 0)
+ bgpids.list = bgpids.end = 0;
+ else if (bgpids.npid == 1)
+ bgpids.end = bgpids.list; /* just to make sure */
+
+ free (p);
+ return 1;
+}
+
+/* Clear out the list of saved statuses */
+static void
+bgp_clear ()
+{
+ struct pidstat *ps, *p;
+
+ for (ps = bgpids.list; ps; )
+ {
+ p = ps;
+ ps = ps->next;
+ free (p);
+ }
+ bgpids.list = bgpids.end = 0;
+ bgpids.npid = 0;
+}
+
+/* Search for PID in the list of saved background pids; return its status if
+ found. If not found, return -1. */
+static int
+bgp_search (pid)
+ pid_t pid;
+{
+ struct pidstat *ps;
+
+ for (ps = bgpids.list ; ps; ps = ps->next)
+ if (ps->pid == pid)
+ return ps->status;
+ return -1;
+}
+
+static void
+bgp_prune ()
+{
+ struct pidstat *ps;
+
+ while (bgpids.npid > js.c_childmax)
+ {
+ ps = bgpids.list;
+ bgpids.list = bgpids.list->next;
+ free (ps);
+ bgpids.npid--;
+ }
+}
+
+/* Reset the values of js.j_lastj and js.j_firstj after one or both have
+ been deleted. The caller should check whether js.j_njobs is 0 before
+ calling this. This wraps around, but the rest of the code does not. At
+ this point, it should not matter. */
+static void
+reset_job_indices ()
+{
+ int old;
+
+ if (jobs[js.j_firstj] == 0)
+ {
+ old = js.j_firstj++;
+ if (old >= js.j_jobslots)
+ old = js.j_jobslots - 1;
+ while (js.j_firstj != old)
+ {
+ if (js.j_firstj >= js.j_jobslots)
+ js.j_firstj = 0;
+ if (jobs[js.j_firstj] || js.j_firstj == old) /* needed if old == 0 */
+ break;
+ js.j_firstj++;
+ }
+ if (js.j_firstj == old)
+ js.j_firstj = js.j_lastj = js.j_njobs = 0;
+ }
+ if (jobs[js.j_lastj] == 0)
+ {
+ old = js.j_lastj--;
+ if (old < 0)
+ old = 0;
+ while (js.j_lastj != old)
+ {
+ if (js.j_lastj < 0)
+ js.j_lastj = js.j_jobslots - 1;
+ if (jobs[js.j_lastj] || js.j_lastj == old) /* needed if old == js.j_jobslots */
+ break;
+ js.j_lastj--;
+ }
+ if (js.j_lastj == old)
+ js.j_firstj = js.j_lastj = js.j_njobs = 0;
+ }
+}
+
+/* Delete all DEAD jobs that the user had received notification about. */
+static void
+cleanup_dead_jobs ()
+{
+ register int i;
+ int os;
+
+ if (js.j_jobslots == 0 || jobs_list_frozen)
+ return;
+
+ QUEUE_SIGCHLD(os);
+
+ /* XXX could use js.j_firstj and js.j_lastj here */
+ for (i = 0; i < js.j_jobslots; i++)
+ {
+#if defined (DEBUG)
+ if (i < js.j_firstj && jobs[i])
+ itrace("cleanup_dead_jobs: job %d non-null before js.j_firstj (%d)", i, js.j_firstj);
+ if (i > js.j_lastj && jobs[i])
+ itrace("cleanup_dead_jobs: job %d non-null after js.j_lastj (%d)", i, js.j_lastj);
+#endif
+
+ if (jobs[i] && DEADJOB (i) && IS_NOTIFIED (i))
+ delete_job (i, 0);
+ }
+
+#if defined (COPROCESS_SUPPORT)
+ coproc_reap ();
+#endif
+
+ UNQUEUE_SIGCHLD(os);
+}
+
+static int
+processes_in_job (job)
+ int job;
+{
+ int nproc;
+ register PROCESS *p;
+
+ nproc = 0;
+ p = jobs[job]->pipe;
+ do
+ {
+ p = p->next;
+ nproc++;
+ }
+ while (p != jobs[job]->pipe);
+
+ return nproc;
+}
+
+static void
+delete_old_job (pid)
+ pid_t pid;
+{
+ PROCESS *p;
+ int job;
+
+ job = find_job (pid, 0, &p);
+ if (job != NO_JOB)
+ {
+#ifdef DEBUG
+ itrace ("delete_old_job: found pid %d in job %d with state %d", pid, job, jobs[job]->state);
+#endif
+ if (JOBSTATE (job) == JDEAD)
+ delete_job (job, DEL_NOBGPID);
+ else
+ {
+ internal_warning (_("forked pid %d appears in running job %d"), pid, job);
+ if (p)
+ p->pid = 0;
+ }
+ }
+}
+
+/* Reallocate and compress the jobs list. This returns with a jobs array
+ whose size is a multiple of JOB_SLOTS and can hold the current number of
+ jobs. Heuristics are used to minimize the number of new reallocs. */
+static void
+realloc_jobs_list ()
+{
+ sigset_t set, oset;
+ int nsize, i, j, ncur, nprev;
+ JOB **nlist;
+
+ ncur = nprev = NO_JOB;
+ nsize = ((js.j_njobs + JOB_SLOTS - 1) / JOB_SLOTS);
+ nsize *= JOB_SLOTS;
+ i = js.j_njobs % JOB_SLOTS;
+ if (i == 0 || i > (JOB_SLOTS >> 1))
+ nsize += JOB_SLOTS;
+
+ BLOCK_CHILD (set, oset);
+ nlist = (js.j_jobslots == nsize) ? jobs : (JOB **) xmalloc (nsize * sizeof (JOB *));
+
+ js.c_reaped = js.j_ndead = 0;
+ for (i = j = 0; i < js.j_jobslots; i++)
+ if (jobs[i])
+ {
+ if (i == js.j_current)
+ ncur = j;
+ if (i == js.j_previous)
+ nprev = j;
+ nlist[j++] = jobs[i];
+ if (jobs[i]->state == JDEAD)
+ {
+ js.j_ndead++;
+ js.c_reaped += processes_in_job (i);
+ }
+ }
+
+#if defined (DEBUG)
+ itrace ("realloc_jobs_list: resize jobs list from %d to %d", js.j_jobslots, nsize);
+ itrace ("realloc_jobs_list: j_lastj changed from %d to %d", js.j_lastj, (j > 0) ? j - 1 : 0);
+ itrace ("realloc_jobs_list: j_njobs changed from %d to %d", js.j_njobs, j);
+ itrace ("realloc_jobs_list: js.j_ndead %d js.c_reaped %d", js.j_ndead, js.c_reaped);
+#endif
+
+ js.j_firstj = 0;
+ js.j_lastj = (j > 0) ? j - 1 : 0;
+ js.j_njobs = j;
+ js.j_jobslots = nsize;
+
+ /* Zero out remaining slots in new jobs list */
+ for ( ; j < nsize; j++)
+ nlist[j] = (JOB *)NULL;
+
+ if (jobs != nlist)
+ {
+ free (jobs);
+ jobs = nlist;
+ }
+
+ if (ncur != NO_JOB)
+ js.j_current = ncur;
+ if (nprev != NO_JOB)
+ js.j_previous = nprev;
+
+ /* Need to reset these */
+ if (js.j_current == NO_JOB || js.j_previous == NO_JOB || js.j_current > js.j_lastj || js.j_previous > js.j_lastj)
+ reset_current ();
+
+#ifdef DEBUG
+ itrace ("realloc_jobs_list: reset js.j_current (%d) and js.j_previous (%d)", js.j_current, js.j_previous);
+#endif
+
+ UNBLOCK_CHILD (oset);
+}
+
+/* Compact the jobs list by removing dead jobs. Assumed that we have filled
+ the jobs array to some predefined maximum. Called when the shell is not
+ the foreground process (subshell_environment != 0). Returns the first
+ available slot in the compacted list. If that value is js.j_jobslots, then
+ the list needs to be reallocated. The jobs array may be in new memory if
+ this returns > 0 and < js.j_jobslots. FLAGS is reserved for future use. */
+static int
+compact_jobs_list (flags)
+ int flags;
+{
+ if (js.j_jobslots == 0 || jobs_list_frozen)
+ return js.j_jobslots;
+
+ reap_dead_jobs ();
+ realloc_jobs_list ();
+
+#ifdef DEBUG
+ itrace("compact_jobs_list: returning %d", (js.j_lastj || jobs[js.j_lastj]) ? js.j_lastj + 1 : 0);
+#endif
+
+ return ((js.j_lastj || jobs[js.j_lastj]) ? js.j_lastj + 1 : 0);
+}
+
+/* Delete the job at INDEX from the job list. Must be called
+ with SIGCHLD blocked. */
+void
+delete_job (job_index, dflags)
+ int job_index, dflags;
+{
+ register JOB *temp;
+ PROCESS *proc;
+ int ndel;
+
+ if (js.j_jobslots == 0 || jobs_list_frozen)
+ return;
+
+ if ((dflags & DEL_WARNSTOPPED) && subshell_environment == 0 && STOPPED (job_index))
+ internal_warning (_("deleting stopped job %d with process group %ld"), job_index+1, (long)jobs[job_index]->pgrp);
+ temp = jobs[job_index];
+ if (temp == 0)
+ return;
+
+ if ((dflags & DEL_NOBGPID) == 0)
+ {
+ proc = find_last_proc (job_index, 0);
+ /* Could do this just for J_ASYNC jobs, but we save all. */
+ if (proc)
+ bgp_add (proc->pid, process_exit_status (proc->status));
+ }
+
+ jobs[job_index] = (JOB *)NULL;
+ if (temp == js.j_lastmade)
+ js.j_lastmade = 0;
+ else if (temp == js.j_lastasync)
+ js.j_lastasync = 0;
+
+ free (temp->wd);
+ ndel = discard_pipeline (temp->pipe);
+
+ js.c_injobs -= ndel;
+ if (temp->state == JDEAD)
+ {
+ js.c_reaped -= ndel;
+ js.j_ndead--;
+ if (js.c_reaped < 0)
+ {
+#ifdef DEBUG
+ itrace("delete_job (%d pgrp %d): js.c_reaped (%d) < 0 ndel = %d js.j_ndead = %d", job_index, temp->pgrp, js.c_reaped, ndel, js.j_ndead);
+#endif
+ js.c_reaped = 0;
+ }
+ }
+
+ if (temp->deferred)
+ dispose_command (temp->deferred);
+
+ free (temp);
+
+ js.j_njobs--;
+ if (js.j_njobs == 0)
+ js.j_firstj = js.j_lastj = 0;
+ else if (jobs[js.j_firstj] == 0 || jobs[js.j_lastj] == 0)
+ reset_job_indices ();
+
+ if (job_index == js.j_current || job_index == js.j_previous)
+ reset_current ();
+}
+
+/* Must be called with SIGCHLD blocked. */
+void
+nohup_job (job_index)
+ int job_index;
+{
+ register JOB *temp;
+
+ if (js.j_jobslots == 0)
+ return;
+
+ if (temp = jobs[job_index])
+ temp->flags |= J_NOHUP;
+}
+
+/* Get rid of the data structure associated with a process chain. */
+static int
+discard_pipeline (chain)
+ register PROCESS *chain;
+{
+ register PROCESS *this, *next;
+ int n;
+
+ this = chain;
+ n = 0;
+ do
+ {
+ next = this->next;
+ FREE (this->command);
+ free (this);
+ n++;
+ this = next;
+ }
+ while (this != chain);
+
+ return n;
+}
+
+/* Add this process to the chain being built in the_pipeline.
+ NAME is the command string that will be exec'ed later.
+ PID is the process id of the child. */
+static void
+add_process (name, pid)
+ char *name;
+ pid_t pid;
+{
+ PROCESS *t, *p;
+
+#if defined (RECYCLES_PIDS)
+ int j;
+ p = find_process (pid, 0, &j);
+ if (p)
+ {
+# ifdef DEBUG
+ if (j == NO_JOB)
+ internal_warning (_("add_process: process %5ld (%s) in the_pipeline"), (long)p->pid, p->command);
+# endif
+ if (PALIVE (p))
+ internal_warning (_("add_process: pid %5ld (%s) marked as still alive"), (long)p->pid, p->command);
+ p->running = PS_RECYCLED; /* mark as recycled */
+ }
+#endif
+
+ t = (PROCESS *)xmalloc (sizeof (PROCESS));
+ t->next = the_pipeline;
+ t->pid = pid;
+ WSTATUS (t->status) = 0;
+ t->running = PS_RUNNING;
+ t->command = name;
+ the_pipeline = t;
+
+ if (t->next == 0)
+ t->next = t;
+ else
+ {
+ p = t->next;
+ while (p->next != t->next)
+ p = p->next;
+ p->next = t;
+ }
+}
+
+#if 0
+/* Take the last job and make it the first job. Must be called with
+ SIGCHLD blocked. */
+int
+rotate_the_pipeline ()
+{
+ PROCESS *p;
+
+ if (the_pipeline->next == the_pipeline)
+ return;
+ for (p = the_pipeline; p->next != the_pipeline; p = p->next)
+ ;
+ the_pipeline = p;
+}
+
+/* Reverse the order of the processes in the_pipeline. Must be called with
+ SIGCHLD blocked. */
+int
+reverse_the_pipeline ()
+{
+ PROCESS *p, *n;
+
+ if (the_pipeline->next == the_pipeline)
+ return;
+
+ for (p = the_pipeline; p->next != the_pipeline; p = p->next)
+ ;
+ p->next = (PROCESS *)NULL;
+
+ n = REVERSE_LIST (the_pipeline, PROCESS *);
+
+ the_pipeline = n;
+ for (p = the_pipeline; p->next; p = p->next)
+ ;
+ p->next = the_pipeline;
+}
+#endif
+
+/* Map FUNC over the list of jobs. If FUNC returns non-zero,
+ then it is time to stop mapping, and that is the return value
+ for map_over_jobs. FUNC is called with a JOB, arg1, arg2,
+ and INDEX. */
+static int
+map_over_jobs (func, arg1, arg2)
+ sh_job_map_func_t *func;
+ int arg1, arg2;
+{
+ register int i;
+ int result;
+ sigset_t set, oset;
+
+ if (js.j_jobslots == 0)
+ return 0;
+
+ BLOCK_CHILD (set, oset);
+
+ /* XXX could use js.j_firstj here */
+ for (i = result = 0; i < js.j_jobslots; i++)
+ {
+#if defined (DEBUG)
+ if (i < js.j_firstj && jobs[i])
+ itrace("map_over_jobs: job %d non-null before js.j_firstj (%d)", i, js.j_firstj);
+ if (i > js.j_lastj && jobs[i])
+ itrace("map_over_jobs: job %d non-null after js.j_lastj (%d)", i, js.j_lastj);
+#endif
+ if (jobs[i])
+ {
+ result = (*func)(jobs[i], arg1, arg2, i);
+ if (result)
+ break;
+ }
+ }
+
+ UNBLOCK_CHILD (oset);
+
+ return (result);
+}
+
+/* Cause all the jobs in the current pipeline to exit. */
+void
+terminate_current_pipeline ()
+{
+ if (pipeline_pgrp && pipeline_pgrp != shell_pgrp)
+ {
+ killpg (pipeline_pgrp, SIGTERM);
+ killpg (pipeline_pgrp, SIGCONT);
+ }
+}
+
+/* Cause all stopped jobs to exit. */
+void
+terminate_stopped_jobs ()
+{
+ register int i;
+
+ /* XXX could use js.j_firstj here */
+ for (i = 0; i < js.j_jobslots; i++)
+ {
+ if (jobs[i] && STOPPED (i))
+ {
+ killpg (jobs[i]->pgrp, SIGTERM);
+ killpg (jobs[i]->pgrp, SIGCONT);
+ }
+ }
+}
+
+/* Cause all jobs, running or stopped, to receive a hangup signal. If
+ a job is marked J_NOHUP, don't send the SIGHUP. */
+void
+hangup_all_jobs ()
+{
+ register int i;
+
+ /* XXX could use js.j_firstj here */
+ for (i = 0; i < js.j_jobslots; i++)
+ {
+ if (jobs[i])
+ {
+ if (jobs[i]->flags & J_NOHUP)
+ continue;
+ killpg (jobs[i]->pgrp, SIGHUP);
+ if (STOPPED (i))
+ killpg (jobs[i]->pgrp, SIGCONT);
+ }
+ }
+}
+
+void
+kill_current_pipeline ()
+{
+ stop_making_children ();
+ start_pipeline ();
+}
+
+/* Return the pipeline that PID belongs to. Note that the pipeline
+ doesn't have to belong to a job. Must be called with SIGCHLD blocked.
+ If JOBP is non-null, return the index of the job containing PID. */
+static PROCESS *
+find_pipeline (pid, alive_only, jobp)
+ pid_t pid;
+ int alive_only;
+ int *jobp; /* index into jobs list or NO_JOB */
+{
+ int job;
+ PROCESS *p;
+
+ /* See if this process is in the pipeline that we are building. */
+ if (jobp)
+ *jobp = NO_JOB;
+ if (the_pipeline)
+ {
+ p = the_pipeline;
+ do
+ {
+ /* Return it if we found it. Don't ever return a recycled pid. */
+ if (p->pid == pid && ((alive_only == 0 && PRECYCLED(p) == 0) || PALIVE(p)))
+ return (p);
+
+ p = p->next;
+ }
+ while (p != the_pipeline);
+ }
+
+ job = find_job (pid, alive_only, &p);
+ if (jobp)
+ *jobp = job;
+ return (job == NO_JOB) ? (PROCESS *)NULL : jobs[job]->pipe;
+}
+
+/* Return the PROCESS * describing PID. If JOBP is non-null return the index
+ into the jobs array of the job containing PID. Must be called with
+ SIGCHLD blocked. */
+static PROCESS *
+find_process (pid, alive_only, jobp)
+ pid_t pid;
+ int alive_only;
+ int *jobp; /* index into jobs list or NO_JOB */
+{
+ PROCESS *p;
+
+ p = find_pipeline (pid, alive_only, jobp);
+ while (p && p->pid != pid)
+ p = p->next;
+ return p;
+}
+
+/* Return the job index that PID belongs to, or NO_JOB if it doesn't
+ belong to any job. Must be called with SIGCHLD blocked. */
+static int
+find_job (pid, alive_only, procp)
+ pid_t pid;
+ int alive_only;
+ PROCESS **procp;
+{
+ register int i;
+ PROCESS *p;
+
+ /* XXX could use js.j_firstj here, and should check js.j_lastj */
+ for (i = 0; i < js.j_jobslots; i++)
+ {
+#if defined (DEBUG)
+ if (i < js.j_firstj && jobs[i])
+ itrace("find_job: job %d non-null before js.j_firstj (%d)", i, js.j_firstj);
+ if (i > js.j_lastj && jobs[i])
+ itrace("find_job: job %d non-null after js.j_lastj (%d)", i, js.j_lastj);
+#endif
+ if (jobs[i])
+ {
+ p = jobs[i]->pipe;
+
+ do
+ {
+ if (p->pid == pid && ((alive_only == 0 && PRECYCLED(p) == 0) || PALIVE(p)))
+ {
+ if (procp)
+ *procp = p;
+ return (i);
+ }
+
+ p = p->next;
+ }
+ while (p != jobs[i]->pipe);
+ }
+ }
+
+ return (NO_JOB);
+}
+
+/* Find a job given a PID. If BLOCK is non-zero, block SIGCHLD as
+ required by find_job. */
+int
+get_job_by_pid (pid, block)
+ pid_t pid;
+ int block;
+{
+ int job;
+ sigset_t set, oset;
+
+ if (block)
+ BLOCK_CHILD (set, oset);
+
+ job = find_job (pid, 0, NULL);
+
+ if (block)
+ UNBLOCK_CHILD (oset);
+
+ return job;
+}
+
+/* Print descriptive information about the job with leader pid PID. */
+void
+describe_pid (pid)
+ pid_t pid;
+{
+ int job;
+ sigset_t set, oset;
+
+ BLOCK_CHILD (set, oset);
+
+ job = find_job (pid, 0, NULL);
+
+ if (job != NO_JOB)
+ fprintf (stderr, "[%d] %ld\n", job + 1, (long)pid);
+ else
+ programming_error (_("describe_pid: %ld: no such pid"), (long)pid);
+
+ UNBLOCK_CHILD (oset);
+}
+
+static char *
+j_strsignal (s)
+ int s;
+{
+ char *x;
+
+ x = strsignal (s);
+ if (x == 0)
+ {
+ x = retcode_name_buffer;
+ sprintf (x, _("Signal %d"), s);
+ }
+ return x;
+}
+
+static char *
+printable_job_status (j, p, format)
+ int j;
+ PROCESS *p;
+ int format;
+{
+ static char *temp;
+ int es;
+
+ temp = _("Done");
+
+ if (STOPPED (j) && format == 0)
+ {
+ if (posixly_correct == 0 || p == 0 || (WIFSTOPPED (p->status) == 0))
+ temp = _("Stopped");
+ else
+ {
+ temp = retcode_name_buffer;
+ sprintf (temp, _("Stopped(%s)"), signal_name (WSTOPSIG (p->status)));
+ }
+ }
+ else if (RUNNING (j))
+ temp = _("Running");
+ else
+ {
+ if (WIFSTOPPED (p->status))
+ temp = j_strsignal (WSTOPSIG (p->status));
+ else if (WIFSIGNALED (p->status))
+ temp = j_strsignal (WTERMSIG (p->status));
+ else if (WIFEXITED (p->status))
+ {
+ temp = retcode_name_buffer;
+ es = WEXITSTATUS (p->status);
+ if (es == 0)
+ strcpy (temp, _("Done"));
+ else if (posixly_correct)
+ sprintf (temp, _("Done(%d)"), es);
+ else
+ sprintf (temp, _("Exit %d"), es);
+ }
+ else
+ temp = _("Unknown status");
+ }
+
+ return temp;
+}
+
+/* This is the way to print out information on a job if you
+ know the index. FORMAT is:
+
+ JLIST_NORMAL) [1]+ Running emacs
+ JLIST_LONG ) [1]+ 2378 Running emacs
+ -1 ) [1]+ 2378 emacs
+
+ JLIST_NORMAL) [1]+ Stopped ls | more
+ JLIST_LONG ) [1]+ 2369 Stopped ls
+ 2367 | more
+ JLIST_PID_ONLY)
+ Just list the pid of the process group leader (really
+ the process group).
+ JLIST_CHANGED_ONLY)
+ Use format JLIST_NORMAL, but list only jobs about which
+ the user has not been notified. */
+
+/* Print status for pipeline P. If JOB_INDEX is >= 0, it is the index into
+ the JOBS array corresponding to this pipeline. FORMAT is as described
+ above. Must be called with SIGCHLD blocked.
+
+ If you're printing a pipeline that's not in the jobs array, like the
+ current pipeline as it's being created, pass -1 for JOB_INDEX */
+static void
+print_pipeline (p, job_index, format, stream)
+ PROCESS *p;
+ int job_index, format;
+ FILE *stream;
+{
+ PROCESS *first, *last, *show;
+ int es, name_padding;
+ char *temp;
+
+ if (p == 0)
+ return;
+
+ first = last = p;
+ while (last->next != first)
+ last = last->next;
+
+ for (;;)
+ {
+ if (p != first)
+ fprintf (stream, format ? " " : " |");
+
+ if (format != JLIST_STANDARD)
+ fprintf (stream, "%5ld", (long)p->pid);
+
+ fprintf (stream, " ");
+
+ if (format > -1 && job_index >= 0)
+ {
+ show = format ? p : last;
+ temp = printable_job_status (job_index, show, format);
+
+ if (p != first)
+ {
+ if (format)
+ {
+ if (show->running == first->running &&
+ WSTATUS (show->status) == WSTATUS (first->status))
+ temp = "";
+ }
+ else
+ temp = (char *)NULL;
+ }
+
+ if (temp)
+ {
+ fprintf (stream, "%s", temp);
+
+ es = STRLEN (temp);
+ if (es == 0)
+ es = 2; /* strlen ("| ") */
+ name_padding = LONGEST_SIGNAL_DESC - es;
+
+ fprintf (stream, "%*s", name_padding, "");
+
+ if ((WIFSTOPPED (show->status) == 0) &&
+ (WIFCONTINUED (show->status) == 0) &&
+ WIFCORED (show->status))
+ fprintf (stream, _("(core dumped) "));
+ }
+ }
+
+ if (p != first && format)
+ fprintf (stream, "| ");
+
+ if (p->command)
+ fprintf (stream, "%s", p->command);
+
+ if (p == last && job_index >= 0)
+ {
+ temp = current_working_directory ();
+
+ if (RUNNING (job_index) && (IS_FOREGROUND (job_index) == 0))
+ fprintf (stream, " &");
+
+ if (strcmp (temp, jobs[job_index]->wd) != 0)
+ fprintf (stream,
+ _(" (wd: %s)"), polite_directory_format (jobs[job_index]->wd));
+ }
+
+ if (format || (p == last))
+ {
+ /* We need to add a CR only if this is an interactive shell, and
+ we're reporting the status of a completed job asynchronously.
+ We can't really check whether this particular job is being
+ reported asynchronously, so just add the CR if the shell is
+ currently interactive and asynchronous notification is enabled. */
+ if (asynchronous_notification && interactive)
+ fprintf (stream, "\r\n");
+ else
+ fprintf (stream, "\n");
+ }
+
+ if (p == last)
+ break;
+ p = p->next;
+ }
+ fflush (stream);
+}
+
+/* Print information to STREAM about jobs[JOB_INDEX] according to FORMAT.
+ Must be called with SIGCHLD blocked or queued with queue_sigchld */
+static void
+pretty_print_job (job_index, format, stream)
+ int job_index, format;
+ FILE *stream;
+{
+ register PROCESS *p;
+
+ /* Format only pid information about the process group leader? */
+ if (format == JLIST_PID_ONLY)
+ {
+ fprintf (stream, "%ld\n", (long)jobs[job_index]->pipe->pid);
+ return;
+ }
+
+ if (format == JLIST_CHANGED_ONLY)
+ {
+ if (IS_NOTIFIED (job_index))
+ return;
+ format = JLIST_STANDARD;
+ }
+
+ if (format != JLIST_NONINTERACTIVE)
+ fprintf (stream, "[%d]%c ", job_index + 1,
+ (job_index == js.j_current) ? '+':
+ (job_index == js.j_previous) ? '-' : ' ');
+
+ if (format == JLIST_NONINTERACTIVE)
+ format = JLIST_LONG;
+
+ p = jobs[job_index]->pipe;
+
+ print_pipeline (p, job_index, format, stream);
+
+ /* We have printed information about this job. When the job's
+ status changes, waitchld () sets the notification flag to 0. */
+ jobs[job_index]->flags |= J_NOTIFIED;
+}
+
+static int
+print_job (job, format, state, job_index)
+ JOB *job;
+ int format, state, job_index;
+{
+ if (state == -1 || (JOB_STATE)state == job->state)
+ pretty_print_job (job_index, format, stdout);
+ return (0);
+}
+
+void
+list_one_job (job, format, ignore, job_index)
+ JOB *job;
+ int format, ignore, job_index;
+{
+ pretty_print_job (job_index, format, stdout);
+}
+
+void
+list_stopped_jobs (format)
+ int format;
+{
+ cleanup_dead_jobs ();
+ map_over_jobs (print_job, format, (int)JSTOPPED);
+}
+
+void
+list_running_jobs (format)
+ int format;
+{
+ cleanup_dead_jobs ();
+ map_over_jobs (print_job, format, (int)JRUNNING);
+}
+
+/* List jobs. If FORMAT is non-zero, then the long form of the information
+ is printed, else just a short version. */
+void
+list_all_jobs (format)
+ int format;
+{
+ cleanup_dead_jobs ();
+ map_over_jobs (print_job, format, -1);
+}
+
+/* Fork, handling errors. Returns the pid of the newly made child, or 0.
+ COMMAND is just for remembering the name of the command; we don't do
+ anything else with it. ASYNC_P says what to do with the tty. If
+ non-zero, then don't give it away. */
+pid_t
+make_child (command, async_p)
+ char *command;
+ int async_p;
+{
+ int forksleep;
+ sigset_t set, oset;
+ pid_t pid;
+
+ sigemptyset (&set);
+ sigaddset (&set, SIGCHLD);
+ sigaddset (&set, SIGINT);
+ sigemptyset (&oset);
+ sigprocmask (SIG_BLOCK, &set, &oset);
+
+ making_children ();
+
+ forksleep = 1;
+
+#if defined (BUFFERED_INPUT)
+ /* If default_buffered_input is active, we are reading a script. If
+ the command is asynchronous, we have already duplicated /dev/null
+ as fd 0, but have not changed the buffered stream corresponding to
+ the old fd 0. We don't want to sync the stream in this case. */
+ if (default_buffered_input != -1 &&
+ (!async_p || default_buffered_input > 0))
+ sync_buffered_stream (default_buffered_input);
+#endif /* BUFFERED_INPUT */
+
+ /* Create the child, handle severe errors. Retry on EAGAIN. */
+ while ((pid = fork ()) < 0 && errno == EAGAIN && forksleep < FORKSLEEP_MAX)
+ {
+#if 0 /* for bash-4.2 */
+ /* If we can't create any children, try to reap some dead ones. */
+ waitchld (-1, 0);
+#endif
+ sys_error ("fork: retry");
+ if (sleep (forksleep) != 0)
+ break;
+ forksleep <<= 1;
+ }
+
+ if (pid < 0)
+ {
+ sys_error ("fork");
+
+ /* Kill all of the processes in the current pipeline. */
+ terminate_current_pipeline ();
+
+ /* Discard the current pipeline, if any. */
+ if (the_pipeline)
+ kill_current_pipeline ();
+
+ last_command_exit_value = EX_NOEXEC;
+ throw_to_top_level (); /* Reset signals, etc. */
+ }
+
+ if (pid == 0)
+ {
+ /* In the child. Give this child the right process group, set the
+ signals to the default state for a new process. */
+ pid_t mypid;
+
+ mypid = getpid ();
+#if defined (BUFFERED_INPUT)
+ /* Close default_buffered_input if it's > 0. We don't close it if it's
+ 0 because that's the file descriptor used when redirecting input,
+ and it's wrong to close the file in that case. */
+ unset_bash_input (0);
+#endif /* BUFFERED_INPUT */
+
+ /* Restore top-level signal mask. */
+ sigprocmask (SIG_SETMASK, &top_level_mask, (sigset_t *)NULL);
+
+ if (job_control)
+ {
+ /* All processes in this pipeline belong in the same
+ process group. */
+
+ if (pipeline_pgrp == 0) /* This is the first child. */
+ pipeline_pgrp = mypid;
+
+ /* Check for running command in backquotes. */
+ if (pipeline_pgrp == shell_pgrp)
+ ignore_tty_job_signals ();
+ else
+ default_tty_job_signals ();
+
+ /* Set the process group before trying to mess with the terminal's
+ process group. This is mandated by POSIX. */
+ /* This is in accordance with the Posix 1003.1 standard,
+ section B.7.2.4, which says that trying to set the terminal
+ process group with tcsetpgrp() to an unused pgrp value (like
+ this would have for the first child) is an error. Section
+ B.4.3.3, p. 237 also covers this, in the context of job control
+ shells. */
+ if (setpgid (mypid, pipeline_pgrp) < 0)
+ sys_error (_("child setpgid (%ld to %ld)"), (long)mypid, (long)pipeline_pgrp);
+
+ /* By convention (and assumption above), if
+ pipeline_pgrp == shell_pgrp, we are making a child for
+ command substitution.
+ In this case, we don't want to give the terminal to the
+ shell's process group (we could be in the middle of a
+ pipeline, for example). */
+ if (async_p == 0 && pipeline_pgrp != shell_pgrp && ((subshell_environment&SUBSHELL_ASYNC) == 0))
+ give_terminal_to (pipeline_pgrp, 0);
+
+#if defined (PGRP_PIPE)
+ if (pipeline_pgrp == mypid)
+ pipe_read (pgrp_pipe);
+#endif
+ }
+ else /* Without job control... */
+ {
+ if (pipeline_pgrp == 0)
+ pipeline_pgrp = shell_pgrp;
+
+ /* If these signals are set to SIG_DFL, we encounter the curious
+ situation of an interactive ^Z to a running process *working*
+ and stopping the process, but being unable to do anything with
+ that process to change its state. On the other hand, if they
+ are set to SIG_IGN, jobs started from scripts do not stop when
+ the shell running the script gets a SIGTSTP and stops. */
+
+ default_tty_job_signals ();
+ }
+
+#if defined (PGRP_PIPE)
+ /* Release the process group pipe, since our call to setpgid ()
+ is done. The last call to sh_closepipe is done in stop_pipeline. */
+ sh_closepipe (pgrp_pipe);
+#endif /* PGRP_PIPE */
+
+#if 0
+ /* Don't set last_asynchronous_pid in the child */
+ if (async_p)
+ last_asynchronous_pid = mypid; /* XXX */
+ else
+#endif
+#if defined (RECYCLES_PIDS)
+ if (last_asynchronous_pid == mypid)
+ /* Avoid pid aliasing. 1 seems like a safe, unusual pid value. */
+ last_asynchronous_pid = 1;
+#endif
+ }
+ else
+ {
+ /* In the parent. Remember the pid of the child just created
+ as the proper pgrp if this is the first child. */
+
+ if (first_pid == NO_PID)
+ first_pid = pid;
+ else if (pid_wrap == -1 && pid < first_pid)
+ pid_wrap = 0;
+ else if (pid_wrap == 0 && pid >= first_pid)
+ pid_wrap = 1;
+
+ if (job_control)
+ {
+ if (pipeline_pgrp == 0)
+ {
+ pipeline_pgrp = pid;
+ /* Don't twiddle terminal pgrps in the parent! This is the bug,
+ not the good thing of twiddling them in the child! */
+ /* give_terminal_to (pipeline_pgrp, 0); */
+ }
+ /* This is done on the recommendation of the Rationale section of
+ the POSIX 1003.1 standard, where it discusses job control and
+ shells. It is done to avoid possible race conditions. (Ref.
+ 1003.1 Rationale, section B.4.3.3, page 236). */
+ setpgid (pid, pipeline_pgrp);
+ }
+ else
+ {
+ if (pipeline_pgrp == 0)
+ pipeline_pgrp = shell_pgrp;
+ }
+
+ /* Place all processes into the jobs array regardless of the
+ state of job_control. */
+ add_process (command, pid);
+
+ if (async_p)
+ last_asynchronous_pid = pid;
+#if defined (RECYCLES_PIDS)
+ else if (last_asynchronous_pid == pid)
+ /* Avoid pid aliasing. 1 seems like a safe, unusual pid value. */
+ last_asynchronous_pid = 1;
+#endif
+
+ if (pid_wrap > 0)
+ delete_old_job (pid);
+
+#if !defined (RECYCLES_PIDS)
+ /* Only check for saved status if we've saved more than CHILD_MAX
+ statuses, unless the system recycles pids. */
+ if ((js.c_reaped + bgpids.npid) >= js.c_childmax)
+#endif
+ bgp_delete (pid); /* new process, discard any saved status */
+
+ last_made_pid = pid;
+
+ /* keep stats */
+ js.c_totforked++;
+ js.c_living++;
+
+ /* Unblock SIGINT and SIGCHLD unless creating a pipeline, in which case
+ SIGCHLD remains blocked until all commands in the pipeline have been
+ created. */
+ sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL);
+ }
+
+ return (pid);
+}
+
+/* These two functions are called only in child processes. */
+void
+ignore_tty_job_signals ()
+{
+ set_signal_handler (SIGTSTP, SIG_IGN);
+ set_signal_handler (SIGTTIN, SIG_IGN);
+ set_signal_handler (SIGTTOU, SIG_IGN);
+}
+
+void
+default_tty_job_signals ()
+{
+ set_signal_handler (SIGTSTP, SIG_DFL);
+ set_signal_handler (SIGTTIN, SIG_DFL);
+ set_signal_handler (SIGTTOU, SIG_DFL);
+}
+
+/* When we end a job abnormally, or if we stop a job, we set the tty to the
+ state kept in here. When a job ends normally, we set the state in here
+ to the state of the tty. */
+
+static TTYSTRUCT shell_tty_info;
+
+#if defined (NEW_TTY_DRIVER)
+static struct tchars shell_tchars;
+static struct ltchars shell_ltchars;
+#endif /* NEW_TTY_DRIVER */
+
+#if defined (NEW_TTY_DRIVER) && defined (DRAIN_OUTPUT)
+/* Since the BSD tty driver does not allow us to change the tty modes
+ while simultaneously waiting for output to drain and preserving
+ typeahead, we have to drain the output ourselves before calling
+ ioctl. We cheat by finding the length of the output queue, and
+ using select to wait for an appropriate length of time. This is
+ a hack, and should be labeled as such (it's a hastily-adapted
+ mutation of a `usleep' implementation). It's only reason for
+ existing is the flaw in the BSD tty driver. */
+
+static int ttspeeds[] =
+{
+ 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200,
+ 1800, 2400, 4800, 9600, 19200, 38400
+};
+
+static void
+draino (fd, ospeed)
+ int fd, ospeed;
+{
+ register int delay = ttspeeds[ospeed];
+ int n;
+
+ if (!delay)
+ return;
+
+ while ((ioctl (fd, TIOCOUTQ, &n) == 0) && n)
+ {
+ if (n > (delay / 100))
+ {
+ struct timeval tv;
+
+ n *= 10; /* 2 bits more for conservativeness. */
+ tv.tv_sec = n / delay;
+ tv.tv_usec = ((n % delay) * 1000000) / delay;
+ select (fd, (fd_set *)0, (fd_set *)0, (fd_set *)0, &tv);
+ }
+ else
+ break;
+ }
+}
+#endif /* NEW_TTY_DRIVER && DRAIN_OUTPUT */
+
+/* Return the fd from which we are actually getting input. */
+#define input_tty() (shell_tty != -1) ? shell_tty : fileno (stderr)
+
+/* Fill the contents of shell_tty_info with the current tty info. */
+int
+get_tty_state ()
+{
+ int tty;
+
+ tty = input_tty ();
+ if (tty != -1)
+ {
+#if defined (NEW_TTY_DRIVER)
+ ioctl (tty, TIOCGETP, &shell_tty_info);
+ ioctl (tty, TIOCGETC, &shell_tchars);
+ ioctl (tty, TIOCGLTC, &shell_ltchars);
+#endif /* NEW_TTY_DRIVER */
+
+#if defined (TERMIO_TTY_DRIVER)
+ ioctl (tty, TCGETA, &shell_tty_info);
+#endif /* TERMIO_TTY_DRIVER */
+
+#if defined (TERMIOS_TTY_DRIVER)
+ if (tcgetattr (tty, &shell_tty_info) < 0)
+ {
+#if 0
+ /* Only print an error message if we're really interactive at
+ this time. */
+ if (interactive)
+ sys_error ("[%ld: %d (%d)] tcgetattr", (long)getpid (), shell_level, tty);
+#endif
+ return -1;
+ }
+#endif /* TERMIOS_TTY_DRIVER */
+ if (check_window_size)
+ get_new_window_size (0, (int *)0, (int *)0);
+ }
+ return 0;
+}
+
+/* Make the current tty use the state in shell_tty_info. */
+int
+set_tty_state ()
+{
+ int tty;
+
+ tty = input_tty ();
+ if (tty != -1)
+ {
+#if defined (NEW_TTY_DRIVER)
+# if defined (DRAIN_OUTPUT)
+ draino (tty, shell_tty_info.sg_ospeed);
+# endif /* DRAIN_OUTPUT */
+ ioctl (tty, TIOCSETN, &shell_tty_info);
+ ioctl (tty, TIOCSETC, &shell_tchars);
+ ioctl (tty, TIOCSLTC, &shell_ltchars);
+#endif /* NEW_TTY_DRIVER */
+
+#if defined (TERMIO_TTY_DRIVER)
+ ioctl (tty, TCSETAW, &shell_tty_info);
+#endif /* TERMIO_TTY_DRIVER */
+
+#if defined (TERMIOS_TTY_DRIVER)
+ if (tcsetattr (tty, TCSADRAIN, &shell_tty_info) < 0)
+ {
+ /* Only print an error message if we're really interactive at
+ this time. */
+ if (interactive)
+ sys_error ("[%ld: %d (%d)] tcsetattr", (long)getpid (), shell_level, tty);
+ return -1;
+ }
+#endif /* TERMIOS_TTY_DRIVER */
+ }
+ return 0;
+}
+
+/* Given an index into the jobs array JOB, return the PROCESS struct of the last
+ process in that job's pipeline. This is the one whose exit status
+ counts. Must be called with SIGCHLD blocked or queued. */
+static PROCESS *
+find_last_proc (job, block)
+ int job;
+ int block;
+{
+ register PROCESS *p;
+ sigset_t set, oset;
+
+ if (block)
+ BLOCK_CHILD (set, oset);
+
+ p = jobs[job]->pipe;
+ while (p && p->next != jobs[job]->pipe)
+ p = p->next;
+
+ if (block)
+ UNBLOCK_CHILD (oset);
+
+ return (p);
+}
+
+static pid_t
+find_last_pid (job, block)
+ int job;
+ int block;
+{
+ PROCESS *p;
+
+ p = find_last_proc (job, block);
+ /* Possible race condition here. */
+ return p->pid;
+}
+
+/* Wait for a particular child of the shell to finish executing.
+ This low-level function prints an error message if PID is not
+ a child of this shell. It returns -1 if it fails, or whatever
+ wait_for returns otherwise. If the child is not found in the
+ jobs table, it returns 127. */
+int
+wait_for_single_pid (pid)
+ pid_t pid;
+{
+ register PROCESS *child;
+ sigset_t set, oset;
+ int r, job;
+
+ BLOCK_CHILD (set, oset);
+ child = find_pipeline (pid, 0, (int *)NULL);
+ UNBLOCK_CHILD (oset);
+
+ if (child == 0)
+ {
+ r = bgp_search (pid);
+ if (r >= 0)
+ return r;
+ }
+
+ if (child == 0)
+ {
+ internal_error (_("wait: pid %ld is not a child of this shell"), (long)pid);
+ return (127);
+ }
+
+ r = wait_for (pid);
+
+ /* POSIX.2: if we just waited for a job, we can remove it from the jobs
+ table. */
+ BLOCK_CHILD (set, oset);
+ job = find_job (pid, 0, NULL);
+ if (job != NO_JOB && jobs[job] && DEADJOB (job))
+ jobs[job]->flags |= J_NOTIFIED;
+ UNBLOCK_CHILD (oset);
+
+ /* If running in posix mode, remove the job from the jobs table immediately */
+ if (posixly_correct)
+ {
+ cleanup_dead_jobs ();
+ bgp_delete (pid);
+ }
+
+ return r;
+}
+
+/* Wait for all of the backgrounds of this shell to finish. */
+void
+wait_for_background_pids ()
+{
+ register int i, r, waited_for;
+ sigset_t set, oset;
+ pid_t pid;
+
+ for (waited_for = 0;;)
+ {
+ BLOCK_CHILD (set, oset);
+
+ /* find first running job; if none running in foreground, break */
+ /* XXX could use js.j_firstj and js.j_lastj here */
+ for (i = 0; i < js.j_jobslots; i++)
+ {
+#if defined (DEBUG)
+ if (i < js.j_firstj && jobs[i])
+ itrace("wait_for_background_pids: job %d non-null before js.j_firstj (%d)", i, js.j_firstj);
+ if (i > js.j_lastj && jobs[i])
+ itrace("wait_for_background_pids: job %d non-null after js.j_lastj (%d)", i, js.j_lastj);
+#endif
+ if (jobs[i] && RUNNING (i) && IS_FOREGROUND (i) == 0)
+ break;
+ }
+ if (i == js.j_jobslots)
+ {
+ UNBLOCK_CHILD (oset);
+ break;
+ }
+
+ /* now wait for the last pid in that job. */
+ pid = find_last_pid (i, 0);
+ UNBLOCK_CHILD (oset);
+ QUIT;
+ errno = 0; /* XXX */
+ r = wait_for_single_pid (pid);
+ if (r == -1)
+ {
+ /* If we're mistaken about job state, compensate. */
+ if (errno == ECHILD)
+ mark_all_jobs_as_dead ();
+ }
+ else
+ waited_for++;
+ }
+
+ /* POSIX.2 says the shell can discard the statuses of all completed jobs if
+ `wait' is called with no arguments. */
+ mark_dead_jobs_as_notified (1);
+ cleanup_dead_jobs ();
+ bgp_clear ();
+}
+
+/* Make OLD_SIGINT_HANDLER the SIGINT signal handler. */
+#define INVALID_SIGNAL_HANDLER (SigHandler *)wait_for_background_pids
+static SigHandler *old_sigint_handler = INVALID_SIGNAL_HANDLER;
+
+static void
+restore_sigint_handler ()
+{
+ if (old_sigint_handler != INVALID_SIGNAL_HANDLER)
+ {
+ set_signal_handler (SIGINT, old_sigint_handler);
+ old_sigint_handler = INVALID_SIGNAL_HANDLER;
+ }
+}
+
+static int wait_sigint_received;
+
+/* Handle SIGINT while we are waiting for children in a script to exit.
+ The `wait' builtin should be interruptible, but all others should be
+ effectively ignored (i.e. not cause the shell to exit). */
+static sighandler
+wait_sigint_handler (sig)
+ int sig;
+{
+ SigHandler *sigint_handler;
+
+ if (interrupt_immediately ||
+ (this_shell_builtin && this_shell_builtin == wait_builtin))
+ {
+ last_command_exit_value = EXECUTION_FAILURE;
+ restore_sigint_handler ();
+ /* If we got a SIGINT while in `wait', and SIGINT is trapped, do
+ what POSIX.2 says (see builtins/wait.def for more info). */
+ if (this_shell_builtin && this_shell_builtin == wait_builtin &&
+ signal_is_trapped (SIGINT) &&
+ ((sigint_handler = trap_to_sighandler (SIGINT)) == trap_handler))
+ {
+ interrupt_immediately = 0;
+ trap_handler (SIGINT); /* set pending_traps[SIGINT] */
+ wait_signal_received = SIGINT;
+ longjmp (wait_intr_buf, 1);
+ }
+
+ ADDINTERRUPT;
+ QUIT;
+ }
+
+ /* XXX - should this be interrupt_state? If it is, the shell will act
+ as if it got the SIGINT interrupt. */
+ wait_sigint_received = 1;
+
+ /* Otherwise effectively ignore the SIGINT and allow the running job to
+ be killed. */
+ SIGRETURN (0);
+}
+
+static int
+process_exit_signal (status)
+ WAIT status;
+{
+ return (WIFSIGNALED (status) ? WTERMSIG (status) : 0);
+}
+
+static int
+process_exit_status (status)
+ WAIT status;
+{
+ if (WIFSIGNALED (status))
+ return (128 + WTERMSIG (status));
+ else if (WIFSTOPPED (status) == 0)
+ return (WEXITSTATUS (status));
+ else
+ return (EXECUTION_SUCCESS);
+}
+
+static WAIT
+job_signal_status (job)
+ int job;
+{
+ register PROCESS *p;
+ WAIT s;
+
+ p = jobs[job]->pipe;
+ do
+ {
+ s = p->status;
+ if (WIFSIGNALED(s) || WIFSTOPPED(s))
+ break;
+ p = p->next;
+ }
+ while (p != jobs[job]->pipe);
+
+ return s;
+}
+
+/* Return the exit status of the last process in the pipeline for job JOB.
+ This is the exit status of the entire job. */
+static WAIT
+raw_job_exit_status (job)
+ int job;
+{
+ register PROCESS *p;
+ int fail;
+ WAIT ret;
+
+ if (pipefail_opt)
+ {
+ fail = 0;
+ p = jobs[job]->pipe;
+ do
+ {
+ if (WSTATUS (p->status) != EXECUTION_SUCCESS)
+ fail = WSTATUS(p->status);
+ p = p->next;
+ }
+ while (p != jobs[job]->pipe);
+ WSTATUS (ret) = fail;
+ return ret;
+ }
+
+ for (p = jobs[job]->pipe; p->next != jobs[job]->pipe; p = p->next)
+ ;
+ return (p->status);
+}
+
+/* Return the exit status of job JOB. This is the exit status of the last
+ (rightmost) process in the job's pipeline, modified if the job was killed
+ by a signal or stopped. */
+static int
+job_exit_status (job)
+ int job;
+{
+ return (process_exit_status (raw_job_exit_status (job)));
+}
+
+static int
+job_exit_signal (job)
+ int job;
+{
+ return (process_exit_signal (raw_job_exit_status (job)));
+}
+
+#define FIND_CHILD(pid, child) \
+ do \
+ { \
+ child = find_pipeline (pid, 0, (int *)NULL); \
+ if (child == 0) \
+ { \
+ give_terminal_to (shell_pgrp, 0); \
+ UNBLOCK_CHILD (oset); \
+ internal_error (_("wait_for: No record of process %ld"), (long)pid); \
+ restore_sigint_handler (); \
+ return (termination_state = 127); \
+ } \
+ } \
+ while (0)
+
+/* Wait for pid (one of our children) to terminate, then
+ return the termination state. Returns 127 if PID is not found in
+ the jobs table. Returns -1 if waitchld() returns -1, indicating
+ that there are no unwaited-for child processes. */
+int
+wait_for (pid)
+ pid_t pid;
+{
+ int job, termination_state, r;
+ WAIT s;
+ register PROCESS *child;
+ sigset_t set, oset;
+ register PROCESS *p;
+
+ /* In the case that this code is interrupted, and we longjmp () out of it,
+ we are relying on the code in throw_to_top_level () to restore the
+ top-level signal mask. */
+ BLOCK_CHILD (set, oset);
+
+ /* Ignore interrupts while waiting for a job run without job control
+ to finish. We don't want the shell to exit if an interrupt is
+ received, only if one of the jobs run is killed via SIGINT. If
+ job control is not set, the job will be run in the same pgrp as
+ the shell, and the shell will see any signals the job gets. In
+ fact, we want this set every time the waiting shell and the waited-
+ for process are in the same process group, including command
+ substitution. */
+
+ /* This is possibly a race condition -- should it go in stop_pipeline? */
+ wait_sigint_received = 0;
+ if (job_control == 0 || (subshell_environment&SUBSHELL_COMSUB))
+ {
+ old_sigint_handler = set_signal_handler (SIGINT, wait_sigint_handler);
+ if (old_sigint_handler == SIG_IGN)
+ set_signal_handler (SIGINT, old_sigint_handler);
+ }
+
+ termination_state = last_command_exit_value;
+
+ if (interactive && job_control == 0)
+ QUIT;
+ /* Check for terminating signals and exit the shell if we receive one */
+ CHECK_TERMSIG;
+
+ /* If we say wait_for (), then we have a record of this child somewhere.
+ If it and none of its peers are running, don't call waitchld(). */
+
+ job = NO_JOB;
+ do
+ {
+ FIND_CHILD (pid, child);
+
+ /* If this child is part of a job, then we are really waiting for the
+ job to finish. Otherwise, we are waiting for the child to finish.
+ We check for JDEAD in case the job state has been set by waitchld
+ after receipt of a SIGCHLD. */
+ if (job == NO_JOB)
+ job = find_job (pid, 0, NULL);
+
+ /* waitchld() takes care of setting the state of the job. If the job
+ has already exited before this is called, sigchld_handler will have
+ called waitchld and the state will be set to JDEAD. */
+
+ if (PRUNNING(child) || (job != NO_JOB && RUNNING (job)))
+ {
+#if defined (WAITPID_BROKEN) /* SCOv4 */
+ sigset_t suspend_set;
+ sigemptyset (&suspend_set);
+ sigsuspend (&suspend_set);
+#else /* !WAITPID_BROKEN */
+# if defined (MUST_UNBLOCK_CHLD)
+ struct sigaction act, oact;
+ sigset_t nullset, chldset;
+
+ sigemptyset (&nullset);
+ sigemptyset (&chldset);
+ sigprocmask (SIG_SETMASK, &nullset, &chldset);
+ act.sa_handler = SIG_DFL;
+ sigemptyset (&act.sa_mask);
+ sigemptyset (&oact.sa_mask);
+ act.sa_flags = 0;
+ sigaction (SIGCHLD, &act, &oact);
+# endif
+ queue_sigchld = 1;
+ r = waitchld (pid, 1);
+# if defined (MUST_UNBLOCK_CHLD)
+ sigaction (SIGCHLD, &oact, (struct sigaction *)NULL);
+ sigprocmask (SIG_SETMASK, &chldset, (sigset_t *)NULL);
+# endif
+ queue_sigchld = 0;
+ if (r == -1 && errno == ECHILD && this_shell_builtin == wait_builtin)
+ {
+ termination_state = -1;
+ goto wait_for_return;
+ }
+
+ /* If child is marked as running, but waitpid() returns -1/ECHILD,
+ there is something wrong. Somewhere, wait should have returned
+ that child's pid. Mark the child as not running and the job,
+ if it exists, as JDEAD. */
+ if (r == -1 && errno == ECHILD)
+ {
+ child->running = PS_DONE;
+ WSTATUS (child->status) = 0; /* XXX -- can't find true status */
+ js.c_living = 0; /* no living child processes */
+ if (job != NO_JOB)
+ {
+ jobs[job]->state = JDEAD;
+ js.c_reaped++;
+ js.j_ndead++;
+ }
+ }
+#endif /* WAITPID_BROKEN */
+ }
+
+ /* If the shell is interactive, and job control is disabled, see
+ if the foreground process has died due to SIGINT and jump out
+ of the wait loop if it has. waitchld has already restored the
+ old SIGINT signal handler. */
+ if (interactive && job_control == 0)
+ QUIT;
+ /* Check for terminating signals and exit the shell if we receive one */
+ CHECK_TERMSIG;
+ }
+ while (PRUNNING (child) || (job != NO_JOB && RUNNING (job)));
+
+ /* The exit state of the command is either the termination state of the
+ child, or the termination state of the job. If a job, the status
+ of the last child in the pipeline is the significant one. If the command
+ or job was terminated by a signal, note that value also. */
+ termination_state = (job != NO_JOB) ? job_exit_status (job)
+ : process_exit_status (child->status);
+ last_command_exit_signal = (job != NO_JOB) ? job_exit_signal (job)
+ : process_exit_signal (child->status);
+
+ /* XXX */
+ if ((job != NO_JOB && JOBSTATE (job) == JSTOPPED) || WIFSTOPPED (child->status))
+ termination_state = 128 + WSTOPSIG (child->status);
+
+ if (job == NO_JOB || IS_JOBCONTROL (job))
+ {
+ /* XXX - under what circumstances is a job not present in the jobs
+ table (job == NO_JOB)?
+ 1. command substitution
+
+ In the case of command substitution, at least, it's probably not
+ the right thing to give the terminal to the shell's process group,
+ even though there is code in subst.c:command_substitute to work
+ around it.
+
+ Things that don't:
+ $PROMPT_COMMAND execution
+ process substitution
+ */
+#if 0
+if (job == NO_JOB)
+ itrace("wait_for: job == NO_JOB, giving the terminal to shell_pgrp (%ld)", (long)shell_pgrp);
+#endif
+ give_terminal_to (shell_pgrp, 0);
+ }
+
+ /* If the command did not exit cleanly, or the job is just
+ being stopped, then reset the tty state back to what it
+ was before this command. Reset the tty state and notify
+ the user of the job termination only if the shell is
+ interactive. Clean up any dead jobs in either case. */
+ if (job != NO_JOB)
+ {
+ if (interactive_shell && subshell_environment == 0)
+ {
+ /* This used to use `child->status'. That's wrong, however, for
+ pipelines. `child' is the first process in the pipeline. It's
+ likely that the process we want to check for abnormal termination
+ or stopping is the last process in the pipeline, especially if
+ it's long-lived and the first process is short-lived. Since we
+ know we have a job here, we can check all the processes in this
+ job's pipeline and see if one of them stopped or terminated due
+ to a signal. We might want to change this later to just check
+ the last process in the pipeline. If no process exits due to a
+ signal, S is left as the status of the last job in the pipeline. */
+ s = job_signal_status (job);
+
+ if (WIFSIGNALED (s) || WIFSTOPPED (s))
+ {
+ set_tty_state ();
+
+ /* If the current job was stopped or killed by a signal, and
+ the user has requested it, get a possibly new window size */
+ if (check_window_size && (job == js.j_current || IS_FOREGROUND (job)))
+ get_new_window_size (0, (int *)0, (int *)0);
+ }
+ else
+ get_tty_state ();
+
+ /* If job control is enabled, the job was started with job
+ control, the job was the foreground job, and it was killed
+ by SIGINT, then print a newline to compensate for the kernel
+ printing the ^C without a trailing newline. */
+ if (job_control && IS_JOBCONTROL (job) && IS_FOREGROUND (job) &&
+ WIFSIGNALED (s) && WTERMSIG (s) == SIGINT)
+ {
+ /* If SIGINT is not trapped and the shell is in a for, while,
+ or until loop, act as if the shell received SIGINT as
+ well, so the loop can be broken. This doesn't call the
+ SIGINT signal handler; maybe it should. */
+ if (signal_is_trapped (SIGINT) == 0 && (loop_level || (shell_compatibility_level > 32 && executing_list)))
+ ADDINTERRUPT;
+ else
+ {
+ putchar ('\n');
+ fflush (stdout);
+ }
+ }
+ }
+ else if ((subshell_environment & SUBSHELL_COMSUB) && wait_sigint_received)
+ {
+ /* If waiting for a job in a subshell started to do command
+ substitution, simulate getting and being killed by the SIGINT to
+ pass the status back to our parent. */
+ s = job_signal_status (job);
+
+ if (WIFSIGNALED (s) && WTERMSIG (s) == SIGINT && signal_is_trapped (SIGINT) == 0)
+ {
+ UNBLOCK_CHILD (oset);
+ restore_sigint_handler ();
+ old_sigint_handler = set_signal_handler (SIGINT, SIG_DFL);
+ if (old_sigint_handler == SIG_IGN)
+ restore_sigint_handler ();
+ else
+ kill (getpid (), SIGINT);
+ }
+ }
+
+ /* Moved here from set_job_status_and_cleanup, which is in the SIGCHLD
+ signal handler path */
+ if (DEADJOB (job) && IS_FOREGROUND (job) /*&& subshell_environment == 0*/)
+ setjstatus (job);
+
+ /* If this job is dead, notify the user of the status. If the shell
+ is interactive, this will display a message on the terminal. If
+ the shell is not interactive, make sure we turn on the notify bit
+ so we don't get an unwanted message about the job's termination,
+ and so delete_job really clears the slot in the jobs table. */
+ notify_and_cleanup ();
+ }
+
+wait_for_return:
+
+ UNBLOCK_CHILD (oset);
+
+ /* Restore the original SIGINT signal handler before we return. */
+ restore_sigint_handler ();
+
+ return (termination_state);
+}
+
+/* Wait for the last process in the pipeline for JOB. Returns whatever
+ wait_for returns: the last process's termination state or -1 if there
+ are no unwaited-for child processes or an error occurs. */
+int
+wait_for_job (job)
+ int job;
+{
+ pid_t pid;
+ int r;
+ sigset_t set, oset;
+
+ BLOCK_CHILD(set, oset);
+ if (JOBSTATE (job) == JSTOPPED)
+ internal_warning (_("wait_for_job: job %d is stopped"), job+1);
+
+ pid = find_last_pid (job, 0);
+ UNBLOCK_CHILD(oset);
+ r = wait_for (pid);
+
+ /* POSIX.2: we can remove the job from the jobs table if we just waited
+ for it. */
+ BLOCK_CHILD (set, oset);
+ if (job != NO_JOB && jobs[job] && DEADJOB (job))
+ jobs[job]->flags |= J_NOTIFIED;
+ UNBLOCK_CHILD (oset);
+
+ return r;
+}
+
+/* Print info about dead jobs, and then delete them from the list
+ of known jobs. This does not actually delete jobs when the
+ shell is not interactive, because the dead jobs are not marked
+ as notified. */
+void
+notify_and_cleanup ()
+{
+ if (jobs_list_frozen)
+ return;
+
+ if (interactive || interactive_shell == 0 || sourcelevel)
+ notify_of_job_status ();
+
+ cleanup_dead_jobs ();
+}
+
+/* Make dead jobs disappear from the jobs array without notification.
+ This is used when the shell is not interactive. */
+void
+reap_dead_jobs ()
+{
+ mark_dead_jobs_as_notified (0);
+ cleanup_dead_jobs ();
+}
+
+/* Return the next closest (chronologically) job to JOB which is in
+ STATE. STATE can be JSTOPPED, JRUNNING. NO_JOB is returned if
+ there is no next recent job. */
+static int
+most_recent_job_in_state (job, state)
+ int job;
+ JOB_STATE state;
+{
+ register int i, result;
+ sigset_t set, oset;
+
+ BLOCK_CHILD (set, oset);
+
+ for (result = NO_JOB, i = job - 1; i >= 0; i--)
+ {
+ if (jobs[i] && (JOBSTATE (i) == state))
+ {
+ result = i;
+ break;
+ }
+ }
+
+ UNBLOCK_CHILD (oset);
+
+ return (result);
+}
+
+/* Return the newest *stopped* job older than JOB, or NO_JOB if not
+ found. */
+static int
+job_last_stopped (job)
+ int job;
+{
+ return (most_recent_job_in_state (job, JSTOPPED));
+}
+
+/* Return the newest *running* job older than JOB, or NO_JOB if not
+ found. */
+static int
+job_last_running (job)
+ int job;
+{
+ return (most_recent_job_in_state (job, JRUNNING));
+}
+
+/* Make JOB be the current job, and make previous be useful. Must be
+ called with SIGCHLD blocked. */
+static void
+set_current_job (job)
+ int job;
+{
+ int candidate;
+
+ if (js.j_current != job)
+ {
+ js.j_previous = js.j_current;
+ js.j_current = job;
+ }
+
+ /* First choice for previous job is the old current job. */
+ if (js.j_previous != js.j_current &&
+ js.j_previous != NO_JOB &&
+ jobs[js.j_previous] &&
+ STOPPED (js.j_previous))
+ return;
+
+ /* Second choice: Newest stopped job that is older than
+ the current job. */
+ candidate = NO_JOB;
+ if (STOPPED (js.j_current))
+ {
+ candidate = job_last_stopped (js.j_current);
+
+ if (candidate != NO_JOB)
+ {
+ js.j_previous = candidate;
+ return;
+ }
+ }
+
+ /* If we get here, there is either only one stopped job, in which case it is
+ the current job and the previous job should be set to the newest running
+ job, or there are only running jobs and the previous job should be set to
+ the newest running job older than the current job. We decide on which
+ alternative to use based on whether or not JOBSTATE(js.j_current) is
+ JSTOPPED. */
+
+ candidate = RUNNING (js.j_current) ? job_last_running (js.j_current)
+ : job_last_running (js.j_jobslots);
+
+ if (candidate != NO_JOB)
+ {
+ js.j_previous = candidate;
+ return;
+ }
+
+ /* There is only a single job, and it is both `+' and `-'. */
+ js.j_previous = js.j_current;
+}
+
+/* Make current_job be something useful, if it isn't already. */
+
+/* Here's the deal: The newest non-running job should be `+', and the
+ next-newest non-running job should be `-'. If there is only a single
+ stopped job, the js.j_previous is the newest non-running job. If there
+ are only running jobs, the newest running job is `+' and the
+ next-newest running job is `-'. Must be called with SIGCHLD blocked. */
+
+static void
+reset_current ()
+{
+ int candidate;
+
+ if (js.j_jobslots && js.j_current != NO_JOB && jobs[js.j_current] && STOPPED (js.j_current))
+ candidate = js.j_current;
+ else
+ {
+ candidate = NO_JOB;
+
+ /* First choice: the previous job. */
+ if (js.j_previous != NO_JOB && jobs[js.j_previous] && STOPPED (js.j_previous))
+ candidate = js.j_previous;
+
+ /* Second choice: the most recently stopped job. */
+ if (candidate == NO_JOB)
+ candidate = job_last_stopped (js.j_jobslots);
+
+ /* Third choice: the newest running job. */
+ if (candidate == NO_JOB)
+ candidate = job_last_running (js.j_jobslots);
+ }
+
+ /* If we found a job to use, then use it. Otherwise, there
+ are no jobs period. */
+ if (candidate != NO_JOB)
+ set_current_job (candidate);
+ else
+ js.j_current = js.j_previous = NO_JOB;
+}
+
+/* Set up the job structures so we know the job and its processes are
+ all running. */
+static void
+set_job_running (job)
+ int job;
+{
+ register PROCESS *p;
+
+ /* Each member of the pipeline is now running. */
+ p = jobs[job]->pipe;
+
+ do
+ {
+ if (WIFSTOPPED (p->status))
+ p->running = PS_RUNNING; /* XXX - could be PS_STOPPED */
+ p = p->next;
+ }
+ while (p != jobs[job]->pipe);
+
+ /* This means that the job is running. */
+ JOBSTATE (job) = JRUNNING;
+}
+
+/* Start a job. FOREGROUND if non-zero says to do that. Otherwise,
+ start the job in the background. JOB is a zero-based index into
+ JOBS. Returns -1 if it is unable to start a job, and the return
+ status of the job otherwise. */
+int
+start_job (job, foreground)
+ int job, foreground;
+{
+ register PROCESS *p;
+ int already_running;
+ sigset_t set, oset;
+ char *wd, *s;
+ static TTYSTRUCT save_stty;
+
+ BLOCK_CHILD (set, oset);
+
+ if (DEADJOB (job))
+ {
+ internal_error (_("%s: job has terminated"), this_command_name);
+ UNBLOCK_CHILD (oset);
+ return (-1);
+ }
+
+ already_running = RUNNING (job);
+
+ if (foreground == 0 && already_running)
+ {
+ internal_error (_("%s: job %d already in background"), this_command_name, job + 1);
+ UNBLOCK_CHILD (oset);
+ return (0); /* XPG6/SUSv3 says this is not an error */
+ }
+
+ wd = current_working_directory ();
+
+ /* You don't know about the state of this job. Do you? */
+ jobs[job]->flags &= ~J_NOTIFIED;
+
+ if (foreground)
+ {
+ set_current_job (job);
+ jobs[job]->flags |= J_FOREGROUND;
+ }
+
+ /* Tell the outside world what we're doing. */
+ p = jobs[job]->pipe;
+
+ if (foreground == 0)
+ {
+ /* POSIX.2 says `bg' doesn't give any indication about current or
+ previous job. */
+ if (posixly_correct == 0)
+ s = (job == js.j_current) ? "+ ": ((job == js.j_previous) ? "- " : " ");
+ else
+ s = " ";
+ printf ("[%d]%s", job + 1, s);
+ }
+
+ do
+ {
+ printf ("%s%s",
+ p->command ? p->command : "",
+ p->next != jobs[job]->pipe? " | " : "");
+ p = p->next;
+ }
+ while (p != jobs[job]->pipe);
+
+ if (foreground == 0)
+ printf (" &");
+
+ if (strcmp (wd, jobs[job]->wd) != 0)
+ printf (" (wd: %s)", polite_directory_format (jobs[job]->wd));
+
+ printf ("\n");
+
+ /* Run the job. */
+ if (already_running == 0)
+ set_job_running (job);
+
+ /* Save the tty settings before we start the job in the foreground. */
+ if (foreground)
+ {
+ get_tty_state ();
+ save_stty = shell_tty_info;
+ /* Give the terminal to this job. */
+ if (IS_JOBCONTROL (job))
+ give_terminal_to (jobs[job]->pgrp, 0);
+ }
+ else
+ jobs[job]->flags &= ~J_FOREGROUND;
+
+ /* If the job is already running, then don't bother jump-starting it. */
+ if (already_running == 0)
+ {
+ jobs[job]->flags |= J_NOTIFIED;
+ killpg (jobs[job]->pgrp, SIGCONT);
+ }
+
+ if (foreground)
+ {
+ pid_t pid;
+ int st;
+
+ pid = find_last_pid (job, 0);
+ UNBLOCK_CHILD (oset);
+ st = wait_for (pid);
+ shell_tty_info = save_stty;
+ set_tty_state ();
+ return (st);
+ }
+ else
+ {
+ reset_current ();
+ UNBLOCK_CHILD (oset);
+ return (0);
+ }
+}
+
+/* Give PID SIGNAL. This determines what job the pid belongs to (if any).
+ If PID does belong to a job, and the job is stopped, then CONTinue the
+ job after giving it SIGNAL. Returns -1 on failure. If GROUP is non-null,
+ then kill the process group associated with PID. */
+int
+kill_pid (pid, sig, group)
+ pid_t pid;
+ int sig, group;
+{
+ register PROCESS *p;
+ int job, result, negative;
+ sigset_t set, oset;
+
+ if (pid < -1)
+ {
+ pid = -pid;
+ group = negative = 1;
+ }
+ else
+ negative = 0;
+
+ result = EXECUTION_SUCCESS;
+ if (group)
+ {
+ BLOCK_CHILD (set, oset);
+ p = find_pipeline (pid, 0, &job);
+
+ if (job != NO_JOB)
+ {
+ jobs[job]->flags &= ~J_NOTIFIED;
+
+ /* Kill process in backquotes or one started without job control? */
+
+ /* If we're passed a pid < -1, just call killpg and see what happens */
+ if (negative && jobs[job]->pgrp == shell_pgrp)
+ result = killpg (pid, sig);
+ /* If we're killing using job control notification, for example,
+ without job control active, we have to do things ourselves. */
+ else if (jobs[job]->pgrp == shell_pgrp)
+ {
+ p = jobs[job]->pipe;
+ do
+ {
+ if (PALIVE (p) == 0)
+ continue; /* avoid pid recycling problem */
+ kill (p->pid, sig);
+ if (PEXITED (p) && (sig == SIGTERM || sig == SIGHUP))
+ kill (p->pid, SIGCONT);
+ p = p->next;
+ }
+ while (p != jobs[job]->pipe);
+ }
+ else
+ {
+ result = killpg (jobs[job]->pgrp, sig);
+ if (p && STOPPED (job) && (sig == SIGTERM || sig == SIGHUP))
+ killpg (jobs[job]->pgrp, SIGCONT);
+ /* If we're continuing a stopped job via kill rather than bg or
+ fg, emulate the `bg' behavior. */
+ if (p && STOPPED (job) && (sig == SIGCONT))
+ {
+ set_job_running (job);
+ jobs[job]->flags &= ~J_FOREGROUND;
+ jobs[job]->flags |= J_NOTIFIED;
+ }
+ }
+ }
+ else
+ result = killpg (pid, sig);
+
+ UNBLOCK_CHILD (oset);
+ }
+ else
+ result = kill (pid, sig);
+
+ return (result);
+}
+
+/* sigchld_handler () flushes at least one of the children that we are
+ waiting for. It gets run when we have gotten a SIGCHLD signal. */
+static sighandler
+sigchld_handler (sig)
+ int sig;
+{
+ int n, oerrno;
+
+ oerrno = errno;
+ REINSTALL_SIGCHLD_HANDLER;
+ sigchld++;
+ n = 0;
+ if (queue_sigchld == 0)
+ n = waitchld (-1, 0);
+ errno = oerrno;
+ SIGRETURN (n);
+}
+
+/* waitchld() reaps dead or stopped children. It's called by wait_for and
+ sigchld_handler, and runs until there aren't any children terminating any
+ more.
+ If BLOCK is 1, this is to be a blocking wait for a single child, although
+ an arriving SIGCHLD could cause the wait to be non-blocking. It returns
+ the number of children reaped, or -1 if there are no unwaited-for child
+ processes. */
+static int
+waitchld (wpid, block)
+ pid_t wpid;
+ int block;
+{
+ WAIT status;
+ PROCESS *child;
+ pid_t pid;
+ int call_set_current, last_stopped_job, job, children_exited, waitpid_flags;
+ static int wcontinued = WCONTINUED; /* run-time fix for glibc problem */
+
+ call_set_current = children_exited = 0;
+ last_stopped_job = NO_JOB;
+
+ do
+ {
+ /* We don't want to be notified about jobs stopping if job control
+ is not active. XXX - was interactive_shell instead of job_control */
+ waitpid_flags = (job_control && subshell_environment == 0)
+ ? (WUNTRACED|wcontinued)
+ : 0;
+ if (sigchld || block == 0)
+ waitpid_flags |= WNOHANG;
+ /* Check for terminating signals and exit the shell if we receive one */
+ CHECK_TERMSIG;
+
+ if (block == 1 && queue_sigchld == 0 && (waitpid_flags & WNOHANG) == 0)
+ {
+ internal_warning (_("waitchld: turning on WNOHANG to avoid indefinite block"));
+ waitpid_flags |= WNOHANG;
+ }
+
+ pid = WAITPID (-1, &status, waitpid_flags);
+
+ /* WCONTINUED may be rejected by waitpid as invalid even when defined */
+ if (wcontinued && pid < 0 && errno == EINVAL)
+ {
+ wcontinued = 0;
+ continue; /* jump back to the test and retry without WCONTINUED */
+ }
+
+ /* The check for WNOHANG is to make sure we decrement sigchld only
+ if it was non-zero before we called waitpid. */
+ if (sigchld > 0 && (waitpid_flags & WNOHANG))
+ sigchld--;
+
+ /* If waitpid returns -1 with errno == ECHILD, there are no more
+ unwaited-for child processes of this shell. */
+ if (pid < 0 && errno == ECHILD)
+ {
+ if (children_exited == 0)
+ return -1;
+ else
+ break;
+ }
+
+ /* If waitpid returns 0, there are running children. If it returns -1,
+ the only other error POSIX says it can return is EINTR. */
+ CHECK_TERMSIG;
+ if (pid <= 0)
+ continue; /* jumps right to the test */
+
+ /* children_exited is used to run traps on SIGCHLD. We don't want to
+ run the trap if a process is just being continued. */
+ if (WIFCONTINUED(status) == 0)
+ {
+ children_exited++;
+ js.c_living--;
+ }
+
+ /* Locate our PROCESS for this pid. */
+ child = find_process (pid, 1, &job); /* want living procs only */
+
+#if defined (COPROCESS_SUPPORT)
+ coproc_pidchk (pid, status);
+#endif
+
+ /* It is not an error to have a child terminate that we did
+ not have a record of. This child could have been part of
+ a pipeline in backquote substitution. Even so, I'm not
+ sure child is ever non-zero. */
+ if (child == 0)
+ {
+ if (WIFEXITED (status) || WIFSIGNALED (status))
+ js.c_reaped++;
+ continue;
+ }
+
+ /* Remember status, and whether or not the process is running. */
+ child->status = status;
+ child->running = WIFCONTINUED(status) ? PS_RUNNING : PS_DONE;
+
+ if (PEXITED (child))
+ {
+ js.c_totreaped++;
+ if (job != NO_JOB)
+ js.c_reaped++;
+ }
+
+ if (job == NO_JOB)
+ continue;
+
+ call_set_current += set_job_status_and_cleanup (job);
+
+ if (STOPPED (job))
+ last_stopped_job = job;
+ else if (DEADJOB (job) && last_stopped_job == job)
+ last_stopped_job = NO_JOB;
+ }
+ while ((sigchld || block == 0) && pid > (pid_t)0);
+
+ /* If a job was running and became stopped, then set the current
+ job. Otherwise, don't change a thing. */
+ if (call_set_current)
+ {
+ if (last_stopped_job != NO_JOB)
+ set_current_job (last_stopped_job);
+ else
+ reset_current ();
+ }
+
+ /* Call a SIGCHLD trap handler for each child that exits, if one is set. */
+ if (job_control && signal_is_trapped (SIGCHLD) && children_exited &&
+ trap_list[SIGCHLD] != (char *)IGNORE_SIG)
+ {
+ if (posixly_correct && this_shell_builtin && this_shell_builtin == wait_builtin)
+ {
+ interrupt_immediately = 0;
+ trap_handler (SIGCHLD); /* set pending_traps[SIGCHLD] */
+ wait_signal_received = SIGCHLD;
+ longjmp (wait_intr_buf, 1);
+ }
+
+ run_sigchld_trap (children_exited);
+ }
+
+ /* We have successfully recorded the useful information about this process
+ that has just changed state. If we notify asynchronously, and the job
+ that this process belongs to is no longer running, then notify the user
+ of that fact now. */
+ if (asynchronous_notification && interactive)
+ notify_of_job_status ();
+
+ return (children_exited);
+}
+
+/* Set the status of JOB and perform any necessary cleanup if the job is
+ marked as JDEAD.
+
+ Currently, the cleanup activity is restricted to handling any SIGINT
+ received while waiting for a foreground job to finish. */
+static int
+set_job_status_and_cleanup (job)
+ int job;
+{
+ PROCESS *child;
+ int tstatus, job_state, any_stopped, any_tstped, call_set_current;
+ SigHandler *temp_handler;
+
+ child = jobs[job]->pipe;
+ jobs[job]->flags &= ~J_NOTIFIED;
+
+ call_set_current = 0;
+
+ /*
+ * COMPUTE JOB STATUS
+ */
+
+ /* If all children are not running, but any of them is stopped, then
+ the job is stopped, not dead. */
+ job_state = any_stopped = any_tstped = 0;
+ do
+ {
+ job_state |= PRUNNING (child);
+#if 0
+ if (PEXITED (child) && (WIFSTOPPED (child->status)))
+#else
+ /* Only checking for WIFSTOPPED now, not for PS_DONE */
+ if (PSTOPPED (child))
+#endif
+ {
+ any_stopped = 1;
+ any_tstped |= interactive && job_control &&
+ (WSTOPSIG (child->status) == SIGTSTP);
+ }
+ child = child->next;
+ }
+ while (child != jobs[job]->pipe);
+
+ /* If job_state != 0, the job is still running, so don't bother with
+ setting the process exit status and job state unless we're
+ transitioning from stopped to running. */
+ if (job_state != 0 && JOBSTATE(job) != JSTOPPED)
+ return 0;
+
+ /*
+ * SET JOB STATUS
+ */
+
+ /* The job is either stopped or dead. Set the state of the job accordingly. */
+ if (any_stopped)
+ {
+ jobs[job]->state = JSTOPPED;
+ jobs[job]->flags &= ~J_FOREGROUND;
+ call_set_current++;
+ /* Suspending a job with SIGTSTP breaks all active loops. */
+ if (any_tstped && loop_level)
+ breaking = loop_level;
+ }
+ else if (job_state != 0) /* was stopped, now running */
+ {
+ jobs[job]->state = JRUNNING;
+ call_set_current++;
+ }
+ else
+ {
+ jobs[job]->state = JDEAD;
+ js.j_ndead++;
+
+#if 0
+ if (IS_FOREGROUND (job))
+ setjstatus (job);
+#endif
+
+ /* If this job has a cleanup function associated with it, call it
+ with `cleanarg' as the single argument, then set the function
+ pointer to NULL so it is not inadvertently called twice. The
+ cleanup function is responsible for deallocating cleanarg. */
+ if (jobs[job]->j_cleanup)
+ {
+ (*jobs[job]->j_cleanup) (jobs[job]->cleanarg);
+ jobs[job]->j_cleanup = (sh_vptrfunc_t *)NULL;
+ }
+ }
+
+ /*
+ * CLEANUP
+ *
+ * Currently, we just do special things if we got a SIGINT while waiting
+ * for a foreground job to complete
+ */
+
+ if (JOBSTATE (job) == JDEAD)
+ {
+ /* If we're running a shell script and we get a SIGINT with a
+ SIGINT trap handler, but the foreground job handles it and
+ does not exit due to SIGINT, run the trap handler but do not
+ otherwise act as if we got the interrupt. */
+ if (wait_sigint_received && interactive_shell == 0 &&
+ WIFSIGNALED (child->status) == 0 && IS_FOREGROUND (job) &&
+ signal_is_trapped (SIGINT))
+ {
+ int old_frozen;
+ wait_sigint_received = 0;
+ last_command_exit_value = process_exit_status (child->status);
+
+ old_frozen = jobs_list_frozen;
+ jobs_list_frozen = 1;
+ tstatus = maybe_call_trap_handler (SIGINT);
+ jobs_list_frozen = old_frozen;
+ }
+
+ /* If the foreground job is killed by SIGINT when job control is not
+ active, we need to perform some special handling.
+
+ The check of wait_sigint_received is a way to determine if the
+ SIGINT came from the keyboard (in which case the shell has already
+ seen it, and wait_sigint_received is non-zero, because keyboard
+ signals are sent to process groups) or via kill(2) to the foreground
+ process by another process (or itself). If the shell did receive the
+ SIGINT, it needs to perform normal SIGINT processing. */
+ else if (wait_sigint_received && (WTERMSIG (child->status) == SIGINT) &&
+ IS_FOREGROUND (job) && IS_JOBCONTROL (job) == 0)
+ {
+ int old_frozen;
+
+ wait_sigint_received = 0;
+
+ /* If SIGINT is trapped, set the exit status so that the trap
+ handler can see it. */
+ if (signal_is_trapped (SIGINT))
+ last_command_exit_value = process_exit_status (child->status);
+
+ /* If the signal is trapped, let the trap handler get it no matter
+ what and simply return if the trap handler returns.
+ maybe_call_trap_handler() may cause dead jobs to be removed from
+ the job table because of a call to execute_command. We work
+ around this by setting JOBS_LIST_FROZEN. */
+ old_frozen = jobs_list_frozen;
+ jobs_list_frozen = 1;
+ tstatus = maybe_call_trap_handler (SIGINT);
+ jobs_list_frozen = old_frozen;
+ if (tstatus == 0 && old_sigint_handler != INVALID_SIGNAL_HANDLER)
+ {
+ /* wait_sigint_handler () has already seen SIGINT and
+ allowed the wait builtin to jump out. We need to
+ call the original SIGINT handler, if necessary. If
+ the original handler is SIG_DFL, we need to resend
+ the signal to ourselves. */
+
+ temp_handler = old_sigint_handler;
+
+ /* Bogus. If we've reset the signal handler as the result
+ of a trap caught on SIGINT, then old_sigint_handler
+ will point to trap_handler, which now knows nothing about
+ SIGINT (if we reset the sighandler to the default).
+ In this case, we have to fix things up. What a crock. */
+ if (temp_handler == trap_handler && signal_is_trapped (SIGINT) == 0)
+ temp_handler = trap_to_sighandler (SIGINT);
+ restore_sigint_handler ();
+ if (temp_handler == SIG_DFL)
+ termsig_handler (SIGINT);
+ else if (temp_handler != SIG_IGN)
+ (*temp_handler) (SIGINT);
+ }
+ }
+ }
+
+ return call_set_current;
+}
+
+/* Build the array of values for the $PIPESTATUS variable from the set of
+ exit statuses of all processes in the job J. */
+static void
+setjstatus (j)
+ int j;
+{
+#if defined (ARRAY_VARS)
+ register int i;
+ register PROCESS *p;
+
+ for (i = 1, p = jobs[j]->pipe; p->next != jobs[j]->pipe; p = p->next, i++)
+ ;
+ i++;
+ if (statsize < i)
+ {
+ pstatuses = (int *)xrealloc (pstatuses, i * sizeof (int));
+ statsize = i;
+ }
+ i = 0;
+ p = jobs[j]->pipe;
+ do
+ {
+ pstatuses[i++] = process_exit_status (p->status);
+ p = p->next;
+ }
+ while (p != jobs[j]->pipe);
+
+ pstatuses[i] = -1; /* sentinel */
+ set_pipestatus_array (pstatuses, i);
+#endif
+}
+
+void
+run_sigchld_trap (nchild)
+ int nchild;
+{
+ char *trap_command;
+ int i;
+
+ /* Turn off the trap list during the call to parse_and_execute ()
+ to avoid potentially infinite recursive calls. Preserve the
+ values of last_command_exit_value, last_made_pid, and the_pipeline
+ around the execution of the trap commands. */
+ trap_command = savestring (trap_list[SIGCHLD]);
+
+ begin_unwind_frame ("SIGCHLD trap");
+ unwind_protect_int (last_command_exit_value);
+ unwind_protect_int (last_command_exit_signal);
+ unwind_protect_var (last_made_pid);
+ unwind_protect_int (interrupt_immediately);
+ unwind_protect_int (jobs_list_frozen);
+ unwind_protect_pointer (the_pipeline);
+ unwind_protect_pointer (subst_assign_varlist);
+
+ /* We have to add the commands this way because they will be run
+ in reverse order of adding. We don't want maybe_set_sigchld_trap ()
+ to reference freed memory. */
+ add_unwind_protect (xfree, trap_command);
+ add_unwind_protect (maybe_set_sigchld_trap, trap_command);
+
+ subst_assign_varlist = (WORD_LIST *)NULL;
+ the_pipeline = (PROCESS *)NULL;
+
+ set_impossible_sigchld_trap ();
+ jobs_list_frozen = 1;
+ for (i = 0; i < nchild; i++)
+ {
+ interrupt_immediately = 1;
+ parse_and_execute (savestring (trap_command), "trap", SEVAL_NOHIST|SEVAL_RESETLINE);
+ }
+
+ run_unwind_frame ("SIGCHLD trap");
+}
+
+/* Function to call when you want to notify people of changes
+ in job status. This prints out all jobs which are pending
+ notification to stderr, and marks those printed as already
+ notified, thus making them candidates for cleanup. */
+static void
+notify_of_job_status ()
+{
+ register int job, termsig;
+ char *dir;
+ sigset_t set, oset;
+ WAIT s;
+
+ if (jobs == 0 || js.j_jobslots == 0)
+ return;
+
+ if (old_ttou != 0)
+ {
+ sigemptyset (&set);
+ sigaddset (&set, SIGCHLD);
+ sigaddset (&set, SIGTTOU);
+ sigemptyset (&oset);
+ sigprocmask (SIG_BLOCK, &set, &oset);
+ }
+ else
+ queue_sigchld++;
+
+ /* XXX could use js.j_firstj here */
+ for (job = 0, dir = (char *)NULL; job < js.j_jobslots; job++)
+ {
+ if (jobs[job] && IS_NOTIFIED (job) == 0)
+ {
+ s = raw_job_exit_status (job);
+ termsig = WTERMSIG (s);
+
+ /* POSIX.2 says we have to hang onto the statuses of at most the
+ last CHILD_MAX background processes if the shell is running a
+ script. If the shell is running a script, either from a file
+ or standard input, don't print anything unless the job was
+ killed by a signal. */
+ if (startup_state == 0 && WIFSIGNALED (s) == 0 &&
+ ((DEADJOB (job) && IS_FOREGROUND (job) == 0) || STOPPED (job)))
+ continue;
+
+#if 0
+ /* If job control is disabled, don't print the status messages.
+ Mark dead jobs as notified so that they get cleaned up. If
+ startup_state == 2, we were started to run `-c command', so
+ don't print anything. */
+ if ((job_control == 0 && interactive_shell) || startup_state == 2)
+#else
+ /* If job control is disabled, don't print the status messages.
+ Mark dead jobs as notified so that they get cleaned up. If
+ startup_state == 2 and subshell_environment has the
+ SUBSHELL_COMSUB bit turned on, we were started to run a command
+ substitution, so don't print anything. */
+ if ((job_control == 0 && interactive_shell) ||
+ (startup_state == 2 && (subshell_environment & SUBSHELL_COMSUB)))
+#endif
+ {
+ /* POSIX.2 compatibility: if the shell is not interactive,
+ hang onto the job corresponding to the last asynchronous
+ pid until the user has been notified of its status or does
+ a `wait'. */
+ if (DEADJOB (job) && (interactive_shell || (find_last_pid (job, 0) != last_asynchronous_pid)))
+ jobs[job]->flags |= J_NOTIFIED;
+ continue;
+ }
+
+ /* Print info on jobs that are running in the background,
+ and on foreground jobs that were killed by anything
+ except SIGINT (and possibly SIGPIPE). */
+ switch (JOBSTATE (job))
+ {
+ case JDEAD:
+ if (interactive_shell == 0 && termsig && WIFSIGNALED (s) &&
+ termsig != SIGINT &&
+#if defined (DONT_REPORT_SIGPIPE)
+ termsig != SIGPIPE &&
+#endif
+ signal_is_trapped (termsig) == 0)
+ {
+ /* Don't print `0' for a line number. */
+ fprintf (stderr, _("%s: line %d: "), get_name_for_error (), (line_number == 0) ? 1 : line_number);
+ pretty_print_job (job, JLIST_NONINTERACTIVE, stderr);
+ }
+ else if (IS_FOREGROUND (job))
+ {
+#if !defined (DONT_REPORT_SIGPIPE)
+ if (termsig && WIFSIGNALED (s) && termsig != SIGINT)
+#else
+ if (termsig && WIFSIGNALED (s) && termsig != SIGINT && termsig != SIGPIPE)
+#endif
+ {
+ fprintf (stderr, "%s", j_strsignal (termsig));
+
+ if (WIFCORED (s))
+ fprintf (stderr, _(" (core dumped)"));
+
+ fprintf (stderr, "\n");
+ }
+ }
+ else if (job_control) /* XXX job control test added */
+ {
+ if (dir == 0)
+ dir = current_working_directory ();
+ pretty_print_job (job, JLIST_STANDARD, stderr);
+ if (dir && strcmp (dir, jobs[job]->wd) != 0)
+ fprintf (stderr,
+ _("(wd now: %s)\n"), polite_directory_format (dir));
+ }
+
+ jobs[job]->flags |= J_NOTIFIED;
+ break;
+
+ case JSTOPPED:
+ fprintf (stderr, "\n");
+ if (dir == 0)
+ dir = current_working_directory ();
+ pretty_print_job (job, JLIST_STANDARD, stderr);
+ if (dir && (strcmp (dir, jobs[job]->wd) != 0))
+ fprintf (stderr,
+ _("(wd now: %s)\n"), polite_directory_format (dir));
+ jobs[job]->flags |= J_NOTIFIED;
+ break;
+
+ case JRUNNING:
+ case JMIXED:
+ break;
+
+ default:
+ programming_error ("notify_of_job_status");
+ }
+ }
+ }
+ if (old_ttou != 0)
+ sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL);
+ else
+ queue_sigchld--;
+}
+
+/* Initialize the job control mechanism, and set up the tty stuff. */
+int
+initialize_job_control (force)
+ int force;
+{
+ pid_t t;
+ int t_errno;
+
+ t_errno = -1;
+ shell_pgrp = getpgid (0);
+
+ if (shell_pgrp == -1)
+ {
+ sys_error (_("initialize_job_control: getpgrp failed"));
+ exit (1);
+ }
+
+ /* We can only have job control if we are interactive. */
+ if (interactive == 0)
+ {
+ job_control = 0;
+ original_pgrp = NO_PID;
+ shell_tty = fileno (stderr);
+ }
+ else
+ {
+ shell_tty = -1;
+
+ /* If forced_interactive is set, we skip the normal check that stderr
+ is attached to a tty, so we need to check here. If it's not, we
+ need to see whether we have a controlling tty by opening /dev/tty,
+ since trying to use job control tty pgrp manipulations on a non-tty
+ is going to fail. */
+ if (forced_interactive && isatty (fileno (stderr)) == 0)
+ shell_tty = open ("/dev/tty", O_RDWR|O_NONBLOCK);
+
+ /* Get our controlling terminal. If job_control is set, or
+ interactive is set, then this is an interactive shell no
+ matter where fd 2 is directed. */
+ if (shell_tty == -1)
+ shell_tty = dup (fileno (stderr)); /* fd 2 */
+
+ shell_tty = move_to_high_fd (shell_tty, 1, -1);
+
+ /* Compensate for a bug in systems that compiled the BSD
+ rlogind with DEBUG defined, like NeXT and Alliant. */
+ if (shell_pgrp == 0)
+ {
+ shell_pgrp = getpid ();
+ setpgid (0, shell_pgrp);
+ tcsetpgrp (shell_tty, shell_pgrp);
+ }
+
+ while ((terminal_pgrp = tcgetpgrp (shell_tty)) != -1)
+ {
+ if (shell_pgrp != terminal_pgrp)
+ {
+ SigHandler *ottin;
+
+ ottin = set_signal_handler(SIGTTIN, SIG_DFL);
+ kill (0, SIGTTIN);
+ set_signal_handler (SIGTTIN, ottin);
+ continue;
+ }
+ break;
+ }
+
+ if (terminal_pgrp == -1)
+ t_errno = errno;
+
+ /* Make sure that we are using the new line discipline. */
+ if (set_new_line_discipline (shell_tty) < 0)
+ {
+ sys_error (_("initialize_job_control: line discipline"));
+ job_control = 0;
+ }
+ else
+ {
+ original_pgrp = shell_pgrp;
+ shell_pgrp = getpid ();
+
+ if ((original_pgrp != shell_pgrp) && (setpgid (0, shell_pgrp) < 0))
+ {
+ sys_error (_("initialize_job_control: setpgid"));
+ shell_pgrp = original_pgrp;
+ }
+
+ job_control = 1;
+
+ /* If (and only if) we just set our process group to our pid,
+ thereby becoming a process group leader, and the terminal
+ is not in the same process group as our (new) process group,
+ then set the terminal's process group to our (new) process
+ group. If that fails, set our process group back to what it
+ was originally (so we can still read from the terminal) and
+ turn off job control. */
+ if (shell_pgrp != original_pgrp && shell_pgrp != terminal_pgrp)
+ {
+ if (give_terminal_to (shell_pgrp, 0) < 0)
+ {
+ t_errno = errno;
+ setpgid (0, original_pgrp);
+ shell_pgrp = original_pgrp;
+ job_control = 0;
+ }
+ }
+
+ if (job_control && ((t = tcgetpgrp (shell_tty)) == -1 || t != shell_pgrp))
+ {
+ if (t_errno != -1)
+ errno = t_errno;
+ sys_error (_("cannot set terminal process group (%d)"), t);
+ job_control = 0;
+ }
+ }
+ if (job_control == 0)
+ internal_error (_("no job control in this shell"));
+ }
+
+ if (shell_tty != fileno (stderr))
+ SET_CLOSE_ON_EXEC (shell_tty);
+
+ set_signal_handler (SIGCHLD, sigchld_handler);
+
+ change_flag ('m', job_control ? '-' : '+');
+
+ if (interactive)
+ get_tty_state ();
+
+ if (js.c_childmax < 0)
+ js.c_childmax = getmaxchild ();
+ if (js.c_childmax < 0)
+ js.c_childmax = DEFAULT_CHILD_MAX;
+
+ return job_control;
+}
+
+#ifdef DEBUG
+void
+debug_print_pgrps ()
+{
+ itrace("original_pgrp = %ld shell_pgrp = %ld terminal_pgrp = %ld",
+ (long)original_pgrp, (long)shell_pgrp, (long)terminal_pgrp);
+ itrace("tcgetpgrp(%d) -> %ld, getpgid(0) -> %ld",
+ shell_tty, (long)tcgetpgrp (shell_tty), (long)getpgid(0));
+}
+#endif
+
+/* Set the line discipline to the best this system has to offer.
+ Return -1 if this is not possible. */
+static int
+set_new_line_discipline (tty)
+ int tty;
+{
+#if defined (NEW_TTY_DRIVER)
+ int ldisc;
+
+ if (ioctl (tty, TIOCGETD, &ldisc) < 0)
+ return (-1);
+
+ if (ldisc != NTTYDISC)
+ {
+ ldisc = NTTYDISC;
+
+ if (ioctl (tty, TIOCSETD, &ldisc) < 0)
+ return (-1);
+ }
+ return (0);
+#endif /* NEW_TTY_DRIVER */
+
+#if defined (TERMIO_TTY_DRIVER)
+# if defined (TERMIO_LDISC) && (NTTYDISC)
+ if (ioctl (tty, TCGETA, &shell_tty_info) < 0)
+ return (-1);
+
+ if (shell_tty_info.c_line != NTTYDISC)
+ {
+ shell_tty_info.c_line = NTTYDISC;
+ if (ioctl (tty, TCSETAW, &shell_tty_info) < 0)
+ return (-1);
+ }
+# endif /* TERMIO_LDISC && NTTYDISC */
+ return (0);
+#endif /* TERMIO_TTY_DRIVER */
+
+#if defined (TERMIOS_TTY_DRIVER)
+# if defined (TERMIOS_LDISC) && defined (NTTYDISC)
+ if (tcgetattr (tty, &shell_tty_info) < 0)
+ return (-1);
+
+ if (shell_tty_info.c_line != NTTYDISC)
+ {
+ shell_tty_info.c_line = NTTYDISC;
+ if (tcsetattr (tty, TCSADRAIN, &shell_tty_info) < 0)
+ return (-1);
+ }
+# endif /* TERMIOS_LDISC && NTTYDISC */
+ return (0);
+#endif /* TERMIOS_TTY_DRIVER */
+
+#if !defined (NEW_TTY_DRIVER) && !defined (TERMIO_TTY_DRIVER) && !defined (TERMIOS_TTY_DRIVER)
+ return (-1);
+#endif
+}
+
+/* Setup this shell to handle C-C, etc. */
+void
+initialize_job_signals ()
+{
+ if (interactive)
+ {
+ set_signal_handler (SIGINT, sigint_sighandler);
+ set_signal_handler (SIGTSTP, SIG_IGN);
+ set_signal_handler (SIGTTOU, SIG_IGN);
+ set_signal_handler (SIGTTIN, SIG_IGN);
+ }
+ else if (job_control)
+ {
+ old_tstp = set_signal_handler (SIGTSTP, sigstop_sighandler);
+ old_ttin = set_signal_handler (SIGTTIN, sigstop_sighandler);
+ old_ttou = set_signal_handler (SIGTTOU, sigstop_sighandler);
+ }
+ /* Leave these things alone for non-interactive shells without job
+ control. */
+}
+
+/* Here we handle CONT signals. */
+static sighandler
+sigcont_sighandler (sig)
+ int sig;
+{
+ initialize_job_signals ();
+ set_signal_handler (SIGCONT, old_cont);
+ kill (getpid (), SIGCONT);
+
+ SIGRETURN (0);
+}
+
+/* Here we handle stop signals while we are running not as a login shell. */
+static sighandler
+sigstop_sighandler (sig)
+ int sig;
+{
+ set_signal_handler (SIGTSTP, old_tstp);
+ set_signal_handler (SIGTTOU, old_ttou);
+ set_signal_handler (SIGTTIN, old_ttin);
+
+ old_cont = set_signal_handler (SIGCONT, sigcont_sighandler);
+
+ give_terminal_to (shell_pgrp, 0);
+
+ kill (getpid (), sig);
+
+ SIGRETURN (0);
+}
+
+/* Give the terminal to PGRP. */
+int
+give_terminal_to (pgrp, force)
+ pid_t pgrp;
+ int force;
+{
+ sigset_t set, oset;
+ int r, e;
+
+ r = 0;
+ if (job_control || force)
+ {
+ sigemptyset (&set);
+ sigaddset (&set, SIGTTOU);
+ sigaddset (&set, SIGTTIN);
+ sigaddset (&set, SIGTSTP);
+ sigaddset (&set, SIGCHLD);
+ sigemptyset (&oset);
+ sigprocmask (SIG_BLOCK, &set, &oset);
+
+ if (tcsetpgrp (shell_tty, pgrp) < 0)
+ {
+ /* Maybe we should print an error message? */
+#if 0
+ sys_error ("tcsetpgrp(%d) failed: pid %ld to pgrp %ld",
+ shell_tty, (long)getpid(), (long)pgrp);
+#endif
+ r = -1;
+ e = errno;
+ }
+ else
+ terminal_pgrp = pgrp;
+ sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL);
+ }
+
+ if (r == -1)
+ errno = e;
+
+ return r;
+}
+
+/* Give terminal to NPGRP iff it's currently owned by OPGRP. FLAGS are the
+ flags to pass to give_terminal_to(). */
+static int
+maybe_give_terminal_to (opgrp, npgrp, flags)
+ pid_t opgrp, npgrp;
+ int flags;
+{
+ int tpgrp;
+
+ tpgrp = tcgetpgrp (shell_tty);
+ if (tpgrp < 0 && errno == ENOTTY)
+ return -1;
+ if (tpgrp == npgrp)
+ {
+ terminal_pgrp = npgrp;
+ return 0;
+ }
+ else if (tpgrp != opgrp)
+ {
+#if defined (DEBUG)
+ internal_warning ("maybe_give_terminal_to: terminal pgrp == %d shell pgrp = %d new pgrp = %d", tpgrp, opgrp, npgrp);
+#endif
+ return -1;
+ }
+ else
+ return (give_terminal_to (npgrp, flags));
+}
+
+/* Clear out any jobs in the job array. This is intended to be used by
+ children of the shell, who should not have any job structures as baggage
+ when they start executing (forking subshells for parenthesized execution
+ and functions with pipes are the two that spring to mind). If RUNNING_ONLY
+ is nonzero, only running jobs are removed from the table. */
+void
+delete_all_jobs (running_only)
+ int running_only;
+{
+ register int i;
+ sigset_t set, oset;
+
+ BLOCK_CHILD (set, oset);
+
+ /* XXX - need to set j_lastj, j_firstj appropriately if running_only != 0. */
+ if (js.j_jobslots)
+ {
+ js.j_current = js.j_previous = NO_JOB;
+
+ /* XXX could use js.j_firstj here */
+ for (i = 0; i < js.j_jobslots; i++)
+ {
+#if defined (DEBUG)
+ if (i < js.j_firstj && jobs[i])
+ itrace("delete_all_jobs: job %d non-null before js.j_firstj (%d)", i, js.j_firstj);
+ if (i > js.j_lastj && jobs[i])
+ itrace("delete_all_jobs: job %d non-null after js.j_lastj (%d)", i, js.j_lastj);
+#endif
+ if (jobs[i] && (running_only == 0 || (running_only && RUNNING(i))))
+ delete_job (i, DEL_WARNSTOPPED);
+ }
+ if (running_only == 0)
+ {
+ free ((char *)jobs);
+ js.j_jobslots = 0;
+ js.j_firstj = js.j_lastj = js.j_njobs = 0;
+ }
+ }
+
+ if (running_only == 0)
+ bgp_clear ();
+
+ UNBLOCK_CHILD (oset);
+}
+
+/* Mark all jobs in the job array so that they don't get a SIGHUP when the
+ shell gets one. If RUNNING_ONLY is nonzero, mark only running jobs. */
+void
+nohup_all_jobs (running_only)
+ int running_only;
+{
+ register int i;
+ sigset_t set, oset;
+
+ BLOCK_CHILD (set, oset);
+
+ if (js.j_jobslots)
+ {
+ /* XXX could use js.j_firstj here */
+ for (i = 0; i < js.j_jobslots; i++)
+ if (jobs[i] && (running_only == 0 || (running_only && RUNNING(i))))
+ nohup_job (i);
+ }
+
+ UNBLOCK_CHILD (oset);
+}
+
+int
+count_all_jobs ()
+{
+ int i, n;
+ sigset_t set, oset;
+
+ /* This really counts all non-dead jobs. */
+ BLOCK_CHILD (set, oset);
+ /* XXX could use js.j_firstj here */
+ for (i = n = 0; i < js.j_jobslots; i++)
+ {
+#if defined (DEBUG)
+ if (i < js.j_firstj && jobs[i])
+ itrace("count_all_jobs: job %d non-null before js.j_firstj (%d)", i, js.j_firstj);
+ if (i > js.j_lastj && jobs[i])
+ itrace("count_all_jobs: job %d non-null after js.j_lastj (%d)", i, js.j_lastj);
+#endif
+ if (jobs[i] && DEADJOB(i) == 0)
+ n++;
+ }
+ UNBLOCK_CHILD (oset);
+ return n;
+}
+
+static void
+mark_all_jobs_as_dead ()
+{
+ register int i;
+ sigset_t set, oset;
+
+ if (js.j_jobslots == 0)
+ return;
+
+ BLOCK_CHILD (set, oset);
+
+ /* XXX could use js.j_firstj here */
+ for (i = 0; i < js.j_jobslots; i++)
+ if (jobs[i])
+ {
+ jobs[i]->state = JDEAD;
+ js.j_ndead++;
+ }
+
+ UNBLOCK_CHILD (oset);
+}
+
+/* Mark all dead jobs as notified, so delete_job () cleans them out
+ of the job table properly. POSIX.2 says we need to save the
+ status of the last CHILD_MAX jobs, so we count the number of dead
+ jobs and mark only enough as notified to save CHILD_MAX statuses. */
+static void
+mark_dead_jobs_as_notified (force)
+ int force;
+{
+ register int i, ndead, ndeadproc;
+ sigset_t set, oset;
+
+ if (js.j_jobslots == 0)
+ return;
+
+ BLOCK_CHILD (set, oset);
+
+ /* If FORCE is non-zero, we don't have to keep CHILD_MAX statuses
+ around; just run through the array. */
+ if (force)
+ {
+ /* XXX could use js.j_firstj here */
+ for (i = 0; i < js.j_jobslots; i++)
+ {
+ if (jobs[i] && DEADJOB (i) && (interactive_shell || (find_last_pid (i, 0) != last_asynchronous_pid)))
+ jobs[i]->flags |= J_NOTIFIED;
+ }
+ UNBLOCK_CHILD (oset);
+ return;
+ }
+
+ /* Mark enough dead jobs as notified to keep CHILD_MAX processes left in the
+ array with the corresponding not marked as notified. This is a better
+ way to avoid pid aliasing and reuse problems than keeping the POSIX-
+ mandated CHILD_MAX jobs around. delete_job() takes care of keeping the
+ bgpids list regulated. */
+
+ /* Count the number of dead jobs */
+ /* XXX could use js.j_firstj here */
+ for (i = ndead = ndeadproc = 0; i < js.j_jobslots; i++)
+ {
+#if defined (DEBUG)
+ if (i < js.j_firstj && jobs[i])
+ itrace("mark_dead_jobs_as_notified: job %d non-null before js.j_firstj (%d)", i, js.j_firstj);
+ if (i > js.j_lastj && jobs[i])
+ itrace("mark_dead_jobs_as_notified: job %d non-null after js.j_lastj (%d)", i, js.j_lastj);
+#endif
+ if (jobs[i] && DEADJOB (i))
+ {
+ ndead++;
+ ndeadproc += processes_in_job (i);
+ }
+ }
+
+#ifdef DEBUG
+ if (ndeadproc != js.c_reaped)
+ itrace("mark_dead_jobs_as_notified: ndeadproc (%d) != js.c_reaped (%d)", ndeadproc, js.c_reaped);
+ if (ndead != js.j_ndead)
+ itrace("mark_dead_jobs_as_notified: ndead (%d) != js.j_ndead (%d)", ndead, js.j_ndead);
+#endif
+
+ if (js.c_childmax < 0)
+ js.c_childmax = getmaxchild ();
+ if (js.c_childmax < 0)
+ js.c_childmax = DEFAULT_CHILD_MAX;
+
+ /* Don't do anything if the number of dead processes is less than CHILD_MAX
+ and we're not forcing a cleanup. */
+ if (ndeadproc <= js.c_childmax)
+ {
+ UNBLOCK_CHILD (oset);
+ return;
+ }
+
+#if 0
+itrace("mark_dead_jobs_as_notified: child_max = %d ndead = %d ndeadproc = %d", js.c_childmax, ndead, ndeadproc);
+#endif
+
+ /* Mark enough dead jobs as notified that we keep CHILD_MAX jobs in
+ the list. This isn't exactly right yet; changes need to be made
+ to stop_pipeline so we don't mark the newer jobs after we've
+ created CHILD_MAX slots in the jobs array. This needs to be
+ integrated with a way to keep the jobs array from growing without
+ bound. Maybe we wrap back around to 0 after we reach some max
+ limit, and there are sufficient job slots free (keep track of total
+ size of jobs array (js.j_jobslots) and running count of number of jobs
+ in jobs array. Then keep a job index corresponding to the `oldest job'
+ and start this loop there, wrapping around as necessary. In effect,
+ we turn the list into a circular buffer. */
+ /* XXX could use js.j_firstj here */
+ for (i = 0; i < js.j_jobslots; i++)
+ {
+ if (jobs[i] && DEADJOB (i) && (interactive_shell || (find_last_pid (i, 0) != last_asynchronous_pid)))
+ {
+#if defined (DEBUG)
+ if (i < js.j_firstj && jobs[i])
+ itrace("mark_dead_jobs_as_notified: job %d non-null before js.j_firstj (%d)", i, js.j_firstj);
+ if (i > js.j_lastj && jobs[i])
+ itrace("mark_dead_jobs_as_notified: job %d non-null after js.j_lastj (%d)", i, js.j_lastj);
+#endif
+ /* If marking this job as notified would drop us down below
+ child_max, don't mark it so we can keep at least child_max
+ statuses. XXX -- need to check what Posix actually says
+ about keeping statuses. */
+ if ((ndeadproc -= processes_in_job (i)) <= js.c_childmax)
+ break;
+ jobs[i]->flags |= J_NOTIFIED;
+ }
+ }
+
+ UNBLOCK_CHILD (oset);
+}
+
+/* Here to allow other parts of the shell (like the trap stuff) to
+ unfreeze the jobs list. */
+void
+unfreeze_jobs_list ()
+{
+ jobs_list_frozen = 0;
+}
+
+/* Allow or disallow job control to take place. Returns the old value
+ of job_control. */
+int
+set_job_control (arg)
+ int arg;
+{
+ int old;
+
+ old = job_control;
+ job_control = arg;
+
+ /* If we're turning on job control, reset pipeline_pgrp so make_child will
+ put new child processes into the right pgrp */
+ if (job_control != old && job_control)
+ pipeline_pgrp = 0;
+
+ return (old);
+}
+
+/* Turn off all traces of job control. This is run by children of the shell
+ which are going to do shellsy things, like wait (), etc. */
+void
+without_job_control ()
+{
+ stop_making_children ();
+ start_pipeline ();
+#if defined (PGRP_PIPE)
+ sh_closepipe (pgrp_pipe);
+#endif
+ delete_all_jobs (0);
+ set_job_control (0);
+}
+
+/* If this shell is interactive, terminate all stopped jobs and
+ restore the original terminal process group. This is done
+ before the `exec' builtin calls shell_execve. */
+void
+end_job_control ()
+{
+ if (interactive_shell) /* XXX - should it be interactive? */
+ {
+ terminate_stopped_jobs ();
+
+ if (original_pgrp >= 0)
+ give_terminal_to (original_pgrp, 1);
+ }
+
+ if (original_pgrp >= 0)
+ setpgid (0, original_pgrp);
+}
+
+/* Restart job control by closing shell tty and reinitializing. This is
+ called after an exec fails in an interactive shell and we do not exit. */
+void
+restart_job_control ()
+{
+ if (shell_tty != -1)
+ close (shell_tty);
+ initialize_job_control (0);
+}
+
+/* Set the handler to run when the shell receives a SIGCHLD signal. */
+void
+set_sigchld_handler ()
+{
+ set_signal_handler (SIGCHLD, sigchld_handler);
+}
+
+#if defined (PGRP_PIPE)
+/* Read from the read end of a pipe. This is how the process group leader
+ blocks until all of the processes in a pipeline have been made. */
+static void
+pipe_read (pp)
+ int *pp;
+{
+ char ch;
+
+ if (pp[1] >= 0)
+ {
+ close (pp[1]);
+ pp[1] = -1;
+ }
+
+ if (pp[0] >= 0)
+ {
+ while (read (pp[0], &ch, 1) == -1 && errno == EINTR)
+ ;
+ }
+}
+
+/* Functional interface closes our local-to-job-control pipes. */
+void
+close_pgrp_pipe ()
+{
+ sh_closepipe (pgrp_pipe);
+}
+
+void
+save_pgrp_pipe (p, clear)
+ int *p;
+ int clear;
+{
+ p[0] = pgrp_pipe[0];
+ p[1] = pgrp_pipe[1];
+ if (clear)
+ pgrp_pipe[0] = pgrp_pipe[1] = -1;
+}
+
+void
+restore_pgrp_pipe (p)
+ int *p;
+{
+ pgrp_pipe[0] = p[0];
+ pgrp_pipe[1] = p[1];
+}
+
+#endif /* PGRP_PIPE */
#include <filecntl.h>
#include <bashansi.h>
+#if !defined (HAVE_KILLPG)
+# include <signal.h>
+#endif
+
#include <stdio.h>
#include <errno.h>
#include <chartypes.h>
# else /* !HAVE_UNAME */
int
gethostname (name, namelen)
- int name, namelen;
+ char *name;
+ int namelen;
{
strncpy (name, "unknown", namelen);
name[namelen] = '\0';
--- /dev/null
+/* oslib.c - functions present only in some unix versions. */
+
+/* Copyright (C) 1995 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>
+#ifndef _MINIX
+# include <sys/param.h>
+#endif
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#if defined (HAVE_LIMITS_H)
+# include <limits.h>
+#endif
+
+#include <posixstat.h>
+#include <filecntl.h>
+#include <bashansi.h>
+
+#if !defined (HAVE_KILLPG)
+# include <signal.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <chartypes.h>
+
+#include <shell.h>
+
+#if !defined (errno)
+extern int errno;
+#endif /* !errno */
+
+/* Make the functions strchr and strrchr if they do not exist. */
+#if !defined (HAVE_STRCHR)
+char *
+strchr (string, c)
+ char *string;
+ int c;
+{
+ register char *s;
+
+ for (s = string; s && *s; s++)
+ if (*s == c)
+ return (s);
+
+ return ((char *) NULL);
+}
+
+char *
+strrchr (string, c)
+ char *string;
+ int c;
+{
+ register char *s, *t;
+
+ for (s = string, t = (char *)NULL; s && *s; s++)
+ if (*s == c)
+ t = s;
+ return (t);
+}
+#endif /* !HAVE_STRCHR */
+
+#if !defined (HAVE_DUP2) || defined (DUP2_BROKEN)
+/* Replacement for dup2 (), for those systems which either don't have it,
+ or supply one with broken behaviour. */
+int
+dup2 (fd1, fd2)
+ int fd1, fd2;
+{
+ int saved_errno, r;
+
+ /* If FD1 is not a valid file descriptor, then return immediately with
+ an error. */
+ if (fcntl (fd1, F_GETFL, 0) == -1)
+ return (-1);
+
+ if (fd2 < 0 || fd2 >= getdtablesize ())
+ {
+ errno = EBADF;
+ return (-1);
+ }
+
+ if (fd1 == fd2)
+ return (0);
+
+ saved_errno = errno;
+
+ (void) close (fd2);
+ r = fcntl (fd1, F_DUPFD, fd2);
+
+ if (r >= 0)
+ errno = saved_errno;
+ else
+ if (errno == EINVAL)
+ errno = EBADF;
+
+ /* Force the new file descriptor to remain open across exec () calls. */
+ SET_OPEN_ON_EXEC (fd2);
+ return (r);
+}
+#endif /* !HAVE_DUP2 */
+
+/*
+ * Return the total number of available file descriptors.
+ *
+ * On some systems, like 4.2BSD and its descendents, there is a system call
+ * that returns the size of the descriptor table: getdtablesize(). There are
+ * lots of ways to emulate this on non-BSD systems.
+ *
+ * On System V.3, this can be obtained via a call to ulimit:
+ * return (ulimit(4, 0L));
+ *
+ * On other System V systems, NOFILE is defined in /usr/include/sys/param.h
+ * (this is what we assume below), so we can simply use it:
+ * return (NOFILE);
+ *
+ * On POSIX systems, there are specific functions for retrieving various
+ * configuration parameters:
+ * return (sysconf(_SC_OPEN_MAX));
+ *
+ */
+
+#if !defined (HAVE_GETDTABLESIZE)
+int
+getdtablesize ()
+{
+# if defined (_POSIX_VERSION) && defined (HAVE_SYSCONF) && defined (_SC_OPEN_MAX)
+ return (sysconf(_SC_OPEN_MAX)); /* Posix systems use sysconf */
+# else /* ! (_POSIX_VERSION && HAVE_SYSCONF && _SC_OPEN_MAX) */
+# if defined (ULIMIT_MAXFDS)
+ return (ulimit (4, 0L)); /* System V.3 systems use ulimit(4, 0L) */
+# else /* !ULIMIT_MAXFDS */
+# if defined (NOFILE) /* Other systems use NOFILE */
+ return (NOFILE);
+# else /* !NOFILE */
+ return (20); /* XXX - traditional value is 20 */
+# endif /* !NOFILE */
+# endif /* !ULIMIT_MAXFDS */
+# endif /* ! (_POSIX_VERSION && _SC_OPEN_MAX) */
+}
+#endif /* !HAVE_GETDTABLESIZE */
+
+#if !defined (HAVE_BCOPY)
+# if defined (bcopy)
+# undef bcopy
+# endif
+void
+bcopy (s,d,n)
+ char *d, *s;
+ int n;
+{
+ FASTCOPY (s, d, n);
+}
+#endif /* !HAVE_BCOPY */
+
+#if !defined (HAVE_BZERO)
+# if defined (bzero)
+# undef bzero
+# endif
+void
+bzero (s, n)
+ char *s;
+ int n;
+{
+ register int i;
+ register char *r;
+
+ for (i = 0, r = s; i < n; i++)
+ *r++ = '\0';
+}
+#endif
+
+#if !defined (HAVE_GETHOSTNAME)
+# if defined (HAVE_UNAME)
+# include <sys/utsname.h>
+int
+gethostname (name, namelen)
+ char *name;
+ int namelen;
+{
+ int i;
+ struct utsname ut;
+
+ --namelen;
+
+ uname (&ut);
+ i = strlen (ut.nodename) + 1;
+ strncpy (name, ut.nodename, i < namelen ? i : namelen);
+ name[namelen] = '\0';
+ return (0);
+}
+# else /* !HAVE_UNAME */
+int
+gethostname (name, namelen)
+ int name, namelen;
+{
+ strncpy (name, "unknown", namelen);
+ name[namelen] = '\0';
+ return 0;
+}
+# endif /* !HAVE_UNAME */
+#endif /* !HAVE_GETHOSTNAME */
+
+#if !defined (HAVE_KILLPG)
+int
+killpg (pgrp, sig)
+ pid_t pgrp;
+ int sig;
+{
+ return (kill (-pgrp, sig));
+}
+#endif /* !HAVE_KILLPG */
+
+#if !defined (HAVE_MKFIFO) && defined (PROCESS_SUBSTITUTION)
+int
+mkfifo (path, mode)
+ char *path;
+ int mode;
+{
+#if defined (S_IFIFO)
+ return (mknod (path, (mode | S_IFIFO), 0));
+#else /* !S_IFIFO */
+ return (-1);
+#endif /* !S_IFIFO */
+}
+#endif /* !HAVE_MKFIFO && PROCESS_SUBSTITUTION */
+
+#define DEFAULT_MAXGROUPS 64
+
+int
+getmaxgroups ()
+{
+ static int maxgroups = -1;
+
+ if (maxgroups > 0)
+ return maxgroups;
+
+#if defined (HAVE_SYSCONF) && defined (_SC_NGROUPS_MAX)
+ maxgroups = sysconf (_SC_NGROUPS_MAX);
+#else
+# if defined (NGROUPS_MAX)
+ maxgroups = NGROUPS_MAX;
+# else /* !NGROUPS_MAX */
+# if defined (NGROUPS)
+ maxgroups = NGROUPS;
+# else /* !NGROUPS */
+ maxgroups = DEFAULT_MAXGROUPS;
+# endif /* !NGROUPS */
+# endif /* !NGROUPS_MAX */
+#endif /* !HAVE_SYSCONF || !SC_NGROUPS_MAX */
+
+ if (maxgroups <= 0)
+ maxgroups = DEFAULT_MAXGROUPS;
+
+ return maxgroups;
+}
+
+long
+getmaxchild ()
+{
+ static long maxchild = -1L;
+
+ if (maxchild > 0)
+ return maxchild;
+
+#if defined (HAVE_SYSCONF) && defined (_SC_CHILD_MAX)
+ maxchild = sysconf (_SC_CHILD_MAX);
+#else
+# if defined (CHILD_MAX)
+ maxchild = CHILD_MAX;
+# else
+# if defined (MAXUPRC)
+ maxchild = MAXUPRC;
+# endif /* MAXUPRC */
+# endif /* CHILD_MAX */
+#endif /* !HAVE_SYSCONF || !_SC_CHILD_MAX */
+
+ return (maxchild);
+}
cprintf ("{%s}", redir_word->word);
else if (redirector != 0)
cprintf ("%d", redirector);
+#if 0
+ /* Don't need to check whether or not to requote, since original quotes
+ are still intact. The only thing that has happened is that $'...'
+ has been replaced with 'expanded ...'. */
if (ansic_shouldquote (redirect->redirectee.filename->word))
{
char *x;
free (x);
}
else
+#endif
cprintf ("<<< %s", redirect->redirectee.filename->word);
break;
--- /dev/null
+/* print_command -- A way to make readable commands from a command tree. */
+
+/* Copyright (C) 1989-2009 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 <stdio.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#if defined (PREFER_STDARG)
+# include <stdarg.h>
+#else
+# include <varargs.h>
+#endif
+
+#include "bashansi.h"
+#include "bashintl.h"
+
+#include "shell.h"
+#include "flags.h"
+#include <y.tab.h> /* use <...> so we pick it up from the build directory */
+
+#include "shmbutil.h"
+
+#include "builtins/common.h"
+
+#if !HAVE_DECL_PRINTF
+extern int printf __P((const char *, ...)); /* Yuck. Double yuck. */
+#endif
+
+extern int indirection_level;
+
+static int indentation;
+static int indentation_amount = 4;
+
+#if defined (PREFER_STDARG)
+typedef void PFUNC __P((const char *, ...));
+
+static void cprintf __P((const char *, ...)) __attribute__((__format__ (printf, 1, 2)));
+static void xprintf __P((const char *, ...)) __attribute__((__format__ (printf, 1, 2)));
+#else
+#define PFUNC VFunction
+static void cprintf ();
+static void xprintf ();
+#endif
+
+static void reset_locals __P((void));
+static void newline __P((char *));
+static void indent __P((int));
+static void semicolon __P((void));
+static void the_printed_command_resize __P((int));
+
+static void make_command_string_internal __P((COMMAND *));
+static void _print_word_list __P((WORD_LIST *, char *, PFUNC *));
+static void command_print_word_list __P((WORD_LIST *, char *));
+static void print_case_clauses __P((PATTERN_LIST *));
+static void print_redirection_list __P((REDIRECT *));
+static void print_redirection __P((REDIRECT *));
+static void print_heredoc_header __P((REDIRECT *));
+static void print_heredoc_body __P((REDIRECT *));
+static void print_heredocs __P((REDIRECT *));
+static void print_deferred_heredocs __P((const char *));
+
+static void print_for_command __P((FOR_COM *));
+#if defined (ARITH_FOR_COMMAND)
+static void print_arith_for_command __P((ARITH_FOR_COM *));
+#endif
+#if defined (SELECT_COMMAND)
+static void print_select_command __P((SELECT_COM *));
+#endif
+static void print_group_command __P((GROUP_COM *));
+static void print_case_command __P((CASE_COM *));
+static void print_while_command __P((WHILE_COM *));
+static void print_until_command __P((WHILE_COM *));
+static void print_until_or_while __P((WHILE_COM *, char *));
+static void print_if_command __P((IF_COM *));
+#if defined (COND_COMMAND)
+static void print_cond_node __P((COND_COM *));
+#endif
+static void print_function_def __P((FUNCTION_DEF *));
+
+#define PRINTED_COMMAND_INITIAL_SIZE 64
+#define PRINTED_COMMAND_GROW_SIZE 128
+
+char *the_printed_command = (char *)NULL;
+int the_printed_command_size = 0;
+int command_string_index = 0;
+
+int xtrace_fd = -1;
+FILE *xtrace_fp = 0;
+
+#define CHECK_XTRACE_FP xtrace_fp = (xtrace_fp ? xtrace_fp : stderr)
+
+/* Non-zero means the stuff being printed is inside of a function def. */
+static int inside_function_def;
+static int skip_this_indent;
+static int was_heredoc;
+static int printing_connection;
+static REDIRECT *deferred_heredocs;
+
+/* The depth of the group commands that we are currently printing. This
+ includes the group command that is a function body. */
+static int group_command_nesting;
+
+/* A buffer to indicate the indirection level (PS4) when set -x is enabled. */
+static char indirection_string[100];
+
+/* Print COMMAND (a command tree) on standard output. */
+void
+print_command (command)
+ COMMAND *command;
+{
+ command_string_index = 0;
+ printf ("%s", make_command_string (command));
+}
+
+/* Make a string which is the printed representation of the command
+ tree in COMMAND. We return this string. However, the string is
+ not consed, so you have to do that yourself if you want it to
+ remain around. */
+char *
+make_command_string (command)
+ COMMAND *command;
+{
+ command_string_index = was_heredoc = 0;
+ deferred_heredocs = 0;
+ make_command_string_internal (command);
+ return (the_printed_command);
+}
+
+/* The internal function. This is the real workhorse. */
+static void
+make_command_string_internal (command)
+ COMMAND *command;
+{
+ char s[3], *op;
+
+ if (command == 0)
+ cprintf ("");
+ else
+ {
+ if (skip_this_indent)
+ skip_this_indent--;
+ else
+ indent (indentation);
+
+ if (command->flags & CMD_TIME_PIPELINE)
+ {
+ cprintf ("time ");
+ if (command->flags & CMD_TIME_POSIX)
+ cprintf ("-p ");
+ }
+
+ if (command->flags & CMD_INVERT_RETURN)
+ cprintf ("! ");
+
+ switch (command->type)
+ {
+ case cm_for:
+ print_for_command (command->value.For);
+ break;
+
+#if defined (ARITH_FOR_COMMAND)
+ case cm_arith_for:
+ print_arith_for_command (command->value.ArithFor);
+ break;
+#endif
+
+#if defined (SELECT_COMMAND)
+ case cm_select:
+ print_select_command (command->value.Select);
+ break;
+#endif
+
+ case cm_case:
+ print_case_command (command->value.Case);
+ break;
+
+ case cm_while:
+ print_while_command (command->value.While);
+ break;
+
+ case cm_until:
+ print_until_command (command->value.While);
+ break;
+
+ case cm_if:
+ print_if_command (command->value.If);
+ break;
+
+#if defined (DPAREN_ARITHMETIC)
+ case cm_arith:
+ print_arith_command (command->value.Arith->exp);
+ break;
+#endif
+
+#if defined (COND_COMMAND)
+ case cm_cond:
+ print_cond_command (command->value.Cond);
+ break;
+#endif
+
+ case cm_simple:
+ print_simple_command (command->value.Simple);
+ break;
+
+ case cm_connection:
+
+ skip_this_indent++;
+ printing_connection++;
+ make_command_string_internal (command->value.Connection->first);
+
+ switch (command->value.Connection->connector)
+ {
+ case '&':
+ case '|':
+ {
+ char c = command->value.Connection->connector;
+
+ s[0] = ' ';
+ s[1] = c;
+ s[2] = '\0';
+
+ print_deferred_heredocs (s);
+
+ if (c != '&' || command->value.Connection->second)
+ {
+ cprintf (" ");
+ skip_this_indent++;
+ }
+ }
+ break;
+
+ case AND_AND:
+ print_deferred_heredocs (" && ");
+ if (command->value.Connection->second)
+ skip_this_indent++;
+ break;
+
+ case OR_OR:
+ print_deferred_heredocs (" || ");
+ if (command->value.Connection->second)
+ skip_this_indent++;
+ break;
+
+ case ';':
+ if (deferred_heredocs == 0)
+ {
+ if (was_heredoc == 0)
+ cprintf (";");
+ else
+ was_heredoc = 0;
+ }
+ else
+ print_deferred_heredocs (inside_function_def ? "" : ";");
+
+ if (inside_function_def)
+ cprintf ("\n");
+ else
+ {
+ cprintf (" ");
+ if (command->value.Connection->second)
+ skip_this_indent++;
+ }
+ break;
+
+ default:
+ cprintf (_("print_command: bad connector `%d'"),
+ command->value.Connection->connector);
+ break;
+ }
+
+ make_command_string_internal (command->value.Connection->second);
+ if (deferred_heredocs)
+ print_deferred_heredocs ("");
+ printing_connection--;
+ break;
+
+ case cm_function_def:
+ print_function_def (command->value.Function_def);
+ break;
+
+ case cm_group:
+ print_group_command (command->value.Group);
+ break;
+
+ case cm_subshell:
+ cprintf ("( ");
+ skip_this_indent++;
+ make_command_string_internal (command->value.Subshell->command);
+ cprintf (" )");
+ break;
+
+ case cm_coproc:
+ cprintf ("coproc %s ", command->value.Coproc->name);
+ skip_this_indent++;
+ make_command_string_internal (command->value.Coproc->command);
+ break;
+
+ default:
+ command_error ("print_command", CMDERR_BADTYPE, command->type, 0);
+ break;
+ }
+
+
+ if (command->redirects)
+ {
+ cprintf (" ");
+ print_redirection_list (command->redirects);
+ }
+ }
+}
+
+static void
+_print_word_list (list, separator, pfunc)
+ WORD_LIST *list;
+ char *separator;
+ PFUNC *pfunc;
+{
+ WORD_LIST *w;
+
+ for (w = list; w; w = w->next)
+ (*pfunc) ("%s%s", w->word->word, w->next ? separator : "");
+}
+
+void
+print_word_list (list, separator)
+ WORD_LIST *list;
+ char *separator;
+{
+ _print_word_list (list, separator, xprintf);
+}
+
+void
+xtrace_set (fd, fp)
+ int fd;
+ FILE *fp;
+{
+ if (fd >= 0 && sh_validfd (fd) == 0)
+ {
+ internal_error (_("xtrace_set: %d: invalid file descriptor"), fd);
+ return;
+ }
+ if (fp == 0)
+ {
+ internal_error (_("xtrace_set: NULL file pointer"));
+ return;
+ }
+ if (fd >= 0 && fileno (fp) != fd)
+ internal_warning (_("xtrace fd (%d) != fileno xtrace fp (%d)"), fd, fileno (fp));
+
+ xtrace_fd = fd;
+ xtrace_fp = fp;
+}
+
+void
+xtrace_init ()
+{
+ xtrace_set (-1, stderr);
+}
+
+void
+xtrace_reset ()
+{
+ if (xtrace_fd >= 0 && xtrace_fp)
+ {
+ fflush (xtrace_fp);
+ fclose (xtrace_fp);
+ }
+ else if (xtrace_fd >= 0)
+ close (xtrace_fd);
+
+ xtrace_fd = -1;
+ xtrace_fp = stderr;
+}
+
+void
+xtrace_fdchk (fd)
+ int fd;
+{
+ if (fd == xtrace_fd)
+ xtrace_reset ();
+}
+
+/* Return a string denoting what our indirection level is. */
+
+char *
+indirection_level_string ()
+{
+ register int i, j;
+ char *ps4;
+ char ps4_firstc[MB_LEN_MAX+1];
+ int ps4_firstc_len, ps4_len;
+
+ indirection_string[0] = '\0';
+ ps4 = get_string_value ("PS4");
+
+ if (ps4 == 0 || *ps4 == '\0')
+ return (indirection_string);
+
+ change_flag ('x', FLAG_OFF);
+ ps4 = decode_prompt_string (ps4);
+ change_flag ('x', FLAG_ON);
+
+ if (ps4 == 0 || *ps4 == '\0')
+ return (indirection_string);
+
+#if defined (HANDLE_MULTIBYTE)
+ ps4_len = strnlen (ps4, MB_CUR_MAX);
+ ps4_firstc_len = MBLEN (ps4, ps4_len);
+ if (ps4_firstc_len == 1 || ps4_firstc_len == 0 || MB_INVALIDCH (ps4_firstc_len))
+ {
+ ps4_firstc[0] = ps4[0];
+ ps4_firstc[ps4_firstc_len = 1] = '\0';
+ }
+ else
+ memcpy (ps4_firstc, ps4, ps4_firstc_len);
+#else
+ ps4_firstc[0] = ps4[0];
+ ps4_firstc[ps4_firstc_len = 1] = '\0';
+#endif
+
+ for (i = j = 0; ps4_firstc[0] && j < indirection_level && i < 99; i += ps4_firstc_len, j++)
+ {
+ if (ps4_firstc_len == 1)
+ indirection_string[i] = ps4_firstc[0];
+ else
+ memcpy (indirection_string+i, ps4_firstc, ps4_firstc_len);
+ }
+
+ for (j = ps4_firstc_len; *ps4 && ps4[j] && i < 99; i++, j++)
+ indirection_string[i] = ps4[j];
+
+ indirection_string[i] = '\0';
+ free (ps4);
+ return (indirection_string);
+}
+
+void
+xtrace_print_assignment (name, value, assign_list, xflags)
+ char *name, *value;
+ int assign_list, xflags;
+{
+ char *nval;
+
+ CHECK_XTRACE_FP;
+
+ if (xflags)
+ fprintf (xtrace_fp, "%s", indirection_level_string ());
+
+ /* VALUE should not be NULL when this is called. */
+ if (*value == '\0' || assign_list)
+ nval = value;
+ else if (sh_contains_shell_metas (value))
+ nval = sh_single_quote (value);
+ else if (ansic_shouldquote (value))
+ nval = ansic_quote (value, 0, (int *)0);
+ else
+ nval = value;
+
+ if (assign_list)
+ fprintf (xtrace_fp, "%s=(%s)\n", name, nval);
+ else
+ fprintf (xtrace_fp, "%s=%s\n", name, nval);
+
+ if (nval != value)
+ FREE (nval);
+
+ fflush (xtrace_fp);
+}
+
+/* A function to print the words of a simple command when set -x is on. */
+void
+xtrace_print_word_list (list, xtflags)
+ WORD_LIST *list;
+ int xtflags;
+{
+ WORD_LIST *w;
+ char *t, *x;
+
+ CHECK_XTRACE_FP;
+
+ if (xtflags)
+ fprintf (xtrace_fp, "%s", indirection_level_string ());
+
+ for (w = list; w; w = w->next)
+ {
+ t = w->word->word;
+ if (t == 0 || *t == '\0')
+ fprintf (xtrace_fp, "''%s", w->next ? " " : "");
+ else if (sh_contains_shell_metas (t))
+ {
+ x = sh_single_quote (t);
+ fprintf (xtrace_fp, "%s%s", x, w->next ? " " : "");
+ free (x);
+ }
+ else if (ansic_shouldquote (t))
+ {
+ x = ansic_quote (t, 0, (int *)0);
+ fprintf (xtrace_fp, "%s%s", x, w->next ? " " : "");
+ free (x);
+ }
+ else
+ fprintf (xtrace_fp, "%s%s", t, w->next ? " " : "");
+ }
+ fprintf (xtrace_fp, "\n");
+ fflush (xtrace_fp);
+}
+
+static void
+command_print_word_list (list, separator)
+ WORD_LIST *list;
+ char *separator;
+{
+ _print_word_list (list, separator, cprintf);
+}
+
+void
+print_for_command_head (for_command)
+ FOR_COM *for_command;
+{
+ cprintf ("for %s in ", for_command->name->word);
+ command_print_word_list (for_command->map_list, " ");
+}
+
+void
+xtrace_print_for_command_head (for_command)
+ FOR_COM *for_command;
+{
+ CHECK_XTRACE_FP;
+ fprintf (xtrace_fp, "%s", indirection_level_string ());
+ fprintf (xtrace_fp, "for %s in ", for_command->name->word);
+ xtrace_print_word_list (for_command->map_list, 0);
+}
+
+static void
+print_for_command (for_command)
+ FOR_COM *for_command;
+{
+ print_for_command_head (for_command);
+
+ cprintf (";");
+ newline ("do\n");
+ indentation += indentation_amount;
+ make_command_string_internal (for_command->action);
+ semicolon ();
+ indentation -= indentation_amount;
+ newline ("done");
+}
+
+#if defined (ARITH_FOR_COMMAND)
+static void
+print_arith_for_command (arith_for_command)
+ ARITH_FOR_COM *arith_for_command;
+{
+ cprintf ("for ((");
+ command_print_word_list (arith_for_command->init, " ");
+ cprintf ("; ");
+ command_print_word_list (arith_for_command->test, " ");
+ cprintf ("; ");
+ command_print_word_list (arith_for_command->step, " ");
+ cprintf ("))");
+ newline ("do\n");
+ indentation += indentation_amount;
+ make_command_string_internal (arith_for_command->action);
+ semicolon ();
+ indentation -= indentation_amount;
+ newline ("done");
+}
+#endif /* ARITH_FOR_COMMAND */
+
+#if defined (SELECT_COMMAND)
+void
+print_select_command_head (select_command)
+ SELECT_COM *select_command;
+{
+ cprintf ("select %s in ", select_command->name->word);
+ command_print_word_list (select_command->map_list, " ");
+}
+
+void
+xtrace_print_select_command_head (select_command)
+ SELECT_COM *select_command;
+{
+ CHECK_XTRACE_FP;
+ fprintf (xtrace_fp, "%s", indirection_level_string ());
+ fprintf (xtrace_fp, "select %s in ", select_command->name->word);
+ xtrace_print_word_list (select_command->map_list, 0);
+}
+
+static void
+print_select_command (select_command)
+ SELECT_COM *select_command;
+{
+ print_select_command_head (select_command);
+
+ cprintf (";");
+ newline ("do\n");
+ indentation += indentation_amount;
+ make_command_string_internal (select_command->action);
+ semicolon ();
+ indentation -= indentation_amount;
+ newline ("done");
+}
+#endif /* SELECT_COMMAND */
+
+static void
+print_group_command (group_command)
+ GROUP_COM *group_command;
+{
+ group_command_nesting++;
+ cprintf ("{ ");
+
+ if (inside_function_def == 0)
+ skip_this_indent++;
+ else
+ {
+ /* This is a group command { ... } inside of a function
+ definition, and should be printed as a multiline group
+ command, using the current indentation. */
+ cprintf ("\n");
+ indentation += indentation_amount;
+ }
+
+ make_command_string_internal (group_command->command);
+
+ if (inside_function_def)
+ {
+ cprintf ("\n");
+ indentation -= indentation_amount;
+ indent (indentation);
+ }
+ else
+ {
+ semicolon ();
+ cprintf (" ");
+ }
+
+ cprintf ("}");
+
+ group_command_nesting--;
+}
+
+void
+print_case_command_head (case_command)
+ CASE_COM *case_command;
+{
+ cprintf ("case %s in ", case_command->word->word);
+}
+
+void
+xtrace_print_case_command_head (case_command)
+ CASE_COM *case_command;
+{
+ CHECK_XTRACE_FP;
+ fprintf (xtrace_fp, "%s", indirection_level_string ());
+ fprintf (xtrace_fp, "case %s in\n", case_command->word->word);
+}
+
+static void
+print_case_command (case_command)
+ CASE_COM *case_command;
+{
+ print_case_command_head (case_command);
+
+ if (case_command->clauses)
+ print_case_clauses (case_command->clauses);
+ newline ("esac");
+}
+
+static void
+print_case_clauses (clauses)
+ PATTERN_LIST *clauses;
+{
+ indentation += indentation_amount;
+ while (clauses)
+ {
+ newline ("");
+ command_print_word_list (clauses->patterns, " | ");
+ cprintf (")\n");
+ indentation += indentation_amount;
+ make_command_string_internal (clauses->action);
+ indentation -= indentation_amount;
+ if (clauses->flags & CASEPAT_FALLTHROUGH)
+ newline (";&");
+ else if (clauses->flags & CASEPAT_TESTNEXT)
+ newline (";;&");
+ else
+ newline (";;");
+ clauses = clauses->next;
+ }
+ indentation -= indentation_amount;
+}
+
+static void
+print_while_command (while_command)
+ WHILE_COM *while_command;
+{
+ print_until_or_while (while_command, "while");
+}
+
+static void
+print_until_command (while_command)
+ WHILE_COM *while_command;
+{
+ print_until_or_while (while_command, "until");
+}
+
+static void
+print_until_or_while (while_command, which)
+ WHILE_COM *while_command;
+ char *which;
+{
+ cprintf ("%s ", which);
+ skip_this_indent++;
+ make_command_string_internal (while_command->test);
+ semicolon ();
+ cprintf (" do\n"); /* was newline ("do\n"); */
+ indentation += indentation_amount;
+ make_command_string_internal (while_command->action);
+ indentation -= indentation_amount;
+ semicolon ();
+ newline ("done");
+}
+
+static void
+print_if_command (if_command)
+ IF_COM *if_command;
+{
+ cprintf ("if ");
+ skip_this_indent++;
+ make_command_string_internal (if_command->test);
+ semicolon ();
+ cprintf (" then\n");
+ indentation += indentation_amount;
+ make_command_string_internal (if_command->true_case);
+ indentation -= indentation_amount;
+
+ if (if_command->false_case)
+ {
+ semicolon ();
+ newline ("else\n");
+ indentation += indentation_amount;
+ make_command_string_internal (if_command->false_case);
+ indentation -= indentation_amount;
+ }
+ semicolon ();
+ newline ("fi");
+}
+
+#if defined (DPAREN_ARITHMETIC)
+void
+print_arith_command (arith_cmd_list)
+ WORD_LIST *arith_cmd_list;
+{
+ cprintf ("((");
+ command_print_word_list (arith_cmd_list, " ");
+ cprintf ("))");
+}
+#endif
+
+#if defined (COND_COMMAND)
+static void
+print_cond_node (cond)
+ COND_COM *cond;
+{
+ if (cond->flags & CMD_INVERT_RETURN)
+ cprintf ("! ");
+
+ if (cond->type == COND_EXPR)
+ {
+ cprintf ("( ");
+ print_cond_node (cond->left);
+ cprintf (" )");
+ }
+ else if (cond->type == COND_AND)
+ {
+ print_cond_node (cond->left);
+ cprintf (" && ");
+ print_cond_node (cond->right);
+ }
+ else if (cond->type == COND_OR)
+ {
+ print_cond_node (cond->left);
+ cprintf (" || ");
+ print_cond_node (cond->right);
+ }
+ else if (cond->type == COND_UNARY)
+ {
+ cprintf ("%s", cond->op->word);
+ cprintf (" ");
+ print_cond_node (cond->left);
+ }
+ else if (cond->type == COND_BINARY)
+ {
+ print_cond_node (cond->left);
+ cprintf (" ");
+ cprintf ("%s", cond->op->word);
+ cprintf (" ");
+ print_cond_node (cond->right);
+ }
+ else if (cond->type == COND_TERM)
+ {
+ cprintf ("%s", cond->op->word); /* need to add quoting here */
+ }
+}
+
+void
+print_cond_command (cond)
+ COND_COM *cond;
+{
+ cprintf ("[[ ");
+ print_cond_node (cond);
+ cprintf (" ]]");
+}
+
+#ifdef DEBUG
+void
+debug_print_cond_command (cond)
+ COND_COM *cond;
+{
+ fprintf (stderr, "DEBUG: ");
+ command_string_index = 0;
+ print_cond_command (cond);
+ fprintf (stderr, "%s\n", the_printed_command);
+}
+#endif
+
+void
+xtrace_print_cond_term (type, invert, op, arg1, arg2)
+ int type, invert;
+ WORD_DESC *op;
+ char *arg1, *arg2;
+{
+ CHECK_XTRACE_FP;
+ command_string_index = 0;
+ fprintf (xtrace_fp, "%s", indirection_level_string ());
+ fprintf (xtrace_fp, "[[ ");
+ if (invert)
+ fprintf (xtrace_fp, "! ");
+
+ if (type == COND_UNARY)
+ {
+ fprintf (xtrace_fp, "%s ", op->word);
+ fprintf (xtrace_fp, "%s", (arg1 && *arg1) ? arg1 : "''");
+ }
+ else if (type == COND_BINARY)
+ {
+ fprintf (xtrace_fp, "%s", (arg1 && *arg1) ? arg1 : "''");
+ fprintf (xtrace_fp, " %s ", op->word);
+ fprintf (xtrace_fp, "%s", (arg2 && *arg2) ? arg2 : "''");
+ }
+
+ fprintf (xtrace_fp, " ]]\n");
+
+ fflush (xtrace_fp);
+}
+#endif /* COND_COMMAND */
+
+#if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND)
+/* A function to print the words of an arithmetic command when set -x is on. */
+void
+xtrace_print_arith_cmd (list)
+ WORD_LIST *list;
+{
+ WORD_LIST *w;
+
+ CHECK_XTRACE_FP;
+ fprintf (xtrace_fp, "%s", indirection_level_string ());
+ fprintf (xtrace_fp, "(( ");
+ for (w = list; w; w = w->next)
+ fprintf (xtrace_fp, "%s%s", w->word->word, w->next ? " " : "");
+ fprintf (xtrace_fp, " ))\n");
+
+ fflush (xtrace_fp);
+}
+#endif
+
+void
+print_simple_command (simple_command)
+ SIMPLE_COM *simple_command;
+{
+ command_print_word_list (simple_command->words, " ");
+
+ if (simple_command->redirects)
+ {
+ cprintf (" ");
+ print_redirection_list (simple_command->redirects);
+ }
+}
+
+static void
+print_heredocs (heredocs)
+ REDIRECT *heredocs;
+{
+ REDIRECT *hdtail;
+
+ cprintf (" ");
+ for (hdtail = heredocs; hdtail; hdtail = hdtail->next)
+ {
+ print_redirection (hdtail);
+ cprintf ("\n");
+ }
+ was_heredoc = 1;
+}
+
+/* Print heredocs that are attached to the command before the connector
+ represented by CSTRING. The parsing semantics require us to print the
+ here-doc delimiters, then the connector (CSTRING), then the here-doc
+ bodies. We don't print the connector if it's a `;', but we use it to
+ note not to print an extra space after the last heredoc body and
+ newline. */
+static void
+print_deferred_heredocs (cstring)
+ const char *cstring;
+{
+ REDIRECT *hdtail;
+
+ for (hdtail = deferred_heredocs; hdtail; hdtail = hdtail->next)
+ {
+ cprintf (" ");
+ print_heredoc_header (hdtail);
+ }
+ if (cstring[0] != ';' || cstring[1])
+ cprintf ("%s", cstring);
+ if (deferred_heredocs)
+ cprintf ("\n");
+ for (hdtail = deferred_heredocs; hdtail; hdtail = hdtail->next)
+ {
+ print_heredoc_body (hdtail);
+ cprintf ("\n");
+ }
+ if (deferred_heredocs)
+ {
+ if (cstring && cstring[0] && (cstring[0] != ';' || cstring[1]))
+ cprintf (" "); /* make sure there's at least one space */
+ dispose_redirects (deferred_heredocs);
+ was_heredoc = 1;
+ }
+ deferred_heredocs = (REDIRECT *)NULL;
+}
+
+static void
+print_redirection_list (redirects)
+ REDIRECT *redirects;
+{
+ REDIRECT *heredocs, *hdtail, *newredir;
+
+ heredocs = (REDIRECT *)NULL;
+ hdtail = heredocs;
+
+ was_heredoc = 0;
+ while (redirects)
+ {
+ /* Defer printing the here documents until we've printed the
+ rest of the redirections. */
+ if (redirects->instruction == r_reading_until || redirects->instruction == r_deblank_reading_until)
+ {
+ newredir = copy_redirect (redirects);
+ newredir->next = (REDIRECT *)NULL;
+ if (heredocs)
+ {
+ hdtail->next = newredir;
+ hdtail = newredir;
+ }
+ else
+ hdtail = heredocs = newredir;
+ }
+ else if (redirects->instruction == r_duplicating_output_word && redirects->redirector.dest == 1)
+ {
+ /* Temporarily translate it as the execution code does. */
+ redirects->instruction = r_err_and_out;
+ print_redirection (redirects);
+ redirects->instruction = r_duplicating_output_word;
+ }
+ else
+ print_redirection (redirects);
+
+ redirects = redirects->next;
+ if (redirects)
+ cprintf (" ");
+ }
+
+ /* Now that we've printed all the other redirections (on one line),
+ print the here documents. */
+ if (heredocs && printing_connection)
+ deferred_heredocs = heredocs;
+ else if (heredocs)
+ {
+ print_heredocs (heredocs);
+ dispose_redirects (heredocs);
+ }
+}
+
+static void
+print_heredoc_header (redirect)
+ REDIRECT *redirect;
+{
+ int kill_leading;
+ char *x;
+
+ kill_leading = redirect->instruction == r_deblank_reading_until;
+
+ /* Here doc header */
+ if (redirect->rflags & REDIR_VARASSIGN)
+ cprintf ("{%s}", redirect->redirector.filename->word);
+ else if (redirect->redirector.dest != 0)
+ cprintf ("%d", redirect->redirector.dest);
+
+ /* If the here document delimiter is quoted, single-quote it. */
+ if (redirect->redirectee.filename->flags & W_QUOTED)
+ {
+ x = sh_single_quote (redirect->here_doc_eof);
+ cprintf ("<<%s%s", kill_leading ? "-" : "", x);
+ free (x);
+ }
+ else
+ cprintf ("<<%s%s", kill_leading ? "-" : "", redirect->here_doc_eof);
+}
+
+static void
+print_heredoc_body (redirect)
+ REDIRECT *redirect;
+{
+ /* Here doc body */
+ cprintf ("%s%s", redirect->redirectee.filename->word, redirect->here_doc_eof);
+}
+
+static void
+print_redirection (redirect)
+ REDIRECT *redirect;
+{
+ int kill_leading, redirector, redir_fd;
+ WORD_DESC *redirectee, *redir_word;
+
+ kill_leading = 0;
+ redirectee = redirect->redirectee.filename;
+ redir_fd = redirect->redirectee.dest;
+
+ redir_word = redirect->redirector.filename;
+ redirector = redirect->redirector.dest;
+
+ switch (redirect->instruction)
+ {
+ case r_input_direction:
+ if (redirect->rflags & REDIR_VARASSIGN)
+ cprintf ("{%s}", redir_word->word);
+ else if (redirector != 0)
+ cprintf ("%d", redirector);
+ cprintf ("< %s", redirectee->word);
+ break;
+
+ case r_output_direction:
+ if (redirect->rflags & REDIR_VARASSIGN)
+ cprintf ("{%s}", redir_word->word);
+ else if (redirector != 1)
+ cprintf ("%d", redirector);
+ cprintf ("> %s", redirectee->word);
+ break;
+
+ case r_inputa_direction: /* Redirection created by the shell. */
+ cprintf ("&");
+ break;
+
+ case r_output_force:
+ if (redirect->rflags & REDIR_VARASSIGN)
+ cprintf ("{%s}", redir_word->word);
+ else if (redirector != 1)
+ cprintf ("%d", redirector);
+ cprintf (">|%s", redirectee->word);
+ break;
+
+ case r_appending_to:
+ if (redirect->rflags & REDIR_VARASSIGN)
+ cprintf ("{%s}", redir_word->word);
+ else if (redirector != 1)
+ cprintf ("%d", redirector);
+ cprintf (">> %s", redirectee->word);
+ break;
+
+ case r_input_output:
+ if (redirect->rflags & REDIR_VARASSIGN)
+ cprintf ("{%s}", redir_word->word);
+ else if (redirector != 1)
+ cprintf ("%d", redirector);
+ cprintf ("<> %s", redirectee->word);
+ break;
+
+ case r_deblank_reading_until:
+ case r_reading_until:
+ print_heredoc_header (redirect);
+ cprintf ("\n");
+ print_heredoc_body (redirect);
+ break;
+
+ case r_reading_string:
+ if (redirect->rflags & REDIR_VARASSIGN)
+ cprintf ("{%s}", redir_word->word);
+ else if (redirector != 0)
+ cprintf ("%d", redirector);
+#if 0
+ if (ansic_shouldquote (redirect->redirectee.filename->word))
+ {
+ char *x;
+ x = ansic_quote (redirect->redirectee.filename->word, 0, (int *)0);
+ cprintf ("<<< %s", x);
+ free (x);
+ }
+ else
+#endif
+ cprintf ("<<< %s", redirect->redirectee.filename->word);
+ break;
+
+ case r_duplicating_input:
+ if (redirect->rflags & REDIR_VARASSIGN)
+ cprintf ("{%s}<&%d", redir_word->word, redir_fd);
+ else
+ cprintf ("%d<&%d", redirector, redir_fd);
+ break;
+
+ case r_duplicating_output:
+ if (redirect->rflags & REDIR_VARASSIGN)
+ cprintf ("{%s}>&%d", redir_word->word, redir_fd);
+ else
+ cprintf ("%d>&%d", redirector, redir_fd);
+ break;
+
+ case r_duplicating_input_word:
+ if (redirect->rflags & REDIR_VARASSIGN)
+ cprintf ("{%s}<&%s", redir_word->word, redirectee->word);
+ else
+ cprintf ("%d<&%s", redirector, redirectee->word);
+ break;
+
+ case r_duplicating_output_word:
+ if (redirect->rflags & REDIR_VARASSIGN)
+ cprintf ("{%s}>&%s", redir_word->word, redirectee->word);
+ else
+ cprintf ("%d>&%s", redirector, redirectee->word);
+ break;
+
+ case r_move_input:
+ if (redirect->rflags & REDIR_VARASSIGN)
+ cprintf ("{%s}<&%d-", redir_word->word, redir_fd);
+ else
+ cprintf ("%d<&%d-", redirector, redir_fd);
+ break;
+
+ case r_move_output:
+ if (redirect->rflags & REDIR_VARASSIGN)
+ cprintf ("{%s}>&%d-", redir_word->word, redir_fd);
+ else
+ cprintf ("%d>&%d-", redirector, redir_fd);
+ break;
+
+ case r_move_input_word:
+ if (redirect->rflags & REDIR_VARASSIGN)
+ cprintf ("{%s}<&%s-", redir_word->word, redirectee->word);
+ else
+ cprintf ("%d<&%s-", redirector, redirectee->word);
+ break;
+
+ case r_move_output_word:
+ if (redirect->rflags & REDIR_VARASSIGN)
+ cprintf ("{%s}>&%s-", redir_word->word, redirectee->word);
+ else
+ cprintf ("%d>&%s-", redirector, redirectee->word);
+ break;
+
+ case r_close_this:
+ if (redirect->rflags & REDIR_VARASSIGN)
+ cprintf ("{%s}>&-", redir_word->word);
+ else
+ cprintf ("%d>&-", redirector);
+ break;
+
+ case r_err_and_out:
+ cprintf ("&>%s", redirectee->word);
+ break;
+
+ case r_append_err_and_out:
+ cprintf ("&>>%s", redirectee->word);
+ break;
+ }
+}
+
+static void
+reset_locals ()
+{
+ inside_function_def = 0;
+ indentation = 0;
+ printing_connection = 0;
+ deferred_heredocs = 0;
+}
+
+static void
+print_function_def (func)
+ FUNCTION_DEF *func;
+{
+ COMMAND *cmdcopy;
+ REDIRECT *func_redirects;
+
+ func_redirects = NULL;
+ cprintf ("function %s () \n", func->name->word);
+ add_unwind_protect (reset_locals, 0);
+
+ indent (indentation);
+ cprintf ("{ \n");
+
+ inside_function_def++;
+ indentation += indentation_amount;
+
+ cmdcopy = copy_command (func->command);
+ if (cmdcopy->type == cm_group)
+ {
+ func_redirects = cmdcopy->redirects;
+ cmdcopy->redirects = (REDIRECT *)NULL;
+ }
+ make_command_string_internal (cmdcopy->type == cm_group
+ ? cmdcopy->value.Group->command
+ : cmdcopy);
+
+ remove_unwind_protect ();
+ indentation -= indentation_amount;
+ inside_function_def--;
+
+ if (func_redirects)
+ { /* { */
+ newline ("} ");
+ print_redirection_list (func_redirects);
+ cmdcopy->redirects = func_redirects;
+ }
+ else
+ newline ("}");
+
+ dispose_command (cmdcopy);
+}
+
+/* Return the string representation of the named function.
+ NAME is the name of the function.
+ COMMAND is the function body. It should be a GROUP_COM.
+ flags&FUNC_MULTILINE is non-zero to pretty-print, or zero for all on one line.
+ flags&FUNC_EXTERNAL means convert from internal to external form
+ */
+char *
+named_function_string (name, command, flags)
+ char *name;
+ COMMAND *command;
+ int flags;
+{
+ char *result;
+ int old_indent, old_amount;
+ COMMAND *cmdcopy;
+ REDIRECT *func_redirects;
+
+ old_indent = indentation;
+ old_amount = indentation_amount;
+ command_string_index = was_heredoc = 0;
+ deferred_heredocs = 0;
+
+ if (name && *name)
+ cprintf ("%s ", name);
+
+ cprintf ("() ");
+
+ if ((flags & FUNC_MULTILINE) == 0)
+ {
+ indentation = 1;
+ indentation_amount = 0;
+ }
+ else
+ {
+ cprintf ("\n");
+ indentation += indentation_amount;
+ }
+
+ inside_function_def++;
+
+ cprintf ((flags & FUNC_MULTILINE) ? "{ \n" : "{ ");
+
+ cmdcopy = copy_command (command);
+ /* Take any redirections specified in the function definition (which should
+ apply to the function as a whole) and save them for printing later. */
+ func_redirects = (REDIRECT *)NULL;
+ if (cmdcopy->type == cm_group)
+ {
+ func_redirects = cmdcopy->redirects;
+ cmdcopy->redirects = (REDIRECT *)NULL;
+ }
+ make_command_string_internal (cmdcopy->type == cm_group
+ ? cmdcopy->value.Group->command
+ : cmdcopy);
+
+ indentation = old_indent;
+ indentation_amount = old_amount;
+ inside_function_def--;
+
+ if (func_redirects)
+ { /* { */
+ newline ("} ");
+ print_redirection_list (func_redirects);
+ cmdcopy->redirects = func_redirects;
+ }
+ else
+ newline ("}");
+
+ result = the_printed_command;
+
+ if ((flags & FUNC_MULTILINE) == 0)
+ {
+#if 0
+ register int i;
+ for (i = 0; result[i]; i++)
+ if (result[i] == '\n')
+ {
+ strcpy (result + i, result + i + 1);
+ --i;
+ }
+#else
+ if (result[2] == '\n') /* XXX -- experimental */
+ strcpy (result + 2, result + 3);
+#endif
+ }
+
+ dispose_command (cmdcopy);
+
+ if (flags & FUNC_EXTERNAL)
+ result = remove_quoted_escapes (result);
+
+ return (result);
+}
+
+static void
+newline (string)
+ char *string;
+{
+ cprintf ("\n");
+ indent (indentation);
+ if (string && *string)
+ cprintf ("%s", string);
+}
+
+static char *indentation_string;
+static int indentation_size;
+
+static void
+indent (amount)
+ int amount;
+{
+ register int i;
+
+ RESIZE_MALLOCED_BUFFER (indentation_string, 0, amount, indentation_size, 16);
+
+ for (i = 0; amount > 0; amount--)
+ indentation_string[i++] = ' ';
+ indentation_string[i] = '\0';
+ cprintf (indentation_string);
+}
+
+static void
+semicolon ()
+{
+ if (command_string_index > 0 &&
+ (the_printed_command[command_string_index - 1] == '&' ||
+ the_printed_command[command_string_index - 1] == '\n'))
+ return;
+ cprintf (";");
+}
+
+/* How to make the string. */
+static void
+#if defined (PREFER_STDARG)
+cprintf (const char *control, ...)
+#else
+cprintf (control, va_alist)
+ const char *control;
+ va_dcl
+#endif
+{
+ register const char *s;
+ char char_arg[2], *argp, intbuf[INT_STRLEN_BOUND (int) + 1];
+ int digit_arg, arg_len, c;
+ va_list args;
+
+ SH_VA_START (args, control);
+
+ arg_len = strlen (control);
+ the_printed_command_resize (arg_len + 1);
+
+ char_arg[1] = '\0';
+ s = control;
+ while (s && *s)
+ {
+ c = *s++;
+ argp = (char *)NULL;
+ if (c != '%' || !*s)
+ {
+ char_arg[0] = c;
+ argp = char_arg;
+ arg_len = 1;
+ }
+ else
+ {
+ c = *s++;
+ switch (c)
+ {
+ case '%':
+ char_arg[0] = c;
+ argp = char_arg;
+ arg_len = 1;
+ break;
+
+ case 's':
+ argp = va_arg (args, char *);
+ arg_len = strlen (argp);
+ break;
+
+ case 'd':
+ /* Represent an out-of-range file descriptor with an out-of-range
+ integer value. We can do this because the only use of `%d' in
+ the calls to cprintf is to output a file descriptor number for
+ a redirection. */
+ digit_arg = va_arg (args, int);
+ if (digit_arg < 0)
+ {
+ sprintf (intbuf, "%u", (unsigned)-1);
+ argp = intbuf;
+ }
+ else
+ argp = inttostr (digit_arg, intbuf, sizeof (intbuf));
+ arg_len = strlen (argp);
+ break;
+
+ case 'c':
+ char_arg[0] = va_arg (args, int);
+ argp = char_arg;
+ arg_len = 1;
+ break;
+
+ default:
+ programming_error (_("cprintf: `%c': invalid format character"), c);
+ /*NOTREACHED*/
+ }
+ }
+
+ if (argp && arg_len)
+ {
+ the_printed_command_resize (arg_len + 1);
+ FASTCOPY (argp, the_printed_command + command_string_index, arg_len);
+ command_string_index += arg_len;
+ }
+ }
+
+ the_printed_command[command_string_index] = '\0';
+}
+
+/* Ensure that there is enough space to stuff LENGTH characters into
+ THE_PRINTED_COMMAND. */
+static void
+the_printed_command_resize (length)
+ int length;
+{
+ if (the_printed_command == 0)
+ {
+ the_printed_command_size = (length + PRINTED_COMMAND_INITIAL_SIZE - 1) & ~(PRINTED_COMMAND_INITIAL_SIZE - 1);
+ the_printed_command = (char *)xmalloc (the_printed_command_size);
+ command_string_index = 0;
+ }
+ else if ((command_string_index + length) >= the_printed_command_size)
+ {
+ int new;
+ new = command_string_index + length + 1;
+
+ /* Round up to the next multiple of PRINTED_COMMAND_GROW_SIZE. */
+ new = (new + PRINTED_COMMAND_GROW_SIZE - 1) & ~(PRINTED_COMMAND_GROW_SIZE - 1);
+ the_printed_command_size = new;
+
+ the_printed_command = (char *)xrealloc (the_printed_command, the_printed_command_size);
+ }
+}
+
+#if defined (HAVE_VPRINTF)
+/* ``If vprintf is available, you may assume that vfprintf and vsprintf are
+ also available.'' */
+
+static void
+#if defined (PREFER_STDARG)
+xprintf (const char *format, ...)
+#else
+xprintf (format, va_alist)
+ const char *format;
+ va_dcl
+#endif
+{
+ va_list args;
+
+ SH_VA_START (args, format);
+
+ vfprintf (stdout, format, args);
+ va_end (args);
+}
+
+#else
+
+static void
+xprintf (format, arg1, arg2, arg3, arg4, arg5)
+ const char *format;
+{
+ printf (format, arg1, arg2, arg3, arg4, arg5);
+}
+
+#endif /* !HAVE_VPRINTF */
extern int comsub_ignore_return;
extern int parse_and_execute_level, shell_initialized;
+extern void intialize_siglist ();
+
/* Non-zero after SIGINT. */
volatile int interrupt_state = 0;
--- /dev/null
+/* sig.c - interface for shell signal handlers and signal initialization. */
+
+/* Copyright (C) 1994-2009 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"
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <signal.h>
+
+#include "bashintl.h"
+
+#include "shell.h"
+#if defined (JOB_CONTROL)
+#include "jobs.h"
+#endif /* JOB_CONTROL */
+#include "siglist.h"
+#include "sig.h"
+#include "trap.h"
+
+#include "builtins/common.h"
+
+#if defined (READLINE)
+# include "bashline.h"
+#endif
+
+#if defined (HISTORY)
+# include "bashhist.h"
+#endif
+
+extern int last_command_exit_value;
+extern int last_command_exit_signal;
+extern int return_catch_flag;
+extern int loop_level, continuing, breaking;
+extern int executing_list;
+extern int comsub_ignore_return;
+extern int parse_and_execute_level, shell_initialized;
+
+/* Non-zero after SIGINT. */
+volatile int interrupt_state = 0;
+
+/* Non-zero after SIGWINCH */
+volatile int sigwinch_received = 0;
+
+/* Set to the value of any terminating signal received. */
+volatile int terminating_signal = 0;
+
+/* The environment at the top-level R-E loop. We use this in
+ the case of error return. */
+procenv_t top_level;
+
+#if defined (JOB_CONTROL) || defined (HAVE_POSIX_SIGNALS)
+/* The signal masks that this shell runs with. */
+sigset_t top_level_mask;
+#endif /* JOB_CONTROL */
+
+/* When non-zero, we throw_to_top_level (). */
+int interrupt_immediately = 0;
+
+/* When non-zero, we call the terminating signal handler immediately. */
+int terminate_immediately = 0;
+
+#if defined (SIGWINCH)
+static SigHandler *old_winch = (SigHandler *)SIG_DFL;
+#endif
+
+static void initialize_shell_signals __P((void));
+
+void
+initialize_signals (reinit)
+ int reinit;
+{
+ initialize_shell_signals ();
+ initialize_job_signals ();
+#if !defined (HAVE_SYS_SIGLIST) && !defined (HAVE_UNDER_SYS_SIGLIST) && !defined (HAVE_STRSIGNAL)
+ if (reinit == 0)
+ initialize_siglist ();
+#endif /* !HAVE_SYS_SIGLIST && !HAVE_UNDER_SYS_SIGLIST && !HAVE_STRSIGNAL */
+}
+
+/* A structure describing a signal that terminates the shell if not
+ caught. The orig_handler member is present so children can reset
+ these signals back to their original handlers. */
+struct termsig {
+ int signum;
+ SigHandler *orig_handler;
+ int orig_flags;
+};
+
+#define NULL_HANDLER (SigHandler *)SIG_DFL
+
+/* The list of signals that would terminate the shell if not caught.
+ We catch them, but just so that we can write the history file,
+ and so forth. */
+static struct termsig terminating_signals[] = {
+#ifdef SIGHUP
+{ SIGHUP, NULL_HANDLER, 0 },
+#endif
+
+#ifdef SIGINT
+{ SIGINT, NULL_HANDLER, 0 },
+#endif
+
+#ifdef SIGILL
+{ SIGILL, NULL_HANDLER, 0 },
+#endif
+
+#ifdef SIGTRAP
+{ SIGTRAP, NULL_HANDLER, 0 },
+#endif
+
+#ifdef SIGIOT
+{ SIGIOT, NULL_HANDLER, 0 },
+#endif
+
+#ifdef SIGDANGER
+{ SIGDANGER, NULL_HANDLER, 0 },
+#endif
+
+#ifdef SIGEMT
+{ SIGEMT, NULL_HANDLER, 0 },
+#endif
+
+#ifdef SIGFPE
+{ SIGFPE, NULL_HANDLER, 0 },
+#endif
+
+#ifdef SIGBUS
+{ SIGBUS, NULL_HANDLER, 0 },
+#endif
+
+#ifdef SIGSEGV
+{ SIGSEGV, NULL_HANDLER, 0 },
+#endif
+
+#ifdef SIGSYS
+{ SIGSYS, NULL_HANDLER, 0 },
+#endif
+
+#ifdef SIGPIPE
+{ SIGPIPE, NULL_HANDLER, 0 },
+#endif
+
+#ifdef SIGALRM
+{ SIGALRM, NULL_HANDLER, 0 },
+#endif
+
+#ifdef SIGTERM
+{ SIGTERM, NULL_HANDLER, 0 },
+#endif
+
+#ifdef SIGXCPU
+{ SIGXCPU, NULL_HANDLER, 0 },
+#endif
+
+#ifdef SIGXFSZ
+{ SIGXFSZ, NULL_HANDLER, 0 },
+#endif
+
+#ifdef SIGVTALRM
+{ SIGVTALRM, NULL_HANDLER, 0 },
+#endif
+
+#if 0
+#ifdef SIGPROF
+{ SIGPROF, NULL_HANDLER, 0 },
+#endif
+#endif
+
+#ifdef SIGLOST
+{ SIGLOST, NULL_HANDLER, 0 },
+#endif
+
+#ifdef SIGUSR1
+{ SIGUSR1, NULL_HANDLER, 0 },
+#endif
+
+#ifdef SIGUSR2
+{ SIGUSR2, NULL_HANDLER, 0 },
+#endif
+};
+
+#define TERMSIGS_LENGTH (sizeof (terminating_signals) / sizeof (struct termsig))
+
+#define XSIG(x) (terminating_signals[x].signum)
+#define XHANDLER(x) (terminating_signals[x].orig_handler)
+#define XSAFLAGS(x) (terminating_signals[x].orig_flags)
+
+static int termsigs_initialized = 0;
+
+/* Initialize signals that will terminate the shell to do some
+ unwind protection. For non-interactive shells, we only call
+ this when a trap is defined for EXIT (0). */
+void
+initialize_terminating_signals ()
+{
+ register int i;
+#if defined (HAVE_POSIX_SIGNALS)
+ struct sigaction act, oact;
+#endif
+
+ if (termsigs_initialized)
+ return;
+
+ /* The following code is to avoid an expensive call to
+ set_signal_handler () for each terminating_signals. Fortunately,
+ this is possible in Posix. Unfortunately, we have to call signal ()
+ on non-Posix systems for each signal in terminating_signals. */
+#if defined (HAVE_POSIX_SIGNALS)
+ act.sa_handler = termsig_sighandler;
+ act.sa_flags = 0;
+ sigemptyset (&act.sa_mask);
+ sigemptyset (&oact.sa_mask);
+ for (i = 0; i < TERMSIGS_LENGTH; i++)
+ sigaddset (&act.sa_mask, XSIG (i));
+ for (i = 0; i < TERMSIGS_LENGTH; i++)
+ {
+ /* If we've already trapped it, don't do anything. */
+ if (signal_is_trapped (XSIG (i)))
+ continue;
+
+ sigaction (XSIG (i), &act, &oact);
+ XHANDLER(i) = oact.sa_handler;
+ XSAFLAGS(i) = oact.sa_flags;
+ /* Don't do anything with signals that are ignored at shell entry
+ if the shell is not interactive. */
+ if (!interactive_shell && XHANDLER (i) == SIG_IGN)
+ {
+ sigaction (XSIG (i), &oact, &act);
+ set_signal_ignored (XSIG (i));
+ }
+#if defined (SIGPROF) && !defined (_MINIX)
+ if (XSIG (i) == SIGPROF && XHANDLER (i) != SIG_DFL && XHANDLER (i) != SIG_IGN)
+ sigaction (XSIG (i), &oact, (struct sigaction *)NULL);
+#endif /* SIGPROF && !_MINIX */
+ }
+
+#else /* !HAVE_POSIX_SIGNALS */
+
+ for (i = 0; i < TERMSIGS_LENGTH; i++)
+ {
+ /* If we've already trapped it, don't do anything. */
+ if (signal_is_trapped (XSIG (i)))
+ continue;
+
+ XHANDLER(i) = signal (XSIG (i), termsig_sighandler);
+ XSAFLAGS(i) = 0;
+ /* Don't do anything with signals that are ignored at shell entry
+ if the shell is not interactive. */
+ if (!interactive_shell && XHANDLER (i) == SIG_IGN)
+ {
+ signal (XSIG (i), SIG_IGN);
+ set_signal_ignored (XSIG (i));
+ }
+#ifdef SIGPROF
+ if (XSIG (i) == SIGPROF && XHANDLER (i) != SIG_DFL && XHANDLER (i) != SIG_IGN)
+ signal (XSIG (i), XHANDLER (i));
+#endif
+ }
+
+#endif /* !HAVE_POSIX_SIGNALS */
+
+ termsigs_initialized = 1;
+}
+
+static void
+initialize_shell_signals ()
+{
+ if (interactive)
+ initialize_terminating_signals ();
+
+#if defined (JOB_CONTROL) || defined (HAVE_POSIX_SIGNALS)
+ /* All shells use the signal mask they inherit, and pass it along
+ to child processes. Children will never block SIGCHLD, though. */
+ sigemptyset (&top_level_mask);
+ sigprocmask (SIG_BLOCK, (sigset_t *)NULL, &top_level_mask);
+# if defined (SIGCHLD)
+ sigdelset (&top_level_mask, SIGCHLD);
+# endif
+#endif /* JOB_CONTROL || HAVE_POSIX_SIGNALS */
+
+ /* And, some signals that are specifically ignored by the shell. */
+ set_signal_handler (SIGQUIT, SIG_IGN);
+
+ if (interactive)
+ {
+ set_signal_handler (SIGINT, sigint_sighandler);
+ set_signal_handler (SIGTERM, SIG_IGN);
+ set_sigwinch_handler ();
+ }
+}
+
+void
+reset_terminating_signals ()
+{
+ register int i;
+#if defined (HAVE_POSIX_SIGNALS)
+ struct sigaction act;
+#endif
+
+ if (termsigs_initialized == 0)
+ return;
+
+#if defined (HAVE_POSIX_SIGNALS)
+ act.sa_flags = 0;
+ sigemptyset (&act.sa_mask);
+ for (i = 0; i < TERMSIGS_LENGTH; i++)
+ {
+ /* Skip a signal if it's trapped or handled specially, because the
+ trap code will restore the correct value. */
+ if (signal_is_trapped (XSIG (i)) || signal_is_special (XSIG (i)))
+ continue;
+
+ act.sa_handler = XHANDLER (i);
+ act.sa_flags = XSAFLAGS (i);
+ sigaction (XSIG (i), &act, (struct sigaction *) NULL);
+ }
+#else /* !HAVE_POSIX_SIGNALS */
+ for (i = 0; i < TERMSIGS_LENGTH; i++)
+ {
+ if (signal_is_trapped (XSIG (i)) || signal_is_special (XSIG (i)))
+ continue;
+
+ signal (XSIG (i), XHANDLER (i));
+ }
+#endif /* !HAVE_POSIX_SIGNALS */
+}
+#undef XSIG
+#undef XHANDLER
+
+/* Run some of the cleanups that should be performed when we run
+ jump_to_top_level from a builtin command context. XXX - might want to
+ also call reset_parser here. */
+void
+top_level_cleanup ()
+{
+ /* Clean up string parser environment. */
+ while (parse_and_execute_level)
+ parse_and_execute_cleanup ();
+
+#if defined (PROCESS_SUBSTITUTION)
+ unlink_fifo_list ();
+#endif /* PROCESS_SUBSTITUTION */
+
+ run_unwind_protects ();
+ loop_level = continuing = breaking = 0;
+ executing_list = comsub_ignore_return = return_catch_flag = 0;
+}
+
+/* What to do when we've been interrupted, and it is safe to handle it. */
+void
+throw_to_top_level ()
+{
+ int print_newline = 0;
+
+ if (interrupt_state)
+ {
+ print_newline = 1;
+ DELINTERRUPT;
+ }
+
+ if (interrupt_state)
+ return;
+
+ last_command_exit_signal = (last_command_exit_value > 128) ?
+ (last_command_exit_value - 128) : 0;
+ last_command_exit_value |= 128;
+
+ /* Run any traps set on SIGINT. */
+ run_interrupt_trap ();
+
+ /* Clean up string parser environment. */
+ while (parse_and_execute_level)
+ parse_and_execute_cleanup ();
+
+#if defined (JOB_CONTROL)
+ give_terminal_to (shell_pgrp, 0);
+#endif /* JOB_CONTROL */
+
+#if defined (JOB_CONTROL) || defined (HAVE_POSIX_SIGNALS)
+ /* This should not be necessary on systems using sigsetjmp/siglongjmp. */
+ sigprocmask (SIG_SETMASK, &top_level_mask, (sigset_t *)NULL);
+#endif
+
+ reset_parser ();
+
+#if defined (READLINE)
+ if (interactive)
+ bashline_reset ();
+#endif /* READLINE */
+
+#if defined (PROCESS_SUBSTITUTION)
+ unlink_fifo_list ();
+#endif /* PROCESS_SUBSTITUTION */
+
+ run_unwind_protects ();
+ loop_level = continuing = breaking = 0;
+ executing_list = comsub_ignore_return = return_catch_flag = 0;
+
+ if (interactive && print_newline)
+ {
+ fflush (stdout);
+ fprintf (stderr, "\n");
+ fflush (stderr);
+ }
+
+ /* An interrupted `wait' command in a script does not exit the script. */
+ if (interactive || (interactive_shell && !shell_initialized) ||
+ (print_newline && signal_is_trapped (SIGINT)))
+ jump_to_top_level (DISCARD);
+ else
+ jump_to_top_level (EXITPROG);
+}
+
+/* This is just here to isolate the longjmp calls. */
+void
+jump_to_top_level (value)
+ int value;
+{
+ longjmp (top_level, value);
+}
+
+sighandler
+termsig_sighandler (sig)
+ int sig;
+{
+ /* If we get called twice with the same signal before handling it,
+ terminate right away. */
+ if (
+#ifdef SIGHUP
+ sig != SIGHUP &&
+#endif
+#ifdef SIGINT
+ sig != SIGINT &&
+#endif
+#ifdef SIGDANGER
+ sig != SIGDANGER &&
+#endif
+#ifdef SIGPIPE
+ sig != SIGPIPE &&
+#endif
+#ifdef SIGALRM
+ sig != SIGALRM &&
+#endif
+#ifdef SIGTERM
+ sig != SIGTERM &&
+#endif
+#ifdef SIGXCPU
+ sig != SIGXCPU &&
+#endif
+#ifdef SIGXFSZ
+ sig != SIGXFSZ &&
+#endif
+#ifdef SIGVTALRM
+ sig != SIGVTALRM &&
+#endif
+#ifdef SIGLOST
+ sig != SIGLOST &&
+#endif
+#ifdef SIGUSR1
+ sig != SIGUSR1 &&
+#endif
+#ifdef SIGUSR2
+ sig != SIGUSR2 &&
+#endif
+ sig == terminating_signal)
+ terminate_immediately = 1;
+
+ terminating_signal = sig;
+
+ /* XXX - should this also trigger when interrupt_immediately is set? */
+ if (terminate_immediately)
+ {
+ terminate_immediately = 0;
+ termsig_handler (sig);
+ }
+
+ SIGRETURN (0);
+}
+
+void
+termsig_handler (sig)
+ int sig;
+{
+ static int handling_termsig = 0;
+
+ /* Simple semaphore to keep this function from being executed multiple
+ times. Since we no longer are running as a signal handler, we don't
+ block multiple occurrences of the terminating signals while running. */
+ if (handling_termsig)
+ return;
+ handling_termsig = 1;
+ terminating_signal = 0; /* keep macro from re-testing true. */
+
+ /* I don't believe this condition ever tests true. */
+ if (sig == SIGINT && signal_is_trapped (SIGINT))
+ run_interrupt_trap ();
+
+#if defined (HISTORY)
+ if (interactive_shell && sig != SIGABRT)
+ maybe_save_shell_history ();
+#endif /* HISTORY */
+
+#if defined (JOB_CONTROL)
+ if (sig == SIGHUP && (interactive || (subshell_environment & (SUBSHELL_COMSUB|SUBSHELL_PROCSUB))))
+ hangup_all_jobs ();
+ end_job_control ();
+#endif /* JOB_CONTROL */
+
+#if defined (PROCESS_SUBSTITUTION)
+ unlink_fifo_list ();
+#endif /* PROCESS_SUBSTITUTION */
+
+ /* Reset execution context */
+ loop_level = continuing = breaking = 0;
+ executing_list = comsub_ignore_return = return_catch_flag = 0;
+
+ run_exit_trap ();
+ set_signal_handler (sig, SIG_DFL);
+ kill (getpid (), sig);
+}
+
+/* What we really do when SIGINT occurs. */
+sighandler
+sigint_sighandler (sig)
+ int sig;
+{
+#if defined (MUST_REINSTALL_SIGHANDLERS)
+ signal (sig, sigint_sighandler);
+#endif
+
+ /* interrupt_state needs to be set for the stack of interrupts to work
+ right. Should it be set unconditionally? */
+ if (interrupt_state == 0)
+ ADDINTERRUPT;
+
+ if (interrupt_immediately)
+ {
+ interrupt_immediately = 0;
+ last_command_exit_value = 128 + sig;
+ throw_to_top_level ();
+ }
+
+ SIGRETURN (0);
+}
+
+#if defined (SIGWINCH)
+sighandler
+sigwinch_sighandler (sig)
+ int sig;
+{
+#if defined (MUST_REINSTALL_SIGHANDLERS)
+ set_signal_handler (SIGWINCH, sigwinch_sighandler);
+#endif /* MUST_REINSTALL_SIGHANDLERS */
+ sigwinch_received = 1;
+ SIGRETURN (0);
+}
+#endif /* SIGWINCH */
+
+void
+set_sigwinch_handler ()
+{
+#if defined (SIGWINCH)
+ old_winch = set_signal_handler (SIGWINCH, sigwinch_sighandler);
+#endif
+}
+
+void
+unset_sigwinch_handler ()
+{
+#if defined (SIGWINCH)
+ set_signal_handler (SIGWINCH, old_winch);
+#endif
+}
+
+/* Signal functions used by the rest of the code. */
+#if !defined (HAVE_POSIX_SIGNALS)
+
+#if defined (JOB_CONTROL)
+/* Perform OPERATION on NEWSET, perhaps leaving information in OLDSET. */
+sigprocmask (operation, newset, oldset)
+ int operation, *newset, *oldset;
+{
+ int old, new;
+
+ if (newset)
+ new = *newset;
+ else
+ new = 0;
+
+ switch (operation)
+ {
+ case SIG_BLOCK:
+ old = sigblock (new);
+ break;
+
+ case SIG_SETMASK:
+ sigsetmask (new);
+ break;
+
+ default:
+ internal_error (_("sigprocmask: %d: invalid operation"), operation);
+ }
+
+ if (oldset)
+ *oldset = old;
+}
+#endif /* JOB_CONTROL */
+
+#else
+
+#if !defined (SA_INTERRUPT)
+# define SA_INTERRUPT 0
+#endif
+
+#if !defined (SA_RESTART)
+# define SA_RESTART 0
+#endif
+
+SigHandler *
+set_signal_handler (sig, handler)
+ int sig;
+ SigHandler *handler;
+{
+ struct sigaction act, oact;
+
+ act.sa_handler = handler;
+ act.sa_flags = 0;
+#if 0
+ if (sig == SIGALRM)
+ act.sa_flags |= SA_INTERRUPT; /* XXX */
+ else
+ act.sa_flags |= SA_RESTART; /* XXX */
+#endif
+ sigemptyset (&act.sa_mask);
+ sigemptyset (&oact.sa_mask);
+ sigaction (sig, &act, &oact);
+ return (oact.sa_handler);
+}
+#endif /* HAVE_POSIX_SIGNALS */
char *akey;
char *t, c;
ARRAY *array;
+ HASH_TABLE *h;
SHELL_VAR *var;
var = array_variable_part (s, &t, &len);
v[*]. Return 0 for everything else. */
array = array_p (var) ? array_cell (var) : (ARRAY *)NULL;
+ h = assoc_p (var) ? assoc_cell (var) : (HASH_TABLE *)NULL;
if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']')
{
if (assoc_p (var))
- return (assoc_num_elements (assoc_cell (var)));
+ return (h ? assoc_num_elements (h) : 0);
else if (array_p (var))
- return (array_num_elements (array));
+ return (array ? array_num_elements (array) : 0);
else
- return 1;
+ return (var_isset (var) ? 1 : 0);
}
if (assoc_p (var))
#if defined (ARRAY_VARS)
else if (valid_array_reference (name))
{
- temp = array_value (name, quoted, &atype);
+ temp = array_value (name, quoted, &atype, (arrayind_t *)NULL);
if (atype == 0 && temp)
temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
? quote_string (temp)
else
{
vtype = VT_ARRAYMEMBER;
- *valp = array_value (varname, 1, (int *)NULL);
+ *valp = array_value (varname, 1, (int *)NULL, (arrayind_t *)NULL);
}
*varp = v;
}
{
vtype = VT_ARRAYMEMBER;
*varp = v;
- *valp = array_value (varname, 1, (int *)NULL);
+ *valp = array_value (varname, 1, (int *)NULL, (arrayind_t *)NULL);
}
}
else if ((v = find_variable (varname)) && (invisible_p (v) == 0) && (assoc_p (v) || array_p (v)))
--- /dev/null
+/* subst.c -- The part of the shell that does parameter, command, arithmetic,
+ and globbing substitutions. */
+
+/* ``Have a little faith, there's magic in the night. You ain't a
+ beauty, but, hey, you're alright.'' */
+
+/* Copyright (C) 1987-2009 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 <stdio.h>
+#include "chartypes.h"
+#if defined (HAVE_PWD_H)
+# include <pwd.h>
+#endif
+#include <signal.h>
+#include <errno.h>
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#include "bashansi.h"
+#include "posixstat.h"
+#include "bashintl.h"
+
+#include "shell.h"
+#include "flags.h"
+#include "jobs.h"
+#include "execute_cmd.h"
+#include "filecntl.h"
+#include "trap.h"
+#include "pathexp.h"
+#include "mailcheck.h"
+
+#include "shmbutil.h"
+
+#include "builtins/getopt.h"
+#include "builtins/common.h"
+
+#include "builtins/builtext.h"
+
+#include <tilde/tilde.h>
+#include <glob/strmatch.h>
+
+#if !defined (errno)
+extern int errno;
+#endif /* !errno */
+
+/* The size that strings change by. */
+#define DEFAULT_INITIAL_ARRAY_SIZE 112
+#define DEFAULT_ARRAY_SIZE 128
+
+/* Variable types. */
+#define VT_VARIABLE 0
+#define VT_POSPARMS 1
+#define VT_ARRAYVAR 2
+#define VT_ARRAYMEMBER 3
+#define VT_ASSOCVAR 4
+
+#define VT_STARSUB 128 /* $* or ${array[*]} -- used to split */
+
+/* Flags for quoted_strchr */
+#define ST_BACKSL 0x01
+#define ST_CTLESC 0x02
+#define ST_SQUOTE 0x04 /* unused yet */
+#define ST_DQUOTE 0x08 /* unused yet */
+
+/* Flags for the `pflags' argument to param_expand() */
+#define PF_NOCOMSUB 0x01 /* Do not perform command substitution */
+#define PF_IGNUNBOUND 0x02 /* ignore unbound vars even if -u set */
+#define PF_NOSPLIT2 0x04 /* same as W_NOSPLIT2 */
+
+/* These defs make it easier to use the editor. */
+#define LBRACE '{'
+#define RBRACE '}'
+#define LPAREN '('
+#define RPAREN ')'
+
+#if defined (HANDLE_MULTIBYTE)
+#define WLPAREN L'('
+#define WRPAREN L')'
+#endif
+
+/* Evaluates to 1 if C is one of the shell's special parameters whose length
+ can be taken, but is also one of the special expansion characters. */
+#define VALID_SPECIAL_LENGTH_PARAM(c) \
+ ((c) == '-' || (c) == '?' || (c) == '#')
+
+/* Evaluates to 1 if C is one of the shell's special parameters for which an
+ indirect variable reference may be made. */
+#define VALID_INDIR_PARAM(c) \
+ ((c) == '#' || (c) == '?' || (c) == '@' || (c) == '*')
+
+/* Evaluates to 1 if C is one of the OP characters that follows the parameter
+ in ${parameter[:]OPword}. */
+#define VALID_PARAM_EXPAND_CHAR(c) (sh_syntaxtab[(unsigned char)c] & CSUBSTOP)
+
+/* Evaluates to 1 if this is one of the shell's special variables. */
+#define SPECIAL_VAR(name, wi) \
+ ((DIGIT (*name) && all_digits (name)) || \
+ (name[1] == '\0' && (sh_syntaxtab[(unsigned char)*name] & CSPECVAR)) || \
+ (wi && name[2] == '\0' && VALID_INDIR_PARAM (name[1])))
+
+/* An expansion function that takes a string and a quoted flag and returns
+ a WORD_LIST *. Used as the type of the third argument to
+ expand_string_if_necessary(). */
+typedef WORD_LIST *EXPFUNC __P((char *, int));
+
+/* Process ID of the last command executed within command substitution. */
+pid_t last_command_subst_pid = NO_PID;
+pid_t current_command_subst_pid = NO_PID;
+
+/* Variables used to keep track of the characters in IFS. */
+SHELL_VAR *ifs_var;
+char *ifs_value;
+unsigned char ifs_cmap[UCHAR_MAX + 1];
+
+#if defined (HANDLE_MULTIBYTE)
+unsigned char ifs_firstc[MB_LEN_MAX];
+size_t ifs_firstc_len;
+#else
+unsigned char ifs_firstc;
+#endif
+
+/* Sentinel to tell when we are performing variable assignments preceding a
+ command name and putting them into the environment. Used to make sure
+ we use the temporary environment when looking up variable values. */
+int assigning_in_environment;
+
+/* Used to hold a list of variable assignments preceding a command. Global
+ so the SIGCHLD handler in jobs.c can unwind-protect it when it runs a
+ SIGCHLD trap and so it can be saved and restored by the trap handlers. */
+WORD_LIST *subst_assign_varlist = (WORD_LIST *)NULL;
+
+/* Extern functions and variables from different files. */
+extern int last_command_exit_value, last_command_exit_signal;
+extern int subshell_environment, line_number;
+extern int subshell_level, parse_and_execute_level, sourcelevel;
+extern int eof_encountered;
+extern int return_catch_flag, return_catch_value;
+extern pid_t dollar_dollar_pid;
+extern int posixly_correct;
+extern char *this_command_name;
+extern struct fd_bitmap *current_fds_to_close;
+extern int wordexp_only;
+extern int expanding_redir;
+extern int tempenv_assign_error;
+
+#if !defined (HAVE_WCSDUP) && defined (HANDLE_MULTIBYTE)
+extern wchar_t *wcsdup __P((const wchar_t *));
+#endif
+
+/* Non-zero means to allow unmatched globbed filenames to expand to
+ a null file. */
+int allow_null_glob_expansion;
+
+/* Non-zero means to throw an error when globbing fails to match anything. */
+int fail_glob_expansion;
+
+#if 0
+/* Variables to keep track of which words in an expanded word list (the
+ output of expand_word_list_internal) are the result of globbing
+ expansions. GLOB_ARGV_FLAGS is used by execute_cmd.c.
+ (CURRENTLY UNUSED). */
+char *glob_argv_flags;
+static int glob_argv_flags_size;
+#endif
+
+static WORD_LIST expand_word_error, expand_word_fatal;
+static WORD_DESC expand_wdesc_error, expand_wdesc_fatal;
+static char expand_param_error, expand_param_fatal;
+static char extract_string_error, extract_string_fatal;
+
+/* Tell the expansion functions to not longjmp back to top_level on fatal
+ errors. Enabled when doing completion and prompt string expansion. */
+static int no_longjmp_on_fatal_error = 0;
+
+/* Set by expand_word_unsplit; used to inhibit splitting and re-joining
+ $* on $IFS, primarily when doing assignment statements. */
+static int expand_no_split_dollar_star = 0;
+
+/* A WORD_LIST of words to be expanded by expand_word_list_internal,
+ without any leading variable assignments. */
+static WORD_LIST *garglist = (WORD_LIST *)NULL;
+
+static char *quoted_substring __P((char *, int, int));
+static int quoted_strlen __P((char *));
+static char *quoted_strchr __P((char *, int, int));
+
+static char *expand_string_if_necessary __P((char *, int, EXPFUNC *));
+static inline char *expand_string_to_string_internal __P((char *, int, EXPFUNC *));
+static WORD_LIST *call_expand_word_internal __P((WORD_DESC *, int, int, int *, int *));
+static WORD_LIST *expand_string_internal __P((char *, int));
+static WORD_LIST *expand_string_leave_quoted __P((char *, int));
+static WORD_LIST *expand_string_for_rhs __P((char *, int, int *, int *));
+
+static WORD_LIST *list_quote_escapes __P((WORD_LIST *));
+static char *make_quoted_char __P((int));
+static WORD_LIST *quote_list __P((WORD_LIST *));
+
+static int unquoted_substring __P((char *, char *));
+static int unquoted_member __P((int, char *));
+
+#if defined (ARRAY_VARS)
+static SHELL_VAR *do_compound_assignment __P((char *, char *, int));
+#endif
+static int do_assignment_internal __P((const WORD_DESC *, int));
+
+static char *string_extract_verbatim __P((char *, size_t, int *, char *, int));
+static char *string_extract __P((char *, int *, char *, int));
+static char *string_extract_double_quoted __P((char *, int *, int));
+static inline char *string_extract_single_quoted __P((char *, int *));
+static inline int skip_single_quoted __P((const char *, size_t, int));
+static int skip_double_quoted __P((char *, size_t, int));
+static char *extract_delimited_string __P((char *, int *, char *, char *, char *, int));
+static char *extract_dollar_brace_string __P((char *, int *, int, int));
+static int skip_matched_pair __P((const char *, int, int, int, int));
+
+static char *pos_params __P((char *, int, int, int));
+
+static unsigned char *mb_getcharlens __P((char *, int));
+
+static char *remove_upattern __P((char *, char *, int));
+#if defined (HANDLE_MULTIBYTE)
+static wchar_t *remove_wpattern __P((wchar_t *, size_t, wchar_t *, int));
+#endif
+static char *remove_pattern __P((char *, char *, int));
+
+static int match_pattern_char __P((char *, char *));
+static int match_upattern __P((char *, char *, int, char **, char **));
+#if defined (HANDLE_MULTIBYTE)
+static int match_pattern_wchar __P((wchar_t *, wchar_t *));
+static int match_wpattern __P((wchar_t *, char **, size_t, wchar_t *, int, char **, char **));
+#endif
+static int match_pattern __P((char *, char *, int, char **, char **));
+static int getpatspec __P((int, char *));
+static char *getpattern __P((char *, int, int));
+static char *variable_remove_pattern __P((char *, char *, int, int));
+static char *list_remove_pattern __P((WORD_LIST *, char *, int, int, int));
+static char *parameter_list_remove_pattern __P((int, char *, int, int));
+#ifdef ARRAY_VARS
+static char *array_remove_pattern __P((SHELL_VAR *, char *, int, char *, int));
+#endif
+static char *parameter_brace_remove_pattern __P((char *, char *, char *, int, int));
+
+static char *process_substitute __P((char *, int));
+
+static char *read_comsub __P((int, int, int *));
+
+#ifdef ARRAY_VARS
+static arrayind_t array_length_reference __P((char *));
+#endif
+
+static int valid_brace_expansion_word __P((char *, int));
+static int chk_atstar __P((char *, int, int *, int *));
+static int chk_arithsub __P((const char *, int));
+
+static WORD_DESC *parameter_brace_expand_word __P((char *, int, int, int));
+static WORD_DESC *parameter_brace_expand_indir __P((char *, int, int, int *, int *));
+static WORD_DESC *parameter_brace_expand_rhs __P((char *, char *, int, int, int *, int *));
+static void parameter_brace_expand_error __P((char *, char *));
+
+static int valid_length_expression __P((char *));
+static intmax_t parameter_brace_expand_length __P((char *));
+
+static char *skiparith __P((char *, int));
+static int verify_substring_values __P((SHELL_VAR *, char *, char *, int, intmax_t *, intmax_t *));
+static int get_var_and_type __P((char *, char *, int, SHELL_VAR **, char **));
+static char *mb_substring __P((char *, int, int));
+static char *parameter_brace_substring __P((char *, char *, char *, int));
+
+static char *pos_params_pat_subst __P((char *, char *, char *, int));
+
+static char *parameter_brace_patsub __P((char *, char *, char *, int));
+
+static char *pos_params_casemod __P((char *, char *, int, int));
+static char *parameter_brace_casemod __P((char *, char *, int, char *, int));
+
+static WORD_DESC *parameter_brace_expand __P((char *, int *, int, int, int *, int *));
+static WORD_DESC *param_expand __P((char *, int *, int, int *, int *, int *, int *, int));
+
+static WORD_LIST *expand_word_internal __P((WORD_DESC *, int, int, int *, int *));
+
+static WORD_LIST *word_list_split __P((WORD_LIST *));
+
+static void exp_jump_to_top_level __P((int));
+
+static WORD_LIST *separate_out_assignments __P((WORD_LIST *));
+static WORD_LIST *glob_expand_word_list __P((WORD_LIST *, int));
+#ifdef BRACE_EXPANSION
+static WORD_LIST *brace_expand_word_list __P((WORD_LIST *, int));
+#endif
+#if defined (ARRAY_VARS)
+static int make_internal_declare __P((char *, char *));
+#endif
+static WORD_LIST *shell_expand_word_list __P((WORD_LIST *, int));
+static WORD_LIST *expand_word_list_internal __P((WORD_LIST *, int));
+
+/* **************************************************************** */
+/* */
+/* Utility Functions */
+/* */
+/* **************************************************************** */
+
+#if defined (DEBUG)
+void
+dump_word_flags (flags)
+ int flags;
+{
+ int f;
+
+ f = flags;
+ fprintf (stderr, "%d -> ", f);
+ if (f & W_ASSIGNASSOC)
+ {
+ f &= ~W_ASSIGNASSOC;
+ fprintf (stderr, "W_ASSIGNASSOC%s", f ? "|" : "");
+ }
+ if (f & W_HASCTLESC)
+ {
+ f &= ~W_HASCTLESC;
+ fprintf (stderr, "W_HASCTLESC%s", f ? "|" : "");
+ }
+ if (f & W_NOPROCSUB)
+ {
+ f &= ~W_NOPROCSUB;
+ fprintf (stderr, "W_NOPROCSUB%s", f ? "|" : "");
+ }
+ if (f & W_DQUOTE)
+ {
+ f &= ~W_DQUOTE;
+ fprintf (stderr, "W_DQUOTE%s", f ? "|" : "");
+ }
+ if (f & W_HASQUOTEDNULL)
+ {
+ f &= ~W_HASQUOTEDNULL;
+ fprintf (stderr, "W_HASQUOTEDNULL%s", f ? "|" : "");
+ }
+ if (f & W_ASSIGNARG)
+ {
+ f &= ~W_ASSIGNARG;
+ fprintf (stderr, "W_ASSIGNARG%s", f ? "|" : "");
+ }
+ if (f & W_ASSNBLTIN)
+ {
+ f &= ~W_ASSNBLTIN;
+ fprintf (stderr, "W_ASSNBLTIN%s", f ? "|" : "");
+ }
+ if (f & W_COMPASSIGN)
+ {
+ f &= ~W_COMPASSIGN;
+ fprintf (stderr, "W_COMPASSIGN%s", f ? "|" : "");
+ }
+ if (f & W_NOEXPAND)
+ {
+ f &= ~W_NOEXPAND;
+ fprintf (stderr, "W_NOEXPAND%s", f ? "|" : "");
+ }
+ if (f & W_ITILDE)
+ {
+ f &= ~W_ITILDE;
+ fprintf (stderr, "W_ITILDE%s", f ? "|" : "");
+ }
+ if (f & W_NOTILDE)
+ {
+ f &= ~W_NOTILDE;
+ fprintf (stderr, "W_NOTILDE%s", f ? "|" : "");
+ }
+ if (f & W_ASSIGNRHS)
+ {
+ f &= ~W_ASSIGNRHS;
+ fprintf (stderr, "W_ASSIGNRHS%s", f ? "|" : "");
+ }
+ if (f & W_NOCOMSUB)
+ {
+ f &= ~W_NOCOMSUB;
+ fprintf (stderr, "W_NOCOMSUB%s", f ? "|" : "");
+ }
+ if (f & W_DOLLARSTAR)
+ {
+ f &= ~W_DOLLARSTAR;
+ fprintf (stderr, "W_DOLLARSTAR%s", f ? "|" : "");
+ }
+ if (f & W_DOLLARAT)
+ {
+ f &= ~W_DOLLARAT;
+ fprintf (stderr, "W_DOLLARAT%s", f ? "|" : "");
+ }
+ if (f & W_TILDEEXP)
+ {
+ f &= ~W_TILDEEXP;
+ fprintf (stderr, "W_TILDEEXP%s", f ? "|" : "");
+ }
+ if (f & W_NOSPLIT2)
+ {
+ f &= ~W_NOSPLIT2;
+ fprintf (stderr, "W_NOSPLIT2%s", f ? "|" : "");
+ }
+ if (f & W_NOGLOB)
+ {
+ f &= ~W_NOGLOB;
+ fprintf (stderr, "W_NOGLOB%s", f ? "|" : "");
+ }
+ if (f & W_NOSPLIT)
+ {
+ f &= ~W_NOSPLIT;
+ fprintf (stderr, "W_NOSPLIT%s", f ? "|" : "");
+ }
+ if (f & W_GLOBEXP)
+ {
+ f &= ~W_GLOBEXP;
+ fprintf (stderr, "W_GLOBEXP%s", f ? "|" : "");
+ }
+ if (f & W_ASSIGNMENT)
+ {
+ f &= ~W_ASSIGNMENT;
+ fprintf (stderr, "W_ASSIGNMENT%s", f ? "|" : "");
+ }
+ if (f & W_QUOTED)
+ {
+ f &= ~W_QUOTED;
+ fprintf (stderr, "W_QUOTED%s", f ? "|" : "");
+ }
+ if (f & W_HASDOLLAR)
+ {
+ f &= ~W_HASDOLLAR;
+ fprintf (stderr, "W_HASDOLLAR%s", f ? "|" : "");
+ }
+ fprintf (stderr, "\n");
+ fflush (stderr);
+}
+#endif
+
+#ifdef INCLUDE_UNUSED
+static char *
+quoted_substring (string, start, end)
+ char *string;
+ int start, end;
+{
+ register int len, l;
+ register char *result, *s, *r;
+
+ len = end - start;
+
+ /* Move to string[start], skipping quoted characters. */
+ for (s = string, l = 0; *s && l < start; )
+ {
+ if (*s == CTLESC)
+ {
+ s++;
+ continue;
+ }
+ l++;
+ if (*s == 0)
+ break;
+ }
+
+ r = result = (char *)xmalloc (2*len + 1); /* save room for quotes */
+
+ /* Copy LEN characters, including quote characters. */
+ s = string + l;
+ for (l = 0; l < len; s++)
+ {
+ if (*s == CTLESC)
+ *r++ = *s++;
+ *r++ = *s;
+ l++;
+ if (*s == 0)
+ break;
+ }
+ *r = '\0';
+ return result;
+}
+#endif
+
+#ifdef INCLUDE_UNUSED
+/* Return the length of S, skipping over quoted characters */
+static int
+quoted_strlen (s)
+ char *s;
+{
+ register char *p;
+ int i;
+
+ i = 0;
+ for (p = s; *p; p++)
+ {
+ if (*p == CTLESC)
+ {
+ p++;
+ if (*p == 0)
+ return (i + 1);
+ }
+ i++;
+ }
+
+ return i;
+}
+#endif
+
+/* Find the first occurrence of character C in string S, obeying shell
+ quoting rules. If (FLAGS & ST_BACKSL) is non-zero, backslash-escaped
+ characters are skipped. If (FLAGS & ST_CTLESC) is non-zero, characters
+ escaped with CTLESC are skipped. */
+static char *
+quoted_strchr (s, c, flags)
+ char *s;
+ int c, flags;
+{
+ register char *p;
+
+ for (p = s; *p; p++)
+ {
+ if (((flags & ST_BACKSL) && *p == '\\')
+ || ((flags & ST_CTLESC) && *p == CTLESC))
+ {
+ p++;
+ if (*p == '\0')
+ return ((char *)NULL);
+ continue;
+ }
+ else if (*p == c)
+ return p;
+ }
+ return ((char *)NULL);
+}
+
+/* Return 1 if CHARACTER appears in an unquoted portion of
+ STRING. Return 0 otherwise. CHARACTER must be a single-byte character. */
+static int
+unquoted_member (character, string)
+ int character;
+ char *string;
+{
+ size_t slen;
+ int sindex, c;
+ DECLARE_MBSTATE;
+
+ slen = strlen (string);
+ sindex = 0;
+ while (c = string[sindex])
+ {
+ if (c == character)
+ return (1);
+
+ switch (c)
+ {
+ default:
+ ADVANCE_CHAR (string, slen, sindex);
+ break;
+
+ case '\\':
+ sindex++;
+ if (string[sindex])
+ ADVANCE_CHAR (string, slen, sindex);
+ break;
+
+ case '\'':
+ sindex = skip_single_quoted (string, slen, ++sindex);
+ break;
+
+ case '"':
+ sindex = skip_double_quoted (string, slen, ++sindex);
+ break;
+ }
+ }
+ return (0);
+}
+
+/* Return 1 if SUBSTR appears in an unquoted portion of STRING. */
+static int
+unquoted_substring (substr, string)
+ char *substr, *string;
+{
+ size_t slen;
+ int sindex, c, sublen;
+ DECLARE_MBSTATE;
+
+ if (substr == 0 || *substr == '\0')
+ return (0);
+
+ slen = strlen (string);
+ sublen = strlen (substr);
+ for (sindex = 0; c = string[sindex]; )
+ {
+ if (STREQN (string + sindex, substr, sublen))
+ return (1);
+
+ switch (c)
+ {
+ case '\\':
+ sindex++;
+
+ if (string[sindex])
+ ADVANCE_CHAR (string, slen, sindex);
+ break;
+
+ case '\'':
+ sindex = skip_single_quoted (string, slen, ++sindex);
+ break;
+
+ case '"':
+ sindex = skip_double_quoted (string, slen, ++sindex);
+ break;
+
+ default:
+ ADVANCE_CHAR (string, slen, sindex);
+ break;
+ }
+ }
+ return (0);
+}
+
+/* Most of the substitutions must be done in parallel. In order
+ to avoid using tons of unclear goto's, I have some functions
+ for manipulating malloc'ed strings. They all take INDX, a
+ pointer to an integer which is the offset into the string
+ where manipulation is taking place. They also take SIZE, a
+ pointer to an integer which is the current length of the
+ character array for this string. */
+
+/* Append SOURCE to TARGET at INDEX. SIZE is the current amount
+ of space allocated to TARGET. SOURCE can be NULL, in which
+ case nothing happens. Gets rid of SOURCE by freeing it.
+ Returns TARGET in case the location has changed. */
+INLINE char *
+sub_append_string (source, target, indx, size)
+ char *source, *target;
+ int *indx, *size;
+{
+ if (source)
+ {
+ int srclen, n;
+
+ srclen = STRLEN (source);
+ if (srclen >= (int)(*size - *indx))
+ {
+ n = srclen + *indx;
+ n = (n + DEFAULT_ARRAY_SIZE) - (n % DEFAULT_ARRAY_SIZE);
+ target = (char *)xrealloc (target, (*size = n));
+ }
+
+ FASTCOPY (source, target + *indx, srclen);
+ *indx += srclen;
+ target[*indx] = '\0';
+
+ free (source);
+ }
+ return (target);
+}
+
+#if 0
+/* UNUSED */
+/* Append the textual representation of NUMBER to TARGET.
+ INDX and SIZE are as in SUB_APPEND_STRING. */
+char *
+sub_append_number (number, target, indx, size)
+ intmax_t number;
+ int *indx, *size;
+ char *target;
+{
+ char *temp;
+
+ temp = itos (number);
+ return (sub_append_string (temp, target, indx, size));
+}
+#endif
+
+/* Extract a substring from STRING, starting at SINDEX and ending with
+ one of the characters in CHARLIST. Don't make the ending character
+ part of the string. Leave SINDEX pointing at the ending character.
+ Understand about backslashes in the string. If (flags & SX_VARNAME)
+ is non-zero, and array variables have been compiled into the shell,
+ everything between a `[' and a corresponding `]' is skipped over.
+ If (flags & SX_NOALLOC) is non-zero, don't return the substring, just
+ update SINDEX. If (flags & SX_REQMATCH) is non-zero, the string must
+ contain a closing character from CHARLIST. */
+static char *
+string_extract (string, sindex, charlist, flags)
+ char *string;
+ int *sindex;
+ char *charlist;
+ int flags;
+{
+ register int c, i;
+ int found;
+ size_t slen;
+ char *temp;
+ DECLARE_MBSTATE;
+
+ slen = (MB_CUR_MAX > 1) ? strlen (string + *sindex) + *sindex : 0;
+ i = *sindex;
+ found = 0;
+ while (c = string[i])
+ {
+ if (c == '\\')
+ {
+ if (string[i + 1])
+ i++;
+ else
+ break;
+ }
+#if defined (ARRAY_VARS)
+ else if ((flags & SX_VARNAME) && c == '[')
+ {
+ int ni;
+ /* If this is an array subscript, skip over it and continue. */
+ ni = skipsubscript (string, i, 0);
+ if (string[ni] == ']')
+ i = ni;
+ }
+#endif
+ else if (MEMBER (c, charlist))
+ {
+ found = 1;
+ break;
+ }
+
+ ADVANCE_CHAR (string, slen, i);
+ }
+
+ /* If we had to have a matching delimiter and didn't find one, return an
+ error and let the caller deal with it. */
+ if ((flags & SX_REQMATCH) && found == 0)
+ {
+ *sindex = i;
+ return (&extract_string_error);
+ }
+
+ temp = (flags & SX_NOALLOC) ? (char *)NULL : substring (string, *sindex, i);
+ *sindex = i;
+
+ return (temp);
+}
+
+/* Extract the contents of STRING as if it is enclosed in double quotes.
+ SINDEX, when passed in, is the offset of the character immediately
+ following the opening double quote; on exit, SINDEX is left pointing after
+ the closing double quote. If STRIPDQ is non-zero, unquoted double
+ quotes are stripped and the string is terminated by a null byte.
+ Backslashes between the embedded double quotes are processed. If STRIPDQ
+ is zero, an unquoted `"' terminates the string. */
+static char *
+string_extract_double_quoted (string, sindex, stripdq)
+ char *string;
+ int *sindex, stripdq;
+{
+ size_t slen;
+ char *send;
+ int j, i, t;
+ unsigned char c;
+ char *temp, *ret; /* The new string we return. */
+ int pass_next, backquote, si; /* State variables for the machine. */
+ int dquote;
+ DECLARE_MBSTATE;
+
+ slen = strlen (string + *sindex) + *sindex;
+ send = string + slen;
+
+ pass_next = backquote = dquote = 0;
+ temp = (char *)xmalloc (1 + slen - *sindex);
+
+ j = 0;
+ i = *sindex;
+ while (c = string[i])
+ {
+ /* Process a character that was quoted by a backslash. */
+ if (pass_next)
+ {
+ /* Posix.2 sez:
+
+ ``The backslash shall retain its special meaning as an escape
+ character only when followed by one of the characters:
+ $ ` " \ <newline>''.
+
+ If STRIPDQ is zero, we handle the double quotes here and let
+ expand_word_internal handle the rest. If STRIPDQ is non-zero,
+ we have already been through one round of backslash stripping,
+ and want to strip these backslashes only if DQUOTE is non-zero,
+ indicating that we are inside an embedded double-quoted string. */
+
+ /* If we are in an embedded quoted string, then don't strip
+ backslashes before characters for which the backslash
+ retains its special meaning, but remove backslashes in
+ front of other characters. If we are not in an
+ embedded quoted string, don't strip backslashes at all.
+ This mess is necessary because the string was already
+ surrounded by double quotes (and sh has some really weird
+ quoting rules).
+ The returned string will be run through expansion as if
+ it were double-quoted. */
+ if ((stripdq == 0 && c != '"') ||
+ (stripdq && ((dquote && (sh_syntaxtab[c] & CBSDQUOTE)) || dquote == 0)))
+ temp[j++] = '\\';
+ pass_next = 0;
+
+add_one_character:
+ COPY_CHAR_I (temp, j, string, send, i);
+ continue;
+ }
+
+ /* A backslash protects the next character. The code just above
+ handles preserving the backslash in front of any character but
+ a double quote. */
+ if (c == '\\')
+ {
+ pass_next++;
+ i++;
+ continue;
+ }
+
+ /* Inside backquotes, ``the portion of the quoted string from the
+ initial backquote and the characters up to the next backquote
+ that is not preceded by a backslash, having escape characters
+ removed, defines that command''. */
+ if (backquote)
+ {
+ if (c == '`')
+ backquote = 0;
+ temp[j++] = c;
+ i++;
+ continue;
+ }
+
+ if (c == '`')
+ {
+ temp[j++] = c;
+ backquote++;
+ i++;
+ continue;
+ }
+
+ /* Pass everything between `$(' and the matching `)' or a quoted
+ ${ ... } pair through according to the Posix.2 specification. */
+ if (c == '$' && ((string[i + 1] == LPAREN) || (string[i + 1] == LBRACE)))
+ {
+ int free_ret = 1;
+
+ si = i + 2;
+ if (string[i + 1] == LPAREN)
+ ret = extract_command_subst (string, &si, 0);
+ else
+ ret = extract_dollar_brace_string (string, &si, 1, 0);
+
+ temp[j++] = '$';
+ temp[j++] = string[i + 1];
+
+ /* Just paranoia; ret will not be 0 unless no_longjmp_on_fatal_error
+ is set. */
+ if (ret == 0 && no_longjmp_on_fatal_error)
+ {
+ free_ret = 0;
+ ret = string + i + 2;
+ }
+
+ for (t = 0; ret[t]; t++, j++)
+ temp[j] = ret[t];
+ temp[j] = string[si];
+
+ if (string[si])
+ {
+ j++;
+ i = si + 1;
+ }
+ else
+ i = si;
+
+ if (free_ret)
+ free (ret);
+ continue;
+ }
+
+ /* Add any character but a double quote to the quoted string we're
+ accumulating. */
+ if (c != '"')
+ goto add_one_character;
+
+ /* c == '"' */
+ if (stripdq)
+ {
+ dquote ^= 1;
+ i++;
+ continue;
+ }
+
+ break;
+ }
+ temp[j] = '\0';
+
+ /* Point to after the closing quote. */
+ if (c)
+ i++;
+ *sindex = i;
+
+ return (temp);
+}
+
+/* This should really be another option to string_extract_double_quoted. */
+static int
+skip_double_quoted (string, slen, sind)
+ char *string;
+ size_t slen;
+ int sind;
+{
+ int c, i;
+ char *ret;
+ int pass_next, backquote, si;
+ DECLARE_MBSTATE;
+
+ pass_next = backquote = 0;
+ i = sind;
+ while (c = string[i])
+ {
+ if (pass_next)
+ {
+ pass_next = 0;
+ ADVANCE_CHAR (string, slen, i);
+ continue;
+ }
+ else if (c == '\\')
+ {
+ pass_next++;
+ i++;
+ continue;
+ }
+ else if (backquote)
+ {
+ if (c == '`')
+ backquote = 0;
+ ADVANCE_CHAR (string, slen, i);
+ continue;
+ }
+ else if (c == '`')
+ {
+ backquote++;
+ i++;
+ continue;
+ }
+ else if (c == '$' && ((string[i + 1] == LPAREN) || (string[i + 1] == LBRACE)))
+ {
+ si = i + 2;
+ if (string[i + 1] == LPAREN)
+ ret = extract_command_subst (string, &si, SX_NOALLOC);
+ else
+ ret = extract_dollar_brace_string (string, &si, 1, SX_NOALLOC);
+
+ i = si + 1;
+ continue;
+ }
+ else if (c != '"')
+ {
+ ADVANCE_CHAR (string, slen, i);
+ continue;
+ }
+ else
+ break;
+ }
+
+ if (c)
+ i++;
+
+ return (i);
+}
+
+/* Extract the contents of STRING as if it is enclosed in single quotes.
+ SINDEX, when passed in, is the offset of the character immediately
+ following the opening single quote; on exit, SINDEX is left pointing after
+ the closing single quote. */
+static inline char *
+string_extract_single_quoted (string, sindex)
+ char *string;
+ int *sindex;
+{
+ register int i;
+ size_t slen;
+ char *t;
+ DECLARE_MBSTATE;
+
+ /* Don't need slen for ADVANCE_CHAR unless multibyte chars possible. */
+ slen = (MB_CUR_MAX > 1) ? strlen (string + *sindex) + *sindex : 0;
+ i = *sindex;
+ while (string[i] && string[i] != '\'')
+ ADVANCE_CHAR (string, slen, i);
+
+ t = substring (string, *sindex, i);
+
+ if (string[i])
+ i++;
+ *sindex = i;
+
+ return (t);
+}
+
+static inline int
+skip_single_quoted (string, slen, sind)
+ const char *string;
+ size_t slen;
+ int sind;
+{
+ register int c;
+ DECLARE_MBSTATE;
+
+ c = sind;
+ while (string[c] && string[c] != '\'')
+ ADVANCE_CHAR (string, slen, c);
+
+ if (string[c])
+ c++;
+ return c;
+}
+
+/* Just like string_extract, but doesn't hack backslashes or any of
+ that other stuff. Obeys CTLESC quoting. Used to do splitting on $IFS. */
+static char *
+string_extract_verbatim (string, slen, sindex, charlist, flags)
+ char *string;
+ size_t slen;
+ int *sindex;
+ char *charlist;
+ int flags;
+{
+ register int i;
+#if defined (HANDLE_MULTIBYTE)
+ size_t clen;
+ wchar_t *wcharlist;
+#endif
+ int c;
+ char *temp;
+ DECLARE_MBSTATE;
+
+ if (charlist[0] == '\'' && charlist[1] == '\0')
+ {
+ temp = string_extract_single_quoted (string, sindex);
+ --*sindex; /* leave *sindex at separator character */
+ return temp;
+ }
+
+ i = *sindex;
+#if 0
+ /* See how the MBLEN and ADVANCE_CHAR macros work to understand why we need
+ this only if MB_CUR_MAX > 1. */
+ slen = (MB_CUR_MAX > 1) ? strlen (string + *sindex) + *sindex : 1;
+#endif
+#if defined (HANDLE_MULTIBYTE)
+ clen = strlen (charlist);
+ wcharlist = 0;
+#endif
+ while (c = string[i])
+ {
+#if defined (HANDLE_MULTIBYTE)
+ size_t mblength;
+#endif
+ if ((flags & SX_NOCTLESC) == 0 && c == CTLESC)
+ {
+ i += 2;
+ continue;
+ }
+ /* Even if flags contains SX_NOCTLESC, we let CTLESC quoting CTLNUL
+ through, to protect the CTLNULs from later calls to
+ remove_quoted_nulls. */
+ else if ((flags & SX_NOESCCTLNUL) == 0 && c == CTLESC && string[i+1] == CTLNUL)
+ {
+ i += 2;
+ continue;
+ }
+
+#if defined (HANDLE_MULTIBYTE)
+ mblength = MBLEN (string + i, slen - i);
+ if (mblength > 1)
+ {
+ wchar_t wc;
+ mblength = mbtowc (&wc, string + i, slen - i);
+ if (MB_INVALIDCH (mblength))
+ {
+ if (MEMBER (c, charlist))
+ break;
+ }
+ else
+ {
+ if (wcharlist == 0)
+ {
+ size_t len;
+ len = mbstowcs (wcharlist, charlist, 0);
+ if (len == -1)
+ len = 0;
+ wcharlist = (wchar_t *)xmalloc (sizeof (wchar_t) * (len + 1));
+ mbstowcs (wcharlist, charlist, len + 1);
+ }
+
+ if (wcschr (wcharlist, wc))
+ break;
+ }
+ }
+ else
+#endif
+ if (MEMBER (c, charlist))
+ break;
+
+ ADVANCE_CHAR (string, slen, i);
+ }
+
+#if defined (HANDLE_MULTIBYTE)
+ FREE (wcharlist);
+#endif
+
+ temp = substring (string, *sindex, i);
+ *sindex = i;
+
+ return (temp);
+}
+
+/* Extract the $( construct in STRING, and return a new string.
+ Start extracting at (SINDEX) as if we had just seen "$(".
+ Make (SINDEX) get the position of the matching ")". )
+ XFLAGS is additional flags to pass to other extraction functions. */
+char *
+extract_command_subst (string, sindex, xflags)
+ char *string;
+ int *sindex;
+ int xflags;
+{
+ if (string[*sindex] == LPAREN)
+ return (extract_delimited_string (string, sindex, "$(", "(", ")", xflags|SX_COMMAND)); /*)*/
+ else
+ {
+ xflags |= (no_longjmp_on_fatal_error ? SX_NOLONGJMP : 0);
+ return (xparse_dolparen (string, string+*sindex, sindex, xflags));
+ }
+}
+
+/* Extract the $[ construct in STRING, and return a new string. (])
+ Start extracting at (SINDEX) as if we had just seen "$[".
+ Make (SINDEX) get the position of the matching "]". */
+char *
+extract_arithmetic_subst (string, sindex)
+ char *string;
+ int *sindex;
+{
+ return (extract_delimited_string (string, sindex, "$[", "[", "]", 0)); /*]*/
+}
+
+#if defined (PROCESS_SUBSTITUTION)
+/* Extract the <( or >( construct in STRING, and return a new string.
+ Start extracting at (SINDEX) as if we had just seen "<(".
+ Make (SINDEX) get the position of the matching ")". */ /*))*/
+char *
+extract_process_subst (string, starter, sindex)
+ char *string;
+ char *starter;
+ int *sindex;
+{
+ return (extract_delimited_string (string, sindex, starter, "(", ")", 0));
+}
+#endif /* PROCESS_SUBSTITUTION */
+
+#if defined (ARRAY_VARS)
+/* This can be fooled by unquoted right parens in the passed string. If
+ each caller verifies that the last character in STRING is a right paren,
+ we don't even need to call extract_delimited_string. */
+char *
+extract_array_assignment_list (string, sindex)
+ char *string;
+ int *sindex;
+{
+ int slen;
+ char *ret;
+
+ slen = strlen (string); /* ( */
+ if (string[slen - 1] == ')')
+ {
+ ret = substring (string, *sindex, slen - 1);
+ *sindex = slen - 1;
+ return ret;
+ }
+ return 0;
+}
+#endif
+
+/* Extract and create a new string from the contents of STRING, a
+ character string delimited with OPENER and CLOSER. SINDEX is
+ the address of an int describing the current offset in STRING;
+ it should point to just after the first OPENER found. On exit,
+ SINDEX gets the position of the last character of the matching CLOSER.
+ If OPENER is more than a single character, ALT_OPENER, if non-null,
+ contains a character string that can also match CLOSER and thus
+ needs to be skipped. */
+static char *
+extract_delimited_string (string, sindex, opener, alt_opener, closer, flags)
+ char *string;
+ int *sindex;
+ char *opener, *alt_opener, *closer;
+ int flags;
+{
+ int i, c, si;
+ size_t slen;
+ char *t, *result;
+ int pass_character, nesting_level, in_comment;
+ int len_closer, len_opener, len_alt_opener;
+ DECLARE_MBSTATE;
+
+ slen = strlen (string + *sindex) + *sindex;
+ len_opener = STRLEN (opener);
+ len_alt_opener = STRLEN (alt_opener);
+ len_closer = STRLEN (closer);
+
+ pass_character = in_comment = 0;
+
+ nesting_level = 1;
+ i = *sindex;
+
+ while (nesting_level)
+ {
+ c = string[i];
+
+ if (c == 0)
+ break;
+
+ if (in_comment)
+ {
+ if (c == '\n')
+ in_comment = 0;
+ ADVANCE_CHAR (string, slen, i);
+ continue;
+ }
+
+ if (pass_character) /* previous char was backslash */
+ {
+ pass_character = 0;
+ ADVANCE_CHAR (string, slen, i);
+ continue;
+ }
+
+ /* Not exactly right yet; should handle shell metacharacters and
+ multibyte characters, too. See COMMENT_BEGIN define in parse.y */
+ if ((flags & SX_COMMAND) && c == '#' && (i == 0 || string[i - 1] == '\n' || shellblank (string[i - 1])))
+ {
+ in_comment = 1;
+ ADVANCE_CHAR (string, slen, i);
+ continue;
+ }
+
+ if (c == CTLESC || c == '\\')
+ {
+ pass_character++;
+ i++;
+ continue;
+ }
+
+#if 0
+ /* Process a nested command substitution, but only if we're parsing a
+ command substitution. XXX - for bash-4.2 */
+ if ((flags & SX_COMMAND) && string[i] == '$' && string[i+1] == LPAREN)
+ {
+ si = i + 2;
+ t = extract_command_subst (string, &si, flags);
+ i = si + 1;
+ continue;
+ }
+#endif
+
+ /* Process a nested OPENER. */
+ if (STREQN (string + i, opener, len_opener))
+ {
+ si = i + len_opener;
+ t = extract_delimited_string (string, &si, opener, alt_opener, closer, flags|SX_NOALLOC);
+ i = si + 1;
+ continue;
+ }
+
+ /* Process a nested ALT_OPENER */
+ if (len_alt_opener && STREQN (string + i, alt_opener, len_alt_opener))
+ {
+ si = i + len_alt_opener;
+ t = extract_delimited_string (string, &si, alt_opener, alt_opener, closer, flags|SX_NOALLOC);
+ i = si + 1;
+ continue;
+ }
+
+ /* If the current substring terminates the delimited string, decrement
+ the nesting level. */
+ if (STREQN (string + i, closer, len_closer))
+ {
+ i += len_closer - 1; /* move to last byte of the closer */
+ nesting_level--;
+ if (nesting_level == 0)
+ break;
+ }
+
+ /* Pass old-style command substitution through verbatim. */
+ if (c == '`')
+ {
+ si = i + 1;
+ t = string_extract (string, &si, "`", flags|SX_NOALLOC);
+ i = si + 1;
+ continue;
+ }
+
+ /* Pass single-quoted and double-quoted strings through verbatim. */
+ if (c == '\'' || c == '"')
+ {
+ si = i + 1;
+ i = (c == '\'') ? skip_single_quoted (string, slen, si)
+ : skip_double_quoted (string, slen, si);
+ continue;
+ }
+
+ /* move past this character, which was not special. */
+ ADVANCE_CHAR (string, slen, i);
+ }
+
+ if (c == 0 && nesting_level)
+ {
+ if (no_longjmp_on_fatal_error == 0)
+ {
+ report_error (_("bad substitution: no closing `%s' in %s"), closer, string);
+ last_command_exit_value = EXECUTION_FAILURE;
+ exp_jump_to_top_level (DISCARD);
+ }
+ else
+ {
+ *sindex = i;
+ return (char *)NULL;
+ }
+ }
+
+ si = i - *sindex - len_closer + 1;
+ if (flags & SX_NOALLOC)
+ result = (char *)NULL;
+ else
+ {
+ result = (char *)xmalloc (1 + si);
+ strncpy (result, string + *sindex, si);
+ result[si] = '\0';
+ }
+ *sindex = i;
+
+ return (result);
+}
+
+/* Extract a parameter expansion expression within ${ and } from STRING.
+ Obey the Posix.2 rules for finding the ending `}': count braces while
+ skipping over enclosed quoted strings and command substitutions.
+ SINDEX is the address of an int describing the current offset in STRING;
+ it should point to just after the first `{' found. On exit, SINDEX
+ gets the position of the matching `}'. QUOTED is non-zero if this
+ occurs inside double quotes. */
+/* XXX -- this is very similar to extract_delimited_string -- XXX */
+static char *
+extract_dollar_brace_string (string, sindex, quoted, flags)
+ char *string;
+ int *sindex, quoted, flags;
+{
+ register int i, c;
+ size_t slen;
+ int pass_character, nesting_level, si;
+ char *result, *t;
+ DECLARE_MBSTATE;
+
+ pass_character = 0;
+ nesting_level = 1;
+ slen = strlen (string + *sindex) + *sindex;
+
+ i = *sindex;
+ while (c = string[i])
+ {
+ if (pass_character)
+ {
+ pass_character = 0;
+ ADVANCE_CHAR (string, slen, i);
+ continue;
+ }
+
+ /* CTLESCs and backslashes quote the next character. */
+ if (c == CTLESC || c == '\\')
+ {
+ pass_character++;
+ i++;
+ continue;
+ }
+
+ if (string[i] == '$' && string[i+1] == LBRACE)
+ {
+ nesting_level++;
+ i += 2;
+ continue;
+ }
+
+ if (c == RBRACE)
+ {
+ nesting_level--;
+ if (nesting_level == 0)
+ break;
+ i++;
+ continue;
+ }
+
+ /* Pass the contents of old-style command substitutions through
+ verbatim. */
+ if (c == '`')
+ {
+ si = i + 1;
+ t = string_extract (string, &si, "`", flags|SX_NOALLOC);
+ i = si + 1;
+ continue;
+ }
+
+ /* Pass the contents of new-style command substitutions and
+ arithmetic substitutions through verbatim. */
+ if (string[i] == '$' && string[i+1] == LPAREN)
+ {
+ si = i + 2;
+ t = extract_command_subst (string, &si, flags|SX_NOALLOC);
+ i = si + 1;
+ continue;
+ }
+
+ /* Pass the contents of single-quoted and double-quoted strings
+ through verbatim. */
+ if (c == '\'' || c == '"')
+ {
+ si = i + 1;
+ i = (c == '\'') ? skip_single_quoted (string, slen, si)
+ : skip_double_quoted (string, slen, si);
+ /* skip_XXX_quoted leaves index one past close quote */
+ continue;
+ }
+
+ /* move past this character, which was not special. */
+ ADVANCE_CHAR (string, slen, i);
+ }
+
+ if (c == 0 && nesting_level)
+ {
+ if (no_longjmp_on_fatal_error == 0)
+ { /* { */
+ report_error (_("bad substitution: no closing `%s' in %s"), "}", string);
+ last_command_exit_value = EXECUTION_FAILURE;
+ exp_jump_to_top_level (DISCARD);
+ }
+ else
+ {
+ *sindex = i;
+ return ((char *)NULL);
+ }
+ }
+
+ result = (flags & SX_NOALLOC) ? (char *)NULL : substring (string, *sindex, i);
+ *sindex = i;
+
+ return (result);
+}
+
+/* Remove backslashes which are quoting backquotes from STRING. Modifies
+ STRING, and returns a pointer to it. */
+char *
+de_backslash (string)
+ char *string;
+{
+ register size_t slen;
+ register int i, j, prev_i;
+ DECLARE_MBSTATE;
+
+ slen = strlen (string);
+ i = j = 0;
+
+ /* Loop copying string[i] to string[j], i >= j. */
+ while (i < slen)
+ {
+ if (string[i] == '\\' && (string[i + 1] == '`' || string[i + 1] == '\\' ||
+ string[i + 1] == '$'))
+ i++;
+ prev_i = i;
+ ADVANCE_CHAR (string, slen, i);
+ if (j < prev_i)
+ do string[j++] = string[prev_i++]; while (prev_i < i);
+ else
+ j = i;
+ }
+ string[j] = '\0';
+
+ return (string);
+}
+
+#if 0
+/*UNUSED*/
+/* Replace instances of \! in a string with !. */
+void
+unquote_bang (string)
+ char *string;
+{
+ register int i, j;
+ register char *temp;
+
+ temp = (char *)xmalloc (1 + strlen (string));
+
+ for (i = 0, j = 0; (temp[j] = string[i]); i++, j++)
+ {
+ if (string[i] == '\\' && string[i + 1] == '!')
+ {
+ temp[j] = '!';
+ i++;
+ }
+ }
+ strcpy (string, temp);
+ free (temp);
+}
+#endif
+
+#define CQ_RETURN(x) do { no_longjmp_on_fatal_error = 0; return (x); } while (0)
+
+/* This function assumes s[i] == open; returns with s[ret] == close; used to
+ parse array subscripts. FLAGS & 1 means to not attempt to skip over
+ matched pairs of quotes or backquotes, or skip word expansions; it is
+ intended to be used after expansion has been performed and during final
+ assignment parsing (see arrayfunc.c:assign_compound_array_list()). */
+static int
+skip_matched_pair (string, start, open, close, flags)
+ const char *string;
+ int start, open, close, flags;
+{
+ int i, pass_next, backq, si, c, count;
+ size_t slen;
+ char *temp, *ss;
+ DECLARE_MBSTATE;
+
+ slen = strlen (string + start) + start;
+ no_longjmp_on_fatal_error = 1;
+
+ i = start + 1; /* skip over leading bracket */
+ count = 1;
+ pass_next = backq = 0;
+ ss = (char *)string;
+ while (c = string[i])
+ {
+ if (pass_next)
+ {
+ pass_next = 0;
+ if (c == 0)
+ CQ_RETURN(i);
+ ADVANCE_CHAR (string, slen, i);
+ continue;
+ }
+ else if (c == '\\')
+ {
+ pass_next = 1;
+ i++;
+ continue;
+ }
+ else if (backq)
+ {
+ if (c == '`')
+ backq = 0;
+ ADVANCE_CHAR (string, slen, i);
+ continue;
+ }
+ else if ((flags & 1) == 0 && c == '`')
+ {
+ backq = 1;
+ i++;
+ continue;
+ }
+ else if ((flags & 1) == 0 && c == open)
+ {
+ count++;
+ i++;
+ continue;
+ }
+ else if (c == close)
+ {
+ count--;
+ if (count == 0)
+ break;
+ i++;
+ continue;
+ }
+ else if ((flags & 1) == 0 && (c == '\'' || c == '"'))
+ {
+ i = (c == '\'') ? skip_single_quoted (ss, slen, ++i)
+ : skip_double_quoted (ss, slen, ++i);
+ /* no increment, the skip functions increment past the closing quote. */
+ }
+ else if ((flags&1) == 0 && c == '$' && (string[i+1] == LPAREN || string[i+1] == LBRACE))
+ {
+ si = i + 2;
+ if (string[si] == '\0')
+ CQ_RETURN(si);
+
+ if (string[i+1] == LPAREN)
+ temp = extract_delimited_string (ss, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
+ else
+ temp = extract_dollar_brace_string (ss, &si, 0, SX_NOALLOC);
+ i = si;
+ if (string[i] == '\0') /* don't increment i past EOS in loop */
+ break;
+ i++;
+ continue;
+ }
+ else
+ ADVANCE_CHAR (string, slen, i);
+ }
+
+ CQ_RETURN(i);
+}
+
+#if defined (ARRAY_VARS)
+int
+skipsubscript (string, start, flags)
+ const char *string;
+ int start, flags;
+{
+ return (skip_matched_pair (string, start, '[', ']', flags));
+}
+#endif
+
+/* Skip characters in STRING until we find a character in DELIMS, and return
+ the index of that character. START is the index into string at which we
+ begin. This is similar in spirit to strpbrk, but it returns an index into
+ STRING and takes a starting index. This little piece of code knows quite
+ a lot of shell syntax. It's very similar to skip_double_quoted and other
+ functions of that ilk. */
+int
+skip_to_delim (string, start, delims, flags)
+ char *string;
+ int start;
+ char *delims;
+ int flags;
+{
+ int i, pass_next, backq, si, c, invert, skipquote, skipcmd;
+ size_t slen;
+ char *temp;
+ DECLARE_MBSTATE;
+
+ slen = strlen (string + start) + start;
+ if (flags & SD_NOJMP)
+ no_longjmp_on_fatal_error = 1;
+ invert = (flags & SD_INVERT);
+ skipcmd = (flags & SD_NOSKIPCMD) == 0;
+
+ i = start;
+ pass_next = backq = 0;
+ while (c = string[i])
+ {
+ /* If this is non-zero, we should not let quote characters be delimiters
+ and the current character is a single or double quote. We should not
+ test whether or not it's a delimiter until after we skip single- or
+ double-quoted strings. */
+ skipquote = ((flags & SD_NOQUOTEDELIM) && (c == '\'' || c =='"'));
+ if (pass_next)
+ {
+ pass_next = 0;
+ if (c == 0)
+ CQ_RETURN(i);
+ ADVANCE_CHAR (string, slen, i);
+ continue;
+ }
+ else if (c == '\\')
+ {
+ pass_next = 1;
+ i++;
+ continue;
+ }
+ else if (backq)
+ {
+ if (c == '`')
+ backq = 0;
+ ADVANCE_CHAR (string, slen, i);
+ continue;
+ }
+ else if (c == '`')
+ {
+ backq = 1;
+ i++;
+ continue;
+ }
+ else if (skipquote == 0 && invert == 0 && member (c, delims))
+ break;
+ else if (c == '\'' || c == '"')
+ {
+ i = (c == '\'') ? skip_single_quoted (string, slen, ++i)
+ : skip_double_quoted (string, slen, ++i);
+ /* no increment, the skip functions increment past the closing quote. */
+ }
+ else if (c == '$' && ((skipcmd && string[i+1] == LPAREN) || string[i+1] == LBRACE))
+ {
+ si = i + 2;
+ if (string[si] == '\0')
+ CQ_RETURN(si);
+
+ if (string[i+1] == LPAREN)
+ temp = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
+ else
+ temp = extract_dollar_brace_string (string, &si, 0, SX_NOALLOC);
+ i = si;
+ if (string[i] == '\0') /* don't increment i past EOS in loop */
+ break;
+ i++;
+ continue;
+ }
+#if defined (PROCESS_SUBSTITUTION)
+ else if (skipcmd && (c == '<' || c == '>') && string[i+1] == LPAREN)
+ {
+ si = i + 2;
+ if (string[si] == '\0')
+ CQ_RETURN(si);
+ temp = extract_process_subst (string, (c == '<') ? "<(" : ">(", &si);
+ i = si;
+ if (string[i] == '\0')
+ break;
+ i++;
+ continue;
+ }
+#endif /* PROCESS_SUBSTITUTION */
+ else if ((skipquote || invert) && (member (c, delims) == 0))
+ break;
+ else
+ ADVANCE_CHAR (string, slen, i);
+ }
+
+ CQ_RETURN(i);
+}
+
+#if defined (READLINE)
+/* Return 1 if the portion of STRING ending at EINDEX is quoted (there is
+ an unclosed quoted string), or if the character at EINDEX is quoted
+ by a backslash. NO_LONGJMP_ON_FATAL_ERROR is used to flag that the various
+ single and double-quoted string parsing functions should not return an
+ error if there are unclosed quotes or braces. The characters that this
+ recognizes need to be the same as the contents of
+ rl_completer_quote_characters. */
+
+int
+char_is_quoted (string, eindex)
+ char *string;
+ int eindex;
+{
+ int i, pass_next, c;
+ size_t slen;
+ DECLARE_MBSTATE;
+
+ slen = strlen (string);
+ no_longjmp_on_fatal_error = 1;
+ i = pass_next = 0;
+ while (i <= eindex)
+ {
+ c = string[i];
+
+ if (pass_next)
+ {
+ pass_next = 0;
+ if (i >= eindex) /* XXX was if (i >= eindex - 1) */
+ CQ_RETURN(1);
+ ADVANCE_CHAR (string, slen, i);
+ continue;
+ }
+ else if (c == '\\')
+ {
+ pass_next = 1;
+ i++;
+ continue;
+ }
+ else if (c == '\'' || c == '"')
+ {
+ i = (c == '\'') ? skip_single_quoted (string, slen, ++i)
+ : skip_double_quoted (string, slen, ++i);
+ if (i > eindex)
+ CQ_RETURN(1);
+ /* no increment, the skip_xxx functions go one past end */
+ }
+ else
+ ADVANCE_CHAR (string, slen, i);
+ }
+
+ CQ_RETURN(0);
+}
+
+int
+unclosed_pair (string, eindex, openstr)
+ char *string;
+ int eindex;
+ char *openstr;
+{
+ int i, pass_next, openc, olen;
+ size_t slen;
+ DECLARE_MBSTATE;
+
+ slen = strlen (string);
+ olen = strlen (openstr);
+ i = pass_next = openc = 0;
+ while (i <= eindex)
+ {
+ if (pass_next)
+ {
+ pass_next = 0;
+ if (i >= eindex) /* XXX was if (i >= eindex - 1) */
+ return 0;
+ ADVANCE_CHAR (string, slen, i);
+ continue;
+ }
+ else if (string[i] == '\\')
+ {
+ pass_next = 1;
+ i++;
+ continue;
+ }
+ else if (STREQN (string + i, openstr, olen))
+ {
+ openc = 1 - openc;
+ i += olen;
+ }
+ else if (string[i] == '\'' || string[i] == '"')
+ {
+ i = (string[i] == '\'') ? skip_single_quoted (string, slen, i)
+ : skip_double_quoted (string, slen, i);
+ if (i > eindex)
+ return 0;
+ }
+ else
+ ADVANCE_CHAR (string, slen, i);
+ }
+ return (openc);
+}
+
+/* Split STRING (length SLEN) at DELIMS, and return a WORD_LIST with the
+ individual words. If DELIMS is NULL, the current value of $IFS is used
+ to split the string, and the function follows the shell field splitting
+ rules. SENTINEL is an index to look for. NWP, if non-NULL,
+ gets the number of words in the returned list. CWP, if non-NULL, gets
+ the index of the word containing SENTINEL. Non-whitespace chars in
+ DELIMS delimit separate fields. */
+WORD_LIST *
+split_at_delims (string, slen, delims, sentinel, flags, nwp, cwp)
+ char *string;
+ int slen;
+ char *delims;
+ int sentinel, flags;
+ int *nwp, *cwp;
+{
+ int ts, te, i, nw, cw, ifs_split, dflags;
+ char *token, *d, *d2;
+ WORD_LIST *ret, *tl;
+
+ if (string == 0 || *string == '\0')
+ {
+ if (nwp)
+ *nwp = 0;
+ if (cwp)
+ *cwp = 0;
+ return ((WORD_LIST *)NULL);
+ }
+
+ d = (delims == 0) ? ifs_value : delims;
+ ifs_split = delims == 0;
+
+ /* Make d2 the non-whitespace characters in delims */
+ d2 = 0;
+ if (delims)
+ {
+ size_t slength;
+#if defined (HANDLE_MULTIBYTE)
+ size_t mblength = 1;
+#endif
+ DECLARE_MBSTATE;
+
+ slength = strlen (delims);
+ d2 = (char *)xmalloc (slength + 1);
+ i = ts = 0;
+ while (delims[i])
+ {
+#if defined (HANDLE_MULTIBYTE)
+ mbstate_t state_bak;
+ state_bak = state;
+ mblength = MBRLEN (delims + i, slength, &state);
+ if (MB_INVALIDCH (mblength))
+ state = state_bak;
+ else if (mblength > 1)
+ {
+ memcpy (d2 + ts, delims + i, mblength);
+ ts += mblength;
+ i += mblength;
+ slength -= mblength;
+ continue;
+ }
+#endif
+ if (whitespace (delims[i]) == 0)
+ d2[ts++] = delims[i];
+
+ i++;
+ slength--;
+ }
+ d2[ts] = '\0';
+ }
+
+ ret = (WORD_LIST *)NULL;
+
+ /* Remove sequences of whitespace characters at the start of the string, as
+ long as those characters are delimiters. */
+ for (i = 0; member (string[i], d) && spctabnl (string[i]); i++)
+ ;
+ if (string[i] == '\0')
+ return (ret);
+
+ ts = i;
+ nw = 0;
+ cw = -1;
+ dflags = flags|SD_NOJMP;
+ while (1)
+ {
+ te = skip_to_delim (string, ts, d, dflags);
+
+ /* If we have a non-whitespace delimiter character, use it to make a
+ separate field. This is just about what $IFS splitting does and
+ is closer to the behavior of the shell parser. */
+ if (ts == te && d2 && member (string[ts], d2))
+ {
+ te = ts + 1;
+ /* If we're using IFS splitting, the non-whitespace delimiter char
+ and any additional IFS whitespace delimits a field. */
+ if (ifs_split)
+ while (member (string[te], d) && spctabnl (string[te]))
+ te++;
+ else
+ while (member (string[te], d2))
+ te++;
+ }
+
+ token = substring (string, ts, te);
+
+ ret = add_string_to_list (token, ret);
+ free (token);
+ nw++;
+
+ if (sentinel >= ts && sentinel <= te)
+ cw = nw;
+
+ /* If the cursor is at whitespace just before word start, set the
+ sentinel word to the current word. */
+ if (cwp && cw == -1 && sentinel == ts-1)
+ cw = nw;
+
+ /* If the cursor is at whitespace between two words, make a new, empty
+ word, add it before (well, after, since the list is in reverse order)
+ the word we just added, and set the current word to that one. */
+ if (cwp && cw == -1 && sentinel < ts)
+ {
+ tl = make_word_list (make_word (""), ret->next);
+ ret->next = tl;
+ cw = nw;
+ nw++;
+ }
+
+ if (string[te] == 0)
+ break;
+
+ i = te;
+ while (member (string[i], d) && (ifs_split || spctabnl(string[i])))
+ i++;
+
+ if (string[i])
+ ts = i;
+ else
+ break;
+ }
+
+ /* Special case for SENTINEL at the end of STRING. If we haven't found
+ the word containing SENTINEL yet, and the index we're looking for is at
+ the end of STRING (or past the end of the previously-found token,
+ possible if the end of the line is composed solely of IFS whitespace)
+ add an additional null argument and set the current word pointer to that. */
+ if (cwp && cw == -1 && (sentinel >= slen || sentinel >= te))
+ {
+ if (whitespace (string[sentinel - 1]))
+ {
+ token = "";
+ ret = add_string_to_list (token, ret);
+ nw++;
+ }
+ cw = nw;
+ }
+
+ if (nwp)
+ *nwp = nw;
+ if (cwp)
+ *cwp = cw;
+
+ return (REVERSE_LIST (ret, WORD_LIST *));
+}
+#endif /* READLINE */
+
+#if 0
+/* UNUSED */
+/* Extract the name of the variable to bind to from the assignment string. */
+char *
+assignment_name (string)
+ char *string;
+{
+ int offset;
+ char *temp;
+
+ offset = assignment (string, 0);
+ if (offset == 0)
+ return (char *)NULL;
+ temp = substring (string, 0, offset);
+ return (temp);
+}
+#endif
+
+/* **************************************************************** */
+/* */
+/* Functions to convert strings to WORD_LISTs and vice versa */
+/* */
+/* **************************************************************** */
+
+/* Return a single string of all the words in LIST. SEP is the separator
+ to put between individual elements of LIST in the output string. */
+char *
+string_list_internal (list, sep)
+ WORD_LIST *list;
+ char *sep;
+{
+ register WORD_LIST *t;
+ char *result, *r;
+ int word_len, sep_len, result_size;
+
+ if (list == 0)
+ return ((char *)NULL);
+
+ /* Short-circuit quickly if we don't need to separate anything. */
+ if (list->next == 0)
+ return (savestring (list->word->word));
+
+ /* This is nearly always called with either sep[0] == 0 or sep[1] == 0. */
+ sep_len = STRLEN (sep);
+ result_size = 0;
+
+ for (t = list; t; t = t->next)
+ {
+ if (t != list)
+ result_size += sep_len;
+ result_size += strlen (t->word->word);
+ }
+
+ r = result = (char *)xmalloc (result_size + 1);
+
+ for (t = list; t; t = t->next)
+ {
+ if (t != list && sep_len)
+ {
+ if (sep_len > 1)
+ {
+ FASTCOPY (sep, r, sep_len);
+ r += sep_len;
+ }
+ else
+ *r++ = sep[0];
+ }
+
+ word_len = strlen (t->word->word);
+ FASTCOPY (t->word->word, r, word_len);
+ r += word_len;
+ }
+
+ *r = '\0';
+ return (result);
+}
+
+/* Return a single string of all the words present in LIST, separating
+ each word with a space. */
+char *
+string_list (list)
+ WORD_LIST *list;
+{
+ return (string_list_internal (list, " "));
+}
+
+/* An external interface that can be used by the rest of the shell to
+ obtain a string containing the first character in $IFS. Handles all
+ the multibyte complications. If LENP is non-null, it is set to the
+ length of the returned string. */
+char *
+ifs_firstchar (lenp)
+ int *lenp;
+{
+ char *ret;
+ int len;
+
+ ret = xmalloc (MB_LEN_MAX + 1);
+#if defined (HANDLE_MULTIBYTE)
+ if (ifs_firstc_len == 1)
+ {
+ ret[0] = ifs_firstc[0];
+ ret[1] = '\0';
+ len = ret[0] ? 1 : 0;
+ }
+ else
+ {
+ memcpy (ret, ifs_firstc, ifs_firstc_len);
+ ret[len = ifs_firstc_len] = '\0';
+ }
+#else
+ ret[0] = ifs_firstc;
+ ret[1] = '\0';
+ len = ret[0] ? 0 : 1;
+#endif
+
+ if (lenp)
+ *lenp = len;
+
+ return ret;
+}
+
+/* Return a single string of all the words present in LIST, obeying the
+ quoting rules for "$*", to wit: (P1003.2, draft 11, 3.5.2) "If the
+ expansion [of $*] appears within a double quoted string, it expands
+ to a single field with the value of each parameter separated by the
+ first character of the IFS variable, or by a <space> if IFS is unset." */
+char *
+string_list_dollar_star (list)
+ WORD_LIST *list;
+{
+ char *ret;
+#if defined (HANDLE_MULTIBYTE)
+# if defined (__GNUC__)
+ char sep[MB_CUR_MAX + 1];
+# else
+ char *sep = 0;
+# endif
+#else
+ char sep[2];
+#endif
+
+#if defined (HANDLE_MULTIBYTE)
+# if !defined (__GNUC__)
+ sep = (char *)xmalloc (MB_CUR_MAX + 1);
+# endif /* !__GNUC__ */
+ if (ifs_firstc_len == 1)
+ {
+ sep[0] = ifs_firstc[0];
+ sep[1] = '\0';
+ }
+ else
+ {
+ memcpy (sep, ifs_firstc, ifs_firstc_len);
+ sep[ifs_firstc_len] = '\0';
+ }
+#else
+ sep[0] = ifs_firstc;
+ sep[1] = '\0';
+#endif
+
+ ret = string_list_internal (list, sep);
+#if defined (HANDLE_MULTIBYTE) && !defined (__GNUC__)
+ free (sep);
+#endif
+ return ret;
+}
+
+/* Turn $@ into a string. If (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+ is non-zero, the $@ appears within double quotes, and we should quote
+ the list before converting it into a string. If IFS is unset, and the
+ word is not quoted, we just need to quote CTLESC and CTLNUL characters
+ in the words in the list, because the default value of $IFS is
+ <space><tab><newline>, IFS characters in the words in the list should
+ also be split. If IFS is null, and the word is not quoted, we need
+ to quote the words in the list to preserve the positional parameters
+ exactly. */
+char *
+string_list_dollar_at (list, quoted)
+ WORD_LIST *list;
+ int quoted;
+{
+ char *ifs, *ret;
+#if defined (HANDLE_MULTIBYTE)
+# if defined (__GNUC__)
+ char sep[MB_CUR_MAX + 1];
+# else
+ char *sep = 0;
+# endif /* !__GNUC__ */
+#else
+ char sep[2];
+#endif
+ WORD_LIST *tlist;
+
+ /* XXX this could just be ifs = ifs_value; */
+ ifs = ifs_var ? value_cell (ifs_var) : (char *)0;
+
+#if defined (HANDLE_MULTIBYTE)
+# if !defined (__GNUC__)
+ sep = (char *)xmalloc (MB_CUR_MAX + 1);
+# endif /* !__GNUC__ */
+ if (ifs && *ifs)
+ {
+ if (ifs_firstc_len == 1)
+ {
+ sep[0] = ifs_firstc[0];
+ sep[1] = '\0';
+ }
+ else
+ {
+ memcpy (sep, ifs_firstc, ifs_firstc_len);
+ sep[ifs_firstc_len] = '\0';
+ }
+ }
+ else
+ {
+ sep[0] = ' ';
+ sep[1] = '\0';
+ }
+#else
+ sep[0] = (ifs == 0 || *ifs == 0) ? ' ' : *ifs;
+ sep[1] = '\0';
+#endif
+
+ /* XXX -- why call quote_list if ifs == 0? we can get away without doing
+ it now that quote_escapes quotes spaces */
+#if 0
+ tlist = ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (ifs && *ifs == 0))
+#else
+ tlist = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES|Q_PATQUOTE))
+#endif
+ ? quote_list (list)
+ : list_quote_escapes (list);
+
+ ret = string_list_internal (tlist, sep);
+#if defined (HANDLE_MULTIBYTE) && !defined (__GNUC__)
+ free (sep);
+#endif
+ return ret;
+}
+
+/* Turn the positional paramters into a string, understanding quoting and
+ the various subtleties of using the first character of $IFS as the
+ separator. Calls string_list_dollar_at, string_list_dollar_star, and
+ string_list as appropriate. */
+char *
+string_list_pos_params (pchar, list, quoted)
+ int pchar;
+ WORD_LIST *list;
+ int quoted;
+{
+ char *ret;
+ WORD_LIST *tlist;
+
+ if (pchar == '*' && (quoted & Q_DOUBLE_QUOTES))
+ {
+ tlist = quote_list (list);
+ word_list_remove_quoted_nulls (tlist);
+ ret = string_list_dollar_star (tlist);
+ }
+ else if (pchar == '*' && (quoted & Q_HERE_DOCUMENT))
+ {
+ tlist = quote_list (list);
+ word_list_remove_quoted_nulls (tlist);
+ ret = string_list (tlist);
+ }
+ else if (pchar == '*')
+ {
+ /* Even when unquoted, string_list_dollar_star does the right thing
+ making sure that the first character of $IFS is used as the
+ separator. */
+ ret = string_list_dollar_star (list);
+ }
+ else if (pchar == '@' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+ /* We use string_list_dollar_at, but only if the string is quoted, since
+ that quotes the escapes if it's not, which we don't want. We could
+ use string_list (the old code did), but that doesn't do the right
+ thing if the first character of $IFS is not a space. We use
+ string_list_dollar_star if the string is unquoted so we make sure that
+ the elements of $@ are separated by the first character of $IFS for
+ later splitting. */
+ ret = string_list_dollar_at (list, quoted);
+ else if (pchar == '@')
+ ret = string_list_dollar_star (list);
+ else
+ ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (list) : list);
+
+ return ret;
+}
+
+/* Return the list of words present in STRING. Separate the string into
+ words at any of the characters found in SEPARATORS. If QUOTED is
+ non-zero then word in the list will have its quoted flag set, otherwise
+ the quoted flag is left as make_word () deemed fit.
+
+ This obeys the P1003.2 word splitting semantics. If `separators' is
+ exactly <space><tab><newline>, then the splitting algorithm is that of
+ the Bourne shell, which treats any sequence of characters from `separators'
+ as a delimiter. If IFS is unset, which results in `separators' being set
+ to "", no splitting occurs. If separators has some other value, the
+ following rules are applied (`IFS white space' means zero or more
+ occurrences of <space>, <tab>, or <newline>, as long as those characters
+ are in `separators'):
+
+ 1) IFS white space is ignored at the start and the end of the
+ string.
+ 2) Each occurrence of a character in `separators' that is not
+ IFS white space, along with any adjacent occurrences of
+ IFS white space delimits a field.
+ 3) Any nonzero-length sequence of IFS white space delimits a field.
+ */
+
+/* BEWARE! list_string strips null arguments. Don't call it twice and
+ expect to have "" preserved! */
+
+/* This performs word splitting and quoted null character removal on
+ STRING. */
+#define issep(c) \
+ (((separators)[0]) ? ((separators)[1] ? isifs(c) \
+ : (c) == (separators)[0]) \
+ : 0)
+
+WORD_LIST *
+list_string (string, separators, quoted)
+ register char *string, *separators;
+ int quoted;
+{
+ WORD_LIST *result;
+ WORD_DESC *t;
+ char *current_word, *s;
+ int sindex, sh_style_split, whitesep, xflags;
+ size_t slen;
+
+ if (!string || !*string)
+ return ((WORD_LIST *)NULL);
+
+ sh_style_split = separators && separators[0] == ' ' &&
+ separators[1] == '\t' &&
+ separators[2] == '\n' &&
+ separators[3] == '\0';
+ for (xflags = 0, s = ifs_value; s && *s; s++)
+ {
+ if (*s == CTLESC) xflags |= SX_NOCTLESC;
+ else if (*s == CTLNUL) xflags |= SX_NOESCCTLNUL;
+ }
+
+ slen = 0;
+ /* Remove sequences of whitespace at the beginning of STRING, as
+ long as those characters appear in IFS. Do not do this if
+ STRING is quoted or if there are no separator characters. */
+ if (!quoted || !separators || !*separators)
+ {
+ for (s = string; *s && spctabnl (*s) && issep (*s); s++);
+
+ if (!*s)
+ return ((WORD_LIST *)NULL);
+
+ string = s;
+ }
+
+ /* OK, now STRING points to a word that does not begin with white space.
+ The splitting algorithm is:
+ extract a word, stopping at a separator
+ skip sequences of spc, tab, or nl as long as they are separators
+ This obeys the field splitting rules in Posix.2. */
+ slen = (MB_CUR_MAX > 1) ? strlen (string) : 1;
+ for (result = (WORD_LIST *)NULL, sindex = 0; string[sindex]; )
+ {
+ /* Don't need string length in ADVANCE_CHAR or string_extract_verbatim
+ unless multibyte chars are possible. */
+ current_word = string_extract_verbatim (string, slen, &sindex, separators, xflags);
+ if (current_word == 0)
+ break;
+
+ /* If we have a quoted empty string, add a quoted null argument. We
+ want to preserve the quoted null character iff this is a quoted
+ empty string; otherwise the quoted null characters are removed
+ below. */
+ if (QUOTED_NULL (current_word))
+ {
+ t = alloc_word_desc ();
+ t->word = make_quoted_char ('\0');
+ t->flags |= W_QUOTED|W_HASQUOTEDNULL;
+ result = make_word_list (t, result);
+ }
+ else if (current_word[0] != '\0')
+ {
+ /* If we have something, then add it regardless. However,
+ perform quoted null character removal on the current word. */
+ remove_quoted_nulls (current_word);
+ result = add_string_to_list (current_word, result);
+ result->word->flags &= ~W_HASQUOTEDNULL; /* just to be sure */
+ if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))
+ result->word->flags |= W_QUOTED;
+ }
+
+ /* If we're not doing sequences of separators in the traditional
+ Bourne shell style, then add a quoted null argument. */
+ else if (!sh_style_split && !spctabnl (string[sindex]))
+ {
+ t = alloc_word_desc ();
+ t->word = make_quoted_char ('\0');
+ t->flags |= W_QUOTED|W_HASQUOTEDNULL;
+ result = make_word_list (t, result);
+ }
+
+ free (current_word);
+
+ /* Note whether or not the separator is IFS whitespace, used later. */
+ whitesep = string[sindex] && spctabnl (string[sindex]);
+
+ /* Move past the current separator character. */
+ if (string[sindex])
+ {
+ DECLARE_MBSTATE;
+ ADVANCE_CHAR (string, slen, sindex);
+ }
+
+ /* Now skip sequences of space, tab, or newline characters if they are
+ in the list of separators. */
+ while (string[sindex] && spctabnl (string[sindex]) && issep (string[sindex]))
+ sindex++;
+
+ /* If the first separator was IFS whitespace and the current character
+ is a non-whitespace IFS character, it should be part of the current
+ field delimiter, not a separate delimiter that would result in an
+ empty field. Look at POSIX.2, 3.6.5, (3)(b). */
+ if (string[sindex] && whitesep && issep (string[sindex]) && !spctabnl (string[sindex]))
+ {
+ sindex++;
+ /* An IFS character that is not IFS white space, along with any
+ adjacent IFS white space, shall delimit a field. (SUSv3) */
+ while (string[sindex] && spctabnl (string[sindex]) && isifs (string[sindex]))
+ sindex++;
+ }
+ }
+ return (REVERSE_LIST (result, WORD_LIST *));
+}
+
+/* Parse a single word from STRING, using SEPARATORS to separate fields.
+ ENDPTR is set to the first character after the word. This is used by
+ the `read' builtin. This is never called with SEPARATORS != $IFS;
+ it should be simplified.
+
+ XXX - this function is very similar to list_string; they should be
+ combined - XXX */
+char *
+get_word_from_string (stringp, separators, endptr)
+ char **stringp, *separators, **endptr;
+{
+ register char *s;
+ char *current_word;
+ int sindex, sh_style_split, whitesep, xflags;
+ size_t slen;
+
+ if (!stringp || !*stringp || !**stringp)
+ return ((char *)NULL);
+
+ sh_style_split = separators && separators[0] == ' ' &&
+ separators[1] == '\t' &&
+ separators[2] == '\n' &&
+ separators[3] == '\0';
+ for (xflags = 0, s = ifs_value; s && *s; s++)
+ {
+ if (*s == CTLESC) xflags |= SX_NOCTLESC;
+ if (*s == CTLNUL) xflags |= SX_NOESCCTLNUL;
+ }
+
+ s = *stringp;
+ slen = 0;
+
+ /* Remove sequences of whitespace at the beginning of STRING, as
+ long as those characters appear in IFS. */
+ if (sh_style_split || !separators || !*separators)
+ {
+ for (; *s && spctabnl (*s) && isifs (*s); s++);
+
+ /* If the string is nothing but whitespace, update it and return. */
+ if (!*s)
+ {
+ *stringp = s;
+ if (endptr)
+ *endptr = s;
+ return ((char *)NULL);
+ }
+ }
+
+ /* OK, S points to a word that does not begin with white space.
+ Now extract a word, stopping at a separator, save a pointer to
+ the first character after the word, then skip sequences of spc,
+ tab, or nl as long as they are separators.
+
+ This obeys the field splitting rules in Posix.2. */
+ sindex = 0;
+ /* Don't need string length in ADVANCE_CHAR or string_extract_verbatim
+ unless multibyte chars are possible. */
+ slen = (MB_CUR_MAX > 1) ? strlen (s) : 1;
+ current_word = string_extract_verbatim (s, slen, &sindex, separators, xflags);
+
+ /* Set ENDPTR to the first character after the end of the word. */
+ if (endptr)
+ *endptr = s + sindex;
+
+ /* Note whether or not the separator is IFS whitespace, used later. */
+ whitesep = s[sindex] && spctabnl (s[sindex]);
+
+ /* Move past the current separator character. */
+ if (s[sindex])
+ {
+ DECLARE_MBSTATE;
+ ADVANCE_CHAR (s, slen, sindex);
+ }
+
+ /* Now skip sequences of space, tab, or newline characters if they are
+ in the list of separators. */
+ while (s[sindex] && spctabnl (s[sindex]) && isifs (s[sindex]))
+ sindex++;
+
+ /* If the first separator was IFS whitespace and the current character is
+ a non-whitespace IFS character, it should be part of the current field
+ delimiter, not a separate delimiter that would result in an empty field.
+ Look at POSIX.2, 3.6.5, (3)(b). */
+ if (s[sindex] && whitesep && isifs (s[sindex]) && !spctabnl (s[sindex]))
+ {
+ sindex++;
+ /* An IFS character that is not IFS white space, along with any adjacent
+ IFS white space, shall delimit a field. */
+ while (s[sindex] && spctabnl (s[sindex]) && isifs (s[sindex]))
+ sindex++;
+ }
+
+ /* Update STRING to point to the next field. */
+ *stringp = s + sindex;
+ return (current_word);
+}
+
+/* Remove IFS white space at the end of STRING. Start at the end
+ of the string and walk backwards until the beginning of the string
+ or we find a character that's not IFS white space and not CTLESC.
+ Only let CTLESC escape a white space character if SAW_ESCAPE is
+ non-zero. */
+char *
+strip_trailing_ifs_whitespace (string, separators, saw_escape)
+ char *string, *separators;
+ int saw_escape;
+{
+ char *s;
+
+ s = string + STRLEN (string) - 1;
+ while (s > string && ((spctabnl (*s) && isifs (*s)) ||
+ (saw_escape && *s == CTLESC && spctabnl (s[1]))))
+ s--;
+ *++s = '\0';
+ return string;
+}
+
+#if 0
+/* UNUSED */
+/* Split STRING into words at whitespace. Obeys shell-style quoting with
+ backslashes, single and double quotes. */
+WORD_LIST *
+list_string_with_quotes (string)
+ char *string;
+{
+ WORD_LIST *list;
+ char *token, *s;
+ size_t s_len;
+ int c, i, tokstart, len;
+
+ for (s = string; s && *s && spctabnl (*s); s++)
+ ;
+ if (s == 0 || *s == 0)
+ return ((WORD_LIST *)NULL);
+
+ s_len = strlen (s);
+ tokstart = i = 0;
+ list = (WORD_LIST *)NULL;
+ while (1)
+ {
+ c = s[i];
+ if (c == '\\')
+ {
+ i++;
+ if (s[i])
+ i++;
+ }
+ else if (c == '\'')
+ i = skip_single_quoted (s, s_len, ++i);
+ else if (c == '"')
+ i = skip_double_quoted (s, s_len, ++i);
+ else if (c == 0 || spctabnl (c))
+ {
+ /* We have found the end of a token. Make a word out of it and
+ add it to the word list. */
+ token = substring (s, tokstart, i);
+ list = add_string_to_list (token, list);
+ free (token);
+ while (spctabnl (s[i]))
+ i++;
+ if (s[i])
+ tokstart = i;
+ else
+ break;
+ }
+ else
+ i++; /* normal character */
+ }
+ return (REVERSE_LIST (list, WORD_LIST *));
+}
+#endif
+
+/********************************************************/
+/* */
+/* Functions to perform assignment statements */
+/* */
+/********************************************************/
+
+#if defined (ARRAY_VARS)
+static SHELL_VAR *
+do_compound_assignment (name, value, flags)
+ char *name, *value;
+ int flags;
+{
+ SHELL_VAR *v;
+ int mklocal, mkassoc;
+ WORD_LIST *list;
+
+ mklocal = flags & ASS_MKLOCAL;
+ mkassoc = flags & ASS_MKASSOC;
+
+ if (mklocal && variable_context)
+ {
+ v = find_variable (name);
+ list = expand_compound_array_assignment (v, value, flags);
+ if (mkassoc)
+ v = make_local_assoc_variable (name);
+ else if (v == 0 || (array_p (v) == 0 && assoc_p (v) == 0) || v->context != variable_context)
+ v = make_local_array_variable (name);
+ assign_compound_array_list (v, list, flags);
+ }
+ else
+ v = assign_array_from_string (name, value, flags);
+
+ return (v);
+}
+#endif
+
+/* Given STRING, an assignment string, get the value of the right side
+ of the `=', and bind it to the left side. If EXPAND is true, then
+ perform parameter expansion, command substitution, and arithmetic
+ expansion on the right-hand side. Perform tilde expansion in any
+ case. Do not perform word splitting on the result of expansion. */
+static int
+do_assignment_internal (word, expand)
+ const WORD_DESC *word;
+ int expand;
+{
+ int offset, tlen, appendop, assign_list, aflags, retval;
+ char *name, *value;
+ SHELL_VAR *entry;
+#if defined (ARRAY_VARS)
+ char *t;
+ int ni;
+#endif
+ const char *string;
+
+ if (word == 0 || word->word == 0)
+ return 0;
+
+ appendop = assign_list = aflags = 0;
+ string = word->word;
+ offset = assignment (string, 0);
+ name = savestring (string);
+ value = (char *)NULL;
+
+ if (name[offset] == '=')
+ {
+ char *temp;
+
+ if (name[offset - 1] == '+')
+ {
+ appendop = 1;
+ name[offset - 1] = '\0';
+ }
+
+ name[offset] = 0; /* might need this set later */
+ temp = name + offset + 1;
+ tlen = STRLEN (temp);
+
+#if defined (ARRAY_VARS)
+ if (expand && (word->flags & W_COMPASSIGN))
+ {
+ assign_list = ni = 1;
+ value = extract_array_assignment_list (temp, &ni);
+ }
+ else
+#endif
+ if (expand && temp[0])
+ value = expand_string_if_necessary (temp, 0, expand_string_assignment);
+ else
+ value = savestring (temp);
+ }
+
+ if (value == 0)
+ {
+ value = (char *)xmalloc (1);
+ value[0] = '\0';
+ }
+
+ if (echo_command_at_execute)
+ {
+ if (appendop)
+ name[offset - 1] = '+';
+ xtrace_print_assignment (name, value, assign_list, 1);
+ if (appendop)
+ name[offset - 1] = '\0';
+ }
+
+#define ASSIGN_RETURN(r) do { FREE (value); free (name); return (r); } while (0)
+
+ if (appendop)
+ aflags |= ASS_APPEND;
+
+#if defined (ARRAY_VARS)
+ if (t = mbschr (name, '[')) /*]*/
+ {
+ if (assign_list)
+ {
+ report_error (_("%s: cannot assign list to array member"), name);
+ ASSIGN_RETURN (0);
+ }
+ entry = assign_array_element (name, value, aflags);
+ if (entry == 0)
+ ASSIGN_RETURN (0);
+ }
+ else if (assign_list)
+ {
+ if (word->flags & W_ASSIGNARG)
+ aflags |= ASS_MKLOCAL;
+ if (word->flags & W_ASSIGNASSOC)
+ aflags |= ASS_MKASSOC;
+ entry = do_compound_assignment (name, value, aflags);
+ }
+ else
+#endif /* ARRAY_VARS */
+ entry = bind_variable (name, value, aflags);
+
+ stupidly_hack_special_variables (name);
+
+#if 1
+ /* Return 1 if the assignment seems to have been performed correctly. */
+ if (entry == 0 || readonly_p (entry))
+ retval = 0; /* assignment failure */
+ else if (noassign_p (entry))
+ {
+ last_command_exit_value = EXECUTION_FAILURE;
+ retval = 1; /* error status, but not assignment failure */
+ }
+ else
+ retval = 1;
+
+ if (entry && retval != 0 && noassign_p (entry) == 0)
+ VUNSETATTR (entry, att_invisible);
+
+ ASSIGN_RETURN (retval);
+#else
+ if (entry)
+ VUNSETATTR (entry, att_invisible);
+
+ ASSIGN_RETURN (entry ? ((readonly_p (entry) == 0) && noassign_p (entry) == 0) : 0);
+#endif
+}
+
+/* Perform the assignment statement in STRING, and expand the
+ right side by doing tilde, command and parameter expansion. */
+int
+do_assignment (string)
+ char *string;
+{
+ WORD_DESC td;
+
+ td.flags = W_ASSIGNMENT;
+ td.word = string;
+
+ return do_assignment_internal (&td, 1);
+}
+
+int
+do_word_assignment (word)
+ WORD_DESC *word;
+{
+ return do_assignment_internal (word, 1);
+}
+
+/* Given STRING, an assignment string, get the value of the right side
+ of the `=', and bind it to the left side. Do not perform any word
+ expansions on the right hand side. */
+int
+do_assignment_no_expand (string)
+ char *string;
+{
+ WORD_DESC td;
+
+ td.flags = W_ASSIGNMENT;
+ td.word = string;
+
+ return (do_assignment_internal (&td, 0));
+}
+
+/***************************************************
+ * *
+ * Functions to manage the positional parameters *
+ * *
+ ***************************************************/
+
+/* Return the word list that corresponds to `$*'. */
+WORD_LIST *
+list_rest_of_args ()
+{
+ register WORD_LIST *list, *args;
+ int i;
+
+ /* Break out of the loop as soon as one of the dollar variables is null. */
+ for (i = 1, list = (WORD_LIST *)NULL; i < 10 && dollar_vars[i]; i++)
+ list = make_word_list (make_bare_word (dollar_vars[i]), list);
+
+ for (args = rest_of_args; args; args = args->next)
+ list = make_word_list (make_bare_word (args->word->word), list);
+
+ return (REVERSE_LIST (list, WORD_LIST *));
+}
+
+int
+number_of_args ()
+{
+ register WORD_LIST *list;
+ int n;
+
+ for (n = 0; n < 9 && dollar_vars[n+1]; n++)
+ ;
+ for (list = rest_of_args; list; list = list->next)
+ n++;
+ return n;
+}
+
+/* Return the value of a positional parameter. This handles values > 10. */
+char *
+get_dollar_var_value (ind)
+ intmax_t ind;
+{
+ char *temp;
+ WORD_LIST *p;
+
+ if (ind < 10)
+ temp = dollar_vars[ind] ? savestring (dollar_vars[ind]) : (char *)NULL;
+ else /* We want something like ${11} */
+ {
+ ind -= 10;
+ for (p = rest_of_args; p && ind--; p = p->next)
+ ;
+ temp = p ? savestring (p->word->word) : (char *)NULL;
+ }
+ return (temp);
+}
+
+/* Make a single large string out of the dollar digit variables,
+ and the rest_of_args. If DOLLAR_STAR is 1, then obey the special
+ case of "$*" with respect to IFS. */
+char *
+string_rest_of_args (dollar_star)
+ int dollar_star;
+{
+ register WORD_LIST *list;
+ char *string;
+
+ list = list_rest_of_args ();
+ string = dollar_star ? string_list_dollar_star (list) : string_list (list);
+ dispose_words (list);
+ return (string);
+}
+
+/* Return a string containing the positional parameters from START to
+ END, inclusive. If STRING[0] == '*', we obey the rules for $*,
+ which only makes a difference if QUOTED is non-zero. If QUOTED includes
+ Q_HERE_DOCUMENT or Q_DOUBLE_QUOTES, this returns a quoted list, otherwise
+ no quoting chars are added. */
+static char *
+pos_params (string, start, end, quoted)
+ char *string;
+ int start, end, quoted;
+{
+ WORD_LIST *save, *params, *h, *t;
+ char *ret;
+ int i;
+
+ /* see if we can short-circuit. if start == end, we want 0 parameters. */
+ if (start == end)
+ return ((char *)NULL);
+
+ save = params = list_rest_of_args ();
+ if (save == 0)
+ return ((char *)NULL);
+
+ if (start == 0) /* handle ${@:0[:x]} specially */
+ {
+ t = make_word_list (make_word (dollar_vars[0]), params);
+ save = params = t;
+ }
+
+ for (i = start ? 1 : 0; params && i < start; i++)
+ params = params->next;
+ if (params == 0)
+ return ((char *)NULL);
+ for (h = t = params; params && i < end; i++)
+ {
+ t = params;
+ params = params->next;
+ }
+
+ t->next = (WORD_LIST *)NULL;
+
+ ret = string_list_pos_params (string[0], h, quoted);
+
+ if (t != params)
+ t->next = params;
+
+ dispose_words (save);
+ return (ret);
+}
+
+/******************************************************************/
+/* */
+/* Functions to expand strings to strings or WORD_LISTs */
+/* */
+/******************************************************************/
+
+#if defined (PROCESS_SUBSTITUTION)
+#define EXP_CHAR(s) (s == '$' || s == '`' || s == '<' || s == '>' || s == CTLESC || s == '~')
+#else
+#define EXP_CHAR(s) (s == '$' || s == '`' || s == CTLESC || s == '~')
+#endif
+
+/* If there are any characters in STRING that require full expansion,
+ then call FUNC to expand STRING; otherwise just perform quote
+ removal if necessary. This returns a new string. */
+static char *
+expand_string_if_necessary (string, quoted, func)
+ char *string;
+ int quoted;
+ EXPFUNC *func;
+{
+ WORD_LIST *list;
+ size_t slen;
+ int i, saw_quote;
+ char *ret;
+ DECLARE_MBSTATE;
+
+ /* Don't need string length for ADVANCE_CHAR unless multibyte chars possible. */
+ slen = (MB_CUR_MAX > 1) ? strlen (string) : 0;
+ i = saw_quote = 0;
+ while (string[i])
+ {
+ if (EXP_CHAR (string[i]))
+ break;
+ else if (string[i] == '\'' || string[i] == '\\' || string[i] == '"')
+ saw_quote = 1;
+ ADVANCE_CHAR (string, slen, i);
+ }
+
+ if (string[i])
+ {
+ list = (*func) (string, quoted);
+ if (list)
+ {
+ ret = string_list (list);
+ dispose_words (list);
+ }
+ else
+ ret = (char *)NULL;
+ }
+ else if (saw_quote && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0))
+ ret = string_quote_removal (string, quoted);
+ else
+ ret = savestring (string);
+
+ return ret;
+}
+
+static inline char *
+expand_string_to_string_internal (string, quoted, func)
+ char *string;
+ int quoted;
+ EXPFUNC *func;
+{
+ WORD_LIST *list;
+ char *ret;
+
+ if (string == 0 || *string == '\0')
+ return ((char *)NULL);
+
+ list = (*func) (string, quoted);
+ if (list)
+ {
+ ret = string_list (list);
+ dispose_words (list);
+ }
+ else
+ ret = (char *)NULL;
+
+ return (ret);
+}
+
+char *
+expand_string_to_string (string, quoted)
+ char *string;
+ int quoted;
+{
+ return (expand_string_to_string_internal (string, quoted, expand_string));
+}
+
+char *
+expand_string_unsplit_to_string (string, quoted)
+ char *string;
+ int quoted;
+{
+ return (expand_string_to_string_internal (string, quoted, expand_string_unsplit));
+}
+
+char *
+expand_assignment_string_to_string (string, quoted)
+ char *string;
+ int quoted;
+{
+ return (expand_string_to_string_internal (string, quoted, expand_string_assignment));
+}
+
+char *
+expand_arith_string (string, quoted)
+ char *string;
+ int quoted;
+{
+ return (expand_string_if_necessary (string, quoted, expand_string));
+}
+
+#if defined (COND_COMMAND)
+/* Just remove backslashes in STRING. Returns a new string. */
+char *
+remove_backslashes (string)
+ char *string;
+{
+ char *r, *ret, *s;
+
+ r = ret = (char *)xmalloc (strlen (string) + 1);
+ for (s = string; s && *s; )
+ {
+ if (*s == '\\')
+ s++;
+ if (*s == 0)
+ break;
+ *r++ = *s++;
+ }
+ *r = '\0';
+ return ret;
+}
+
+/* This needs better error handling. */
+/* Expand W for use as an argument to a unary or binary operator in a
+ [[...]] expression. If SPECIAL is 1, this is the rhs argument
+ to the != or == operator, and should be treated as a pattern. In
+ this case, we quote the string specially for the globbing code. If
+ SPECIAL is 2, this is an rhs argument for the =~ operator, and should
+ be quoted appropriately for regcomp/regexec. The caller is responsible
+ for removing the backslashes if the unquoted word is needed later. */
+char *
+cond_expand_word (w, special)
+ WORD_DESC *w;
+ int special;
+{
+ char *r, *p;
+ WORD_LIST *l;
+ int qflags;
+
+ if (w->word == 0 || w->word[0] == '\0')
+ return ((char *)NULL);
+
+ w->flags |= W_NOSPLIT2;
+ l = call_expand_word_internal (w, 0, 0, (int *)0, (int *)0);
+ if (l)
+ {
+ if (special == 0)
+ {
+ dequote_list (l);
+ r = string_list (l);
+ }
+ else
+ {
+ qflags = QGLOB_CVTNULL;
+ if (special == 2)
+ qflags |= QGLOB_REGEXP;
+ p = string_list (l);
+ r = quote_string_for_globbing (p, qflags);
+ free (p);
+ }
+ dispose_words (l);
+ }
+ else
+ r = (char *)NULL;
+
+ return r;
+}
+#endif
+
+/* Call expand_word_internal to expand W and handle error returns.
+ A convenience function for functions that don't want to handle
+ any errors or free any memory before aborting. */
+static WORD_LIST *
+call_expand_word_internal (w, q, i, c, e)
+ WORD_DESC *w;
+ int q, i, *c, *e;
+{
+ WORD_LIST *result;
+
+ result = expand_word_internal (w, q, i, c, e);
+ if (result == &expand_word_error || result == &expand_word_fatal)
+ {
+ /* By convention, each time this error is returned, w->word has
+ already been freed (it sometimes may not be in the fatal case,
+ but that doesn't result in a memory leak because we're going
+ to exit in most cases). */
+ w->word = (char *)NULL;
+ last_command_exit_value = EXECUTION_FAILURE;
+ exp_jump_to_top_level ((result == &expand_word_error) ? DISCARD : FORCE_EOF);
+ /* NOTREACHED */
+ }
+ else
+ return (result);
+}
+
+/* Perform parameter expansion, command substitution, and arithmetic
+ expansion on STRING, as if it were a word. Leave the result quoted. */
+static WORD_LIST *
+expand_string_internal (string, quoted)
+ char *string;
+ int quoted;
+{
+ WORD_DESC td;
+ WORD_LIST *tresult;
+
+ if (string == 0 || *string == 0)
+ return ((WORD_LIST *)NULL);
+
+ td.flags = 0;
+ td.word = savestring (string);
+
+ tresult = call_expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL);
+
+ FREE (td.word);
+ return (tresult);
+}
+
+/* Expand STRING by performing parameter expansion, command substitution,
+ and arithmetic expansion. Dequote the resulting WORD_LIST before
+ returning it, but do not perform word splitting. The call to
+ remove_quoted_nulls () is in here because word splitting normally
+ takes care of quote removal. */
+WORD_LIST *
+expand_string_unsplit (string, quoted)
+ char *string;
+ int quoted;
+{
+ WORD_LIST *value;
+
+ if (string == 0 || *string == '\0')
+ return ((WORD_LIST *)NULL);
+
+ expand_no_split_dollar_star = 1;
+ value = expand_string_internal (string, quoted);
+ expand_no_split_dollar_star = 0;
+
+ if (value)
+ {
+ if (value->word)
+ {
+ remove_quoted_nulls (value->word->word);
+ value->word->flags &= ~W_HASQUOTEDNULL;
+ }
+ dequote_list (value);
+ }
+ return (value);
+}
+
+/* Expand the rhs of an assignment statement */
+WORD_LIST *
+expand_string_assignment (string, quoted)
+ char *string;
+ int quoted;
+{
+ WORD_DESC td;
+ WORD_LIST *value;
+
+ if (string == 0 || *string == '\0')
+ return ((WORD_LIST *)NULL);
+
+ expand_no_split_dollar_star = 1;
+
+ td.flags = W_ASSIGNRHS;
+ td.word = savestring (string);
+ value = call_expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL);
+ FREE (td.word);
+
+ expand_no_split_dollar_star = 0;
+
+ if (value)
+ {
+ if (value->word)
+ {
+ remove_quoted_nulls (value->word->word);
+ value->word->flags &= ~W_HASQUOTEDNULL;
+ }
+ dequote_list (value);
+ }
+ return (value);
+}
+
+
+/* Expand one of the PS? prompt strings. This is a sort of combination of
+ expand_string_unsplit and expand_string_internal, but returns the
+ passed string when an error occurs. Might want to trap other calls
+ to jump_to_top_level here so we don't endlessly loop. */
+WORD_LIST *
+expand_prompt_string (string, quoted, wflags)
+ char *string;
+ int quoted;
+ int wflags;
+{
+ WORD_LIST *value;
+ WORD_DESC td;
+
+ if (string == 0 || *string == 0)
+ return ((WORD_LIST *)NULL);
+
+ td.flags = wflags;
+ td.word = savestring (string);
+
+ no_longjmp_on_fatal_error = 1;
+ value = expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL);
+ no_longjmp_on_fatal_error = 0;
+
+ if (value == &expand_word_error || value == &expand_word_fatal)
+ {
+ value = make_word_list (make_bare_word (string), (WORD_LIST *)NULL);
+ return value;
+ }
+ FREE (td.word);
+ if (value)
+ {
+ if (value->word)
+ {
+ remove_quoted_nulls (value->word->word);
+ value->word->flags &= ~W_HASQUOTEDNULL;
+ }
+ dequote_list (value);
+ }
+ return (value);
+}
+
+/* Expand STRING just as if you were expanding a word, but do not dequote
+ the resultant WORD_LIST. This is called only from within this file,
+ and is used to correctly preserve quoted characters when expanding
+ things like ${1+"$@"}. This does parameter expansion, command
+ substitution, arithmetic expansion, and word splitting. */
+static WORD_LIST *
+expand_string_leave_quoted (string, quoted)
+ char *string;
+ int quoted;
+{
+ WORD_LIST *tlist;
+ WORD_LIST *tresult;
+
+ if (string == 0 || *string == '\0')
+ return ((WORD_LIST *)NULL);
+
+ tlist = expand_string_internal (string, quoted);
+
+ if (tlist)
+ {
+ tresult = word_list_split (tlist);
+ dispose_words (tlist);
+ return (tresult);
+ }
+ return ((WORD_LIST *)NULL);
+}
+
+/* This does not perform word splitting or dequote the WORD_LIST
+ it returns. */
+static WORD_LIST *
+expand_string_for_rhs (string, quoted, dollar_at_p, has_dollar_at)
+ char *string;
+ int quoted, *dollar_at_p, *has_dollar_at;
+{
+ WORD_DESC td;
+ WORD_LIST *tresult;
+
+ if (string == 0 || *string == '\0')
+ return (WORD_LIST *)NULL;
+
+ td.flags = 0;
+ td.word = string;
+ tresult = call_expand_word_internal (&td, quoted, 1, dollar_at_p, has_dollar_at);
+ return (tresult);
+}
+
+/* Expand STRING just as if you were expanding a word. This also returns
+ a list of words. Note that filename globbing is *NOT* done for word
+ or string expansion, just when the shell is expanding a command. This
+ does parameter expansion, command substitution, arithmetic expansion,
+ and word splitting. Dequote the resultant WORD_LIST before returning. */
+WORD_LIST *
+expand_string (string, quoted)
+ char *string;
+ int quoted;
+{
+ WORD_LIST *result;
+
+ if (string == 0 || *string == '\0')
+ return ((WORD_LIST *)NULL);
+
+ result = expand_string_leave_quoted (string, quoted);
+ return (result ? dequote_list (result) : result);
+}
+
+/***************************************************
+ * *
+ * Functions to handle quoting chars *
+ * *
+ ***************************************************/
+
+/* Conventions:
+
+ A string with s[0] == CTLNUL && s[1] == 0 is a quoted null string.
+ The parser passes CTLNUL as CTLESC CTLNUL. */
+
+/* Quote escape characters in string s, but no other characters. This is
+ used to protect CTLESC and CTLNUL in variable values from the rest of
+ the word expansion process after the variable is expanded (word splitting
+ and filename generation). If IFS is null, we quote spaces as well, just
+ in case we split on spaces later (in the case of unquoted $@, we will
+ eventually attempt to split the entire word on spaces). Corresponding
+ code exists in dequote_escapes. Even if we don't end up splitting on
+ spaces, quoting spaces is not a problem. This should never be called on
+ a string that is quoted with single or double quotes or part of a here
+ document (effectively double-quoted). */
+char *
+quote_escapes (string)
+ char *string;
+{
+ register char *s, *t;
+ size_t slen;
+ char *result, *send;
+ int quote_spaces, skip_ctlesc, skip_ctlnul;
+ DECLARE_MBSTATE;
+
+ slen = strlen (string);
+ send = string + slen;
+
+ quote_spaces = (ifs_value && *ifs_value == 0);
+
+ for (skip_ctlesc = skip_ctlnul = 0, s = ifs_value; s && *s; s++)
+ skip_ctlesc |= *s == CTLESC, skip_ctlnul |= *s == CTLNUL;
+
+ t = result = (char *)xmalloc ((slen * 2) + 1);
+ s = string;
+
+ while (*s)
+ {
+ if ((skip_ctlesc == 0 && *s == CTLESC) || (skip_ctlnul == 0 && *s == CTLNUL) || (quote_spaces && *s == ' '))
+ *t++ = CTLESC;
+ COPY_CHAR_P (t, s, send);
+ }
+ *t = '\0';
+ return (result);
+}
+
+static WORD_LIST *
+list_quote_escapes (list)
+ WORD_LIST *list;
+{
+ register WORD_LIST *w;
+ char *t;
+
+ for (w = list; w; w = w->next)
+ {
+ t = w->word->word;
+ w->word->word = quote_escapes (t);
+ free (t);
+ }
+ return list;
+}
+
+/* Inverse of quote_escapes; remove CTLESC protecting CTLESC or CTLNUL.
+
+ The parser passes us CTLESC as CTLESC CTLESC and CTLNUL as CTLESC CTLNUL.
+ This is necessary to make unquoted CTLESC and CTLNUL characters in the
+ data stream pass through properly.
+
+ We need to remove doubled CTLESC characters inside quoted strings before
+ quoting the entire string, so we do not double the number of CTLESC
+ characters.
+
+ Also used by parts of the pattern substitution code. */
+char *
+dequote_escapes (string)
+ char *string;
+{
+ register char *s, *t, *s1;
+ size_t slen;
+ char *result, *send;
+ int quote_spaces;
+ DECLARE_MBSTATE;
+
+ if (string == 0)
+ return string;
+
+ slen = strlen (string);
+ send = string + slen;
+
+ t = result = (char *)xmalloc (slen + 1);
+
+ if (strchr (string, CTLESC) == 0)
+ return (strcpy (result, string));
+
+ quote_spaces = (ifs_value && *ifs_value == 0);
+
+ s = string;
+ while (*s)
+ {
+ if (*s == CTLESC && (s[1] == CTLESC || s[1] == CTLNUL || (quote_spaces && s[1] == ' ')))
+ {
+ s++;
+ if (*s == '\0')
+ break;
+ }
+ COPY_CHAR_P (t, s, send);
+ }
+ *t = '\0';
+ return result;
+}
+
+/* Return a new string with the quoted representation of character C.
+ This turns "" into QUOTED_NULL, so the W_HASQUOTEDNULL flag needs to be
+ set in any resultant WORD_DESC where this value is the word. */
+static char *
+make_quoted_char (c)
+ int c;
+{
+ char *temp;
+
+ temp = (char *)xmalloc (3);
+ if (c == 0)
+ {
+ temp[0] = CTLNUL;
+ temp[1] = '\0';
+ }
+ else
+ {
+ temp[0] = CTLESC;
+ temp[1] = c;
+ temp[2] = '\0';
+ }
+ return (temp);
+}
+
+/* Quote STRING, returning a new string. This turns "" into QUOTED_NULL, so
+ the W_HASQUOTEDNULL flag needs to be set in any resultant WORD_DESC where
+ this value is the word. */
+char *
+quote_string (string)
+ char *string;
+{
+ register char *t;
+ size_t slen;
+ char *result, *send;
+
+ if (*string == 0)
+ {
+ result = (char *)xmalloc (2);
+ result[0] = CTLNUL;
+ result[1] = '\0';
+ }
+ else
+ {
+ DECLARE_MBSTATE;
+
+ slen = strlen (string);
+ send = string + slen;
+
+ result = (char *)xmalloc ((slen * 2) + 1);
+
+ for (t = result; string < send; )
+ {
+ *t++ = CTLESC;
+ COPY_CHAR_P (t, string, send);
+ }
+ *t = '\0';
+ }
+ return (result);
+}
+
+/* De-quote quoted characters in STRING. */
+char *
+dequote_string (string)
+ char *string;
+{
+ register char *s, *t;
+ size_t slen;
+ char *result, *send;
+ DECLARE_MBSTATE;
+
+ slen = strlen (string);
+
+ t = result = (char *)xmalloc (slen + 1);
+
+ if (QUOTED_NULL (string))
+ {
+ result[0] = '\0';
+ return (result);
+ }
+
+ /* If no character in the string can be quoted, don't bother examining
+ each character. Just return a copy of the string passed to us. */
+ if (strchr (string, CTLESC) == NULL)
+ return (strcpy (result, string));
+
+ send = string + slen;
+ s = string;
+ while (*s)
+ {
+ if (*s == CTLESC)
+ {
+ s++;
+ if (*s == '\0')
+ break;
+ }
+ COPY_CHAR_P (t, s, send);
+ }
+
+ *t = '\0';
+ return (result);
+}
+
+/* Quote the entire WORD_LIST list. */
+static WORD_LIST *
+quote_list (list)
+ WORD_LIST *list;
+{
+ register WORD_LIST *w;
+ char *t;
+
+ for (w = list; w; w = w->next)
+ {
+ t = w->word->word;
+ w->word->word = quote_string (t);
+ if (*t == 0)
+ w->word->flags |= W_HASQUOTEDNULL; /* XXX - turn on W_HASQUOTEDNULL here? */
+ w->word->flags |= W_QUOTED;
+ free (t);
+ }
+ return list;
+}
+
+/* De-quote quoted characters in each word in LIST. */
+WORD_LIST *
+dequote_list (list)
+ WORD_LIST *list;
+{
+ register char *s;
+ register WORD_LIST *tlist;
+
+ for (tlist = list; tlist; tlist = tlist->next)
+ {
+ s = dequote_string (tlist->word->word);
+ if (QUOTED_NULL (tlist->word->word))
+ tlist->word->flags &= ~W_HASQUOTEDNULL;
+ free (tlist->word->word);
+ tlist->word->word = s;
+ }
+ return list;
+}
+
+/* Remove CTLESC protecting a CTLESC or CTLNUL in place. Return the passed
+ string. */
+char *
+remove_quoted_escapes (string)
+ char *string;
+{
+ char *t;
+
+ if (string)
+ {
+ t = dequote_escapes (string);
+ strcpy (string, t);
+ free (t);
+ }
+
+ return (string);
+}
+
+/* Perform quoted null character removal on STRING. We don't allow any
+ quoted null characters in the middle or at the ends of strings because
+ of how expand_word_internal works. remove_quoted_nulls () turns
+ STRING into an empty string iff it only consists of a quoted null,
+ and removes all unquoted CTLNUL characters. */
+char *
+remove_quoted_nulls (string)
+ char *string;
+{
+ register size_t slen;
+ register int i, j, prev_i;
+ DECLARE_MBSTATE;
+
+ if (strchr (string, CTLNUL) == 0) /* XXX */
+ return string; /* XXX */
+
+ slen = strlen (string);
+ i = j = 0;
+
+ while (i < slen)
+ {
+ if (string[i] == CTLESC)
+ {
+ /* Old code had j++, but we cannot assume that i == j at this
+ point -- what if a CTLNUL has already been removed from the
+ string? We don't want to drop the CTLESC or recopy characters
+ that we've already copied down. */
+ i++; string[j++] = CTLESC;
+ if (i == slen)
+ break;
+ }
+ else if (string[i] == CTLNUL)
+ i++;
+
+ prev_i = i;
+ ADVANCE_CHAR (string, slen, i);
+ if (j < prev_i)
+ {
+ do string[j++] = string[prev_i++]; while (prev_i < i);
+ }
+ else
+ j = i;
+ }
+ string[j] = '\0';
+
+ return (string);
+}
+
+/* Perform quoted null character removal on each element of LIST.
+ This modifies LIST. */
+void
+word_list_remove_quoted_nulls (list)
+ WORD_LIST *list;
+{
+ register WORD_LIST *t;
+
+ for (t = list; t; t = t->next)
+ {
+ remove_quoted_nulls (t->word->word);
+ t->word->flags &= ~W_HASQUOTEDNULL;
+ }
+}
+
+/* **************************************************************** */
+/* */
+/* Functions for Matching and Removing Patterns */
+/* */
+/* **************************************************************** */
+
+#if defined (HANDLE_MULTIBYTE)
+#if 0 /* Currently unused */
+static unsigned char *
+mb_getcharlens (string, len)
+ char *string;
+ int len;
+{
+ int i, offset, last;
+ unsigned char *ret;
+ char *p;
+ DECLARE_MBSTATE;
+
+ i = offset = 0;
+ last = 0;
+ ret = (unsigned char *)xmalloc (len);
+ memset (ret, 0, len);
+ while (string[last])
+ {
+ ADVANCE_CHAR (string, len, offset);
+ ret[last] = offset - last;
+ last = offset;
+ }
+ return ret;
+}
+#endif
+#endif
+
+/* Remove the portion of PARAM matched by PATTERN according to OP, where OP
+ can have one of 4 values:
+ RP_LONG_LEFT remove longest matching portion at start of PARAM
+ RP_SHORT_LEFT remove shortest matching portion at start of PARAM
+ RP_LONG_RIGHT remove longest matching portion at end of PARAM
+ RP_SHORT_RIGHT remove shortest matching portion at end of PARAM
+*/
+
+#define RP_LONG_LEFT 1
+#define RP_SHORT_LEFT 2
+#define RP_LONG_RIGHT 3
+#define RP_SHORT_RIGHT 4
+
+static char *
+remove_upattern (param, pattern, op)
+ char *param, *pattern;
+ int op;
+{
+ register int len;
+ register char *end;
+ register char *p, *ret, c;
+
+ len = STRLEN (param);
+ end = param + len;
+
+ switch (op)
+ {
+ case RP_LONG_LEFT: /* remove longest match at start */
+ for (p = end; p >= param; p--)
+ {
+ c = *p; *p = '\0';
+ if (strmatch (pattern, param, FNMATCH_EXTFLAG) != FNM_NOMATCH)
+ {
+ *p = c;
+ return (savestring (p));
+ }
+ *p = c;
+
+ }
+ break;
+
+ case RP_SHORT_LEFT: /* remove shortest match at start */
+ for (p = param; p <= end; p++)
+ {
+ c = *p; *p = '\0';
+ if (strmatch (pattern, param, FNMATCH_EXTFLAG) != FNM_NOMATCH)
+ {
+ *p = c;
+ return (savestring (p));
+ }
+ *p = c;
+ }
+ break;
+
+ case RP_LONG_RIGHT: /* remove longest match at end */
+ for (p = param; p <= end; p++)
+ {
+ if (strmatch (pattern, p, FNMATCH_EXTFLAG) != FNM_NOMATCH)
+ {
+ c = *p; *p = '\0';
+ ret = savestring (param);
+ *p = c;
+ return (ret);
+ }
+ }
+ break;
+
+ case RP_SHORT_RIGHT: /* remove shortest match at end */
+ for (p = end; p >= param; p--)
+ {
+ if (strmatch (pattern, p, FNMATCH_EXTFLAG) != FNM_NOMATCH)
+ {
+ c = *p; *p = '\0';
+ ret = savestring (param);
+ *p = c;
+ return (ret);
+ }
+ }
+ break;
+ }
+
+ return (savestring (param)); /* no match, return original string */
+}
+
+#if defined (HANDLE_MULTIBYTE)
+static wchar_t *
+remove_wpattern (wparam, wstrlen, wpattern, op)
+ wchar_t *wparam;
+ size_t wstrlen;
+ wchar_t *wpattern;
+ int op;
+{
+ wchar_t wc, *ret;
+ int n;
+
+ switch (op)
+ {
+ case RP_LONG_LEFT: /* remove longest match at start */
+ for (n = wstrlen; n >= 0; n--)
+ {
+ wc = wparam[n]; wparam[n] = L'\0';
+ if (wcsmatch (wpattern, wparam, FNMATCH_EXTFLAG) != FNM_NOMATCH)
+ {
+ wparam[n] = wc;
+ return (wcsdup (wparam + n));
+ }
+ wparam[n] = wc;
+ }
+ break;
+
+ case RP_SHORT_LEFT: /* remove shortest match at start */
+ for (n = 0; n <= wstrlen; n++)
+ {
+ wc = wparam[n]; wparam[n] = L'\0';
+ if (wcsmatch (wpattern, wparam, FNMATCH_EXTFLAG) != FNM_NOMATCH)
+ {
+ wparam[n] = wc;
+ return (wcsdup (wparam + n));
+ }
+ wparam[n] = wc;
+ }
+ break;
+
+ case RP_LONG_RIGHT: /* remove longest match at end */
+ for (n = 0; n <= wstrlen; n++)
+ {
+ if (wcsmatch (wpattern, wparam + n, FNMATCH_EXTFLAG) != FNM_NOMATCH)
+ {
+ wc = wparam[n]; wparam[n] = L'\0';
+ ret = wcsdup (wparam);
+ wparam[n] = wc;
+ return (ret);
+ }
+ }
+ break;
+
+ case RP_SHORT_RIGHT: /* remove shortest match at end */
+ for (n = wstrlen; n >= 0; n--)
+ {
+ if (wcsmatch (wpattern, wparam + n, FNMATCH_EXTFLAG) != FNM_NOMATCH)
+ {
+ wc = wparam[n]; wparam[n] = L'\0';
+ ret = wcsdup (wparam);
+ wparam[n] = wc;
+ return (ret);
+ }
+ }
+ break;
+ }
+
+ return (wcsdup (wparam)); /* no match, return original string */
+}
+#endif /* HANDLE_MULTIBYTE */
+
+static char *
+remove_pattern (param, pattern, op)
+ char *param, *pattern;
+ int op;
+{
+ if (param == NULL)
+ return (param);
+ if (*param == '\0' || pattern == NULL || *pattern == '\0') /* minor optimization */
+ return (savestring (param));
+
+#if defined (HANDLE_MULTIBYTE)
+ if (MB_CUR_MAX > 1)
+ {
+ wchar_t *ret, *oret;
+ size_t n;
+ wchar_t *wparam, *wpattern;
+ mbstate_t ps;
+ char *xret;
+
+ n = xdupmbstowcs (&wpattern, NULL, pattern);
+ if (n == (size_t)-1)
+ return (remove_upattern (param, pattern, op));
+ n = xdupmbstowcs (&wparam, NULL, param);
+ if (n == (size_t)-1)
+ {
+ free (wpattern);
+ return (remove_upattern (param, pattern, op));
+ }
+ oret = ret = remove_wpattern (wparam, n, wpattern, op);
+
+ free (wparam);
+ free (wpattern);
+
+ n = strlen (param);
+ xret = (char *)xmalloc (n + 1);
+ memset (&ps, '\0', sizeof (mbstate_t));
+ n = wcsrtombs (xret, (const wchar_t **)&ret, n, &ps);
+ xret[n] = '\0'; /* just to make sure */
+ free (oret);
+ return xret;
+ }
+ else
+#endif
+ return (remove_upattern (param, pattern, op));
+}
+
+/* Return 1 of the first character of STRING could match the first
+ character of pattern PAT. Used to avoid n2 calls to strmatch(). */
+static int
+match_pattern_char (pat, string)
+ char *pat, *string;
+{
+ char c;
+
+ if (*string == 0)
+ return (0);
+
+ switch (c = *pat++)
+ {
+ default:
+ return (*string == c);
+ case '\\':
+ return (*string == *pat);
+ case '?':
+ return (*pat == LPAREN ? 1 : (*string != '\0'));
+ case '*':
+ return (1);
+ case '+':
+ case '!':
+ case '@':
+ return (*pat == LPAREN ? 1 : (*string == c));
+ case '[':
+ return (*string != '\0');
+ }
+}
+
+/* Match PAT anywhere in STRING and return the match boundaries.
+ This returns 1 in case of a successful match, 0 otherwise. SP
+ and EP are pointers into the string where the match begins and
+ ends, respectively. MTYPE controls what kind of match is attempted.
+ MATCH_BEG and MATCH_END anchor the match at the beginning and end
+ of the string, respectively. The longest match is returned. */
+static int
+match_upattern (string, pat, mtype, sp, ep)
+ char *string, *pat;
+ int mtype;
+ char **sp, **ep;
+{
+ int c, len;
+ register char *p, *p1, *npat;
+ char *end;
+
+ /* If the pattern doesn't match anywhere in the string, go ahead and
+ short-circuit right away. A minor optimization, saves a bunch of
+ unnecessary calls to strmatch (up to N calls for a string of N
+ characters) if the match is unsuccessful. To preserve the semantics
+ of the substring matches below, we make sure that the pattern has
+ `*' as first and last character, making a new pattern if necessary. */
+ /* XXX - check this later if I ever implement `**' with special meaning,
+ since this will potentially result in `**' at the beginning or end */
+ len = STRLEN (pat);
+ if (pat[0] != '*' || (pat[0] == '*' && pat[1] == LPAREN && extended_glob) || pat[len - 1] != '*')
+ {
+ p = npat = (char *)xmalloc (len + 3);
+ p1 = pat;
+ if (*p1 != '*' || (*p1 == '*' && p1[1] == LPAREN && extended_glob))
+ *p++ = '*';
+ while (*p1)
+ *p++ = *p1++;
+ if (p1[-1] != '*' || p[-2] == '\\')
+ *p++ = '*';
+ *p = '\0';
+ }
+ else
+ npat = pat;
+ c = strmatch (npat, string, FNMATCH_EXTFLAG);
+ if (npat != pat)
+ free (npat);
+ if (c == FNM_NOMATCH)
+ return (0);
+
+ len = STRLEN (string);
+ end = string + len;
+
+ switch (mtype)
+ {
+ case MATCH_ANY:
+ for (p = string; p <= end; p++)
+ {
+ if (match_pattern_char (pat, p))
+ {
+ for (p1 = end; p1 >= p; p1--)
+ {
+ c = *p1; *p1 = '\0';
+ if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0)
+ {
+ *p1 = c;
+ *sp = p;
+ *ep = p1;
+ return 1;
+ }
+ *p1 = c;
+ }
+ }
+ }
+
+ return (0);
+
+ case MATCH_BEG:
+ if (match_pattern_char (pat, string) == 0)
+ return (0);
+
+ for (p = end; p >= string; p--)
+ {
+ c = *p; *p = '\0';
+ if (strmatch (pat, string, FNMATCH_EXTFLAG) == 0)
+ {
+ *p = c;
+ *sp = string;
+ *ep = p;
+ return 1;
+ }
+ *p = c;
+ }
+
+ return (0);
+
+ case MATCH_END:
+ for (p = string; p <= end; p++)
+ {
+ if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0)
+ {
+ *sp = p;
+ *ep = end;
+ return 1;
+ }
+
+ }
+
+ return (0);
+ }
+
+ return (0);
+}
+
+#if defined (HANDLE_MULTIBYTE)
+/* Return 1 of the first character of WSTRING could match the first
+ character of pattern WPAT. Wide character version. */
+static int
+match_pattern_wchar (wpat, wstring)
+ wchar_t *wpat, *wstring;
+{
+ wchar_t wc;
+
+ if (*wstring == 0)
+ return (0);
+
+ switch (wc = *wpat++)
+ {
+ default:
+ return (*wstring == wc);
+ case L'\\':
+ return (*wstring == *wpat);
+ case L'?':
+ return (*wpat == LPAREN ? 1 : (*wstring != L'\0'));
+ case L'*':
+ return (1);
+ case L'+':
+ case L'!':
+ case L'@':
+ return (*wpat == LPAREN ? 1 : (*wstring == wc));
+ case L'[':
+ return (*wstring != L'\0');
+ }
+}
+
+/* Match WPAT anywhere in WSTRING and return the match boundaries.
+ This returns 1 in case of a successful match, 0 otherwise. Wide
+ character version. */
+static int
+match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
+ wchar_t *wstring;
+ char **indices;
+ size_t wstrlen;
+ wchar_t *wpat;
+ int mtype;
+ char **sp, **ep;
+{
+ wchar_t wc, *wp, *nwpat, *wp1;
+ int len;
+#if 0
+ size_t n, n1; /* Apple's gcc seems to miscompile this badly */
+#else
+ int n, n1;
+#endif
+
+ /* If the pattern doesn't match anywhere in the string, go ahead and
+ short-circuit right away. A minor optimization, saves a bunch of
+ unnecessary calls to strmatch (up to N calls for a string of N
+ characters) if the match is unsuccessful. To preserve the semantics
+ of the substring matches below, we make sure that the pattern has
+ `*' as first and last character, making a new pattern if necessary. */
+ /* XXX - check this later if I ever implement `**' with special meaning,
+ since this will potentially result in `**' at the beginning or end */
+ len = wcslen (wpat);
+ if (wpat[0] != L'*' || (wpat[0] == L'*' && wpat[1] == WLPAREN && extended_glob) || wpat[len - 1] != L'*')
+ {
+ wp = nwpat = (wchar_t *)xmalloc ((len + 3) * sizeof (wchar_t));
+ wp1 = wpat;
+ if (*wp1 != L'*' || (*wp1 == '*' && wp1[1] == WLPAREN && extended_glob))
+ *wp++ = L'*';
+ while (*wp1 != L'\0')
+ *wp++ = *wp1++;
+ if (wp1[-1] != L'*' || wp1[-2] == L'\\')
+ *wp++ = L'*';
+ *wp = '\0';
+ }
+ else
+ nwpat = wpat;
+ len = wcsmatch (nwpat, wstring, FNMATCH_EXTFLAG);
+ if (nwpat != wpat)
+ free (nwpat);
+ if (len == FNM_NOMATCH)
+ return (0);
+
+ switch (mtype)
+ {
+ case MATCH_ANY:
+ for (n = 0; n <= wstrlen; n++)
+ {
+ if (match_pattern_wchar (wpat, wstring + n))
+ {
+ for (n1 = wstrlen; n1 >= n; n1--)
+ {
+ wc = wstring[n1]; wstring[n1] = L'\0';
+ if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG) == 0)
+ {
+ wstring[n1] = wc;
+ *sp = indices[n];
+ *ep = indices[n1];
+ return 1;
+ }
+ wstring[n1] = wc;
+ }
+ }
+ }
+
+ return (0);
+
+ case MATCH_BEG:
+ if (match_pattern_wchar (wpat, wstring) == 0)
+ return (0);
+
+ for (n = wstrlen; n >= 0; n--)
+ {
+ wc = wstring[n]; wstring[n] = L'\0';
+ if (wcsmatch (wpat, wstring, FNMATCH_EXTFLAG) == 0)
+ {
+ wstring[n] = wc;
+ *sp = indices[0];
+ *ep = indices[n];
+ return 1;
+ }
+ wstring[n] = wc;
+ }
+
+ return (0);
+
+ case MATCH_END:
+ for (n = 0; n <= wstrlen; n++)
+ {
+ if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG) == 0)
+ {
+ *sp = indices[n];
+ *ep = indices[wstrlen];
+ return 1;
+ }
+ }
+
+ return (0);
+ }
+
+ return (0);
+}
+#endif /* HANDLE_MULTIBYTE */
+
+static int
+match_pattern (string, pat, mtype, sp, ep)
+ char *string, *pat;
+ int mtype;
+ char **sp, **ep;
+{
+#if defined (HANDLE_MULTIBYTE)
+ int ret;
+ size_t n;
+ wchar_t *wstring, *wpat;
+ char **indices;
+#endif
+
+ if (string == 0 || *string == 0 || pat == 0 || *pat == 0)
+ return (0);
+
+#if defined (HANDLE_MULTIBYTE)
+ if (MB_CUR_MAX > 1)
+ {
+ n = xdupmbstowcs (&wpat, NULL, pat);
+ if (n == (size_t)-1)
+ return (match_upattern (string, pat, mtype, sp, ep));
+ n = xdupmbstowcs (&wstring, &indices, string);
+ if (n == (size_t)-1)
+ {
+ free (wpat);
+ return (match_upattern (string, pat, mtype, sp, ep));
+ }
+ ret = match_wpattern (wstring, indices, n, wpat, mtype, sp, ep);
+
+ free (wpat);
+ free (wstring);
+ free (indices);
+
+ return (ret);
+ }
+ else
+#endif
+ return (match_upattern (string, pat, mtype, sp, ep));
+}
+
+static int
+getpatspec (c, value)
+ int c;
+ char *value;
+{
+ if (c == '#')
+ return ((*value == '#') ? RP_LONG_LEFT : RP_SHORT_LEFT);
+ else /* c == '%' */
+ return ((*value == '%') ? RP_LONG_RIGHT : RP_SHORT_RIGHT);
+}
+
+/* Posix.2 says that the WORD should be run through tilde expansion,
+ parameter expansion, command substitution and arithmetic expansion.
+ This leaves the result quoted, so quote_string_for_globbing () has
+ to be called to fix it up for strmatch (). If QUOTED is non-zero,
+ it means that the entire expression was enclosed in double quotes.
+ This means that quoting characters in the pattern do not make any
+ special pattern characters quoted. For example, the `*' in the
+ following retains its special meaning: "${foo#'*'}". */
+static char *
+getpattern (value, quoted, expandpat)
+ char *value;
+ int quoted, expandpat;
+{
+ char *pat, *tword;
+ WORD_LIST *l;
+#if 0
+ int i;
+#endif
+ /* There is a problem here: how to handle single or double quotes in the
+ pattern string when the whole expression is between double quotes?
+ POSIX.2 says that enclosing double quotes do not cause the pattern to
+ be quoted, but does that leave us a problem with @ and array[@] and their
+ expansions inside a pattern? */
+#if 0
+ if (expandpat && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && *tword)
+ {
+ i = 0;
+ pat = string_extract_double_quoted (tword, &i, 1);
+ free (tword);
+ tword = pat;
+ }
+#endif
+
+ /* expand_string_for_rhs () leaves WORD quoted and does not perform
+ word splitting. */
+ l = *value ? expand_string_for_rhs (value,
+ (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? Q_PATQUOTE : quoted,
+ (int *)NULL, (int *)NULL)
+ : (WORD_LIST *)0;
+ pat = string_list (l);
+ dispose_words (l);
+ if (pat)
+ {
+ tword = quote_string_for_globbing (pat, QGLOB_CVTNULL);
+ free (pat);
+ pat = tword;
+ }
+ return (pat);
+}
+
+#if 0
+/* Handle removing a pattern from a string as a result of ${name%[%]value}
+ or ${name#[#]value}. */
+static char *
+variable_remove_pattern (value, pattern, patspec, quoted)
+ char *value, *pattern;
+ int patspec, quoted;
+{
+ char *tword;
+
+ tword = remove_pattern (value, pattern, patspec);
+
+ return (tword);
+}
+#endif
+
+static char *
+list_remove_pattern (list, pattern, patspec, itype, quoted)
+ WORD_LIST *list;
+ char *pattern;
+ int patspec, itype, quoted;
+{
+ WORD_LIST *new, *l;
+ WORD_DESC *w;
+ char *tword;
+
+ for (new = (WORD_LIST *)NULL, l = list; l; l = l->next)
+ {
+ tword = remove_pattern (l->word->word, pattern, patspec);
+ w = alloc_word_desc ();
+ w->word = tword ? tword : savestring ("");
+ new = make_word_list (w, new);
+ }
+
+ l = REVERSE_LIST (new, WORD_LIST *);
+ tword = string_list_pos_params (itype, l, quoted);
+ dispose_words (l);
+
+ return (tword);
+}
+
+static char *
+parameter_list_remove_pattern (itype, pattern, patspec, quoted)
+ int itype;
+ char *pattern;
+ int patspec, quoted;
+{
+ char *ret;
+ WORD_LIST *list;
+
+ list = list_rest_of_args ();
+ if (list == 0)
+ return ((char *)NULL);
+ ret = list_remove_pattern (list, pattern, patspec, itype, quoted);
+ dispose_words (list);
+ return (ret);
+}
+
+#if defined (ARRAY_VARS)
+static char *
+array_remove_pattern (var, pattern, patspec, varname, quoted)
+ SHELL_VAR *var;
+ char *pattern;
+ int patspec;
+ char *varname; /* so we can figure out how it's indexed */
+ int quoted;
+{
+ ARRAY *a;
+ HASH_TABLE *h;
+ int itype;
+ char *ret;
+ WORD_LIST *list;
+ SHELL_VAR *v;
+
+ /* compute itype from varname here */
+ v = array_variable_part (varname, &ret, 0);
+ itype = ret[0];
+
+ a = (v && array_p (v)) ? array_cell (v) : 0;
+ h = (v && assoc_p (v)) ? assoc_cell (v) : 0;
+
+ list = a ? array_to_word_list (a) : (h ? assoc_to_word_list (h) : 0);
+ if (list == 0)
+ return ((char *)NULL);
+ ret = list_remove_pattern (list, pattern, patspec, itype, quoted);
+ dispose_words (list);
+
+ return ret;
+}
+#endif /* ARRAY_VARS */
+
+static char *
+parameter_brace_remove_pattern (varname, value, patstr, rtype, quoted)
+ char *varname, *value, *patstr;
+ int rtype, quoted;
+{
+ int vtype, patspec, starsub;
+ char *temp1, *val, *pattern;
+ SHELL_VAR *v;
+
+ if (value == 0)
+ return ((char *)NULL);
+
+ this_command_name = varname;
+
+ vtype = get_var_and_type (varname, value, quoted, &v, &val);
+ if (vtype == -1)
+ return ((char *)NULL);
+
+ starsub = vtype & VT_STARSUB;
+ vtype &= ~VT_STARSUB;
+
+ patspec = getpatspec (rtype, patstr);
+ if (patspec == RP_LONG_LEFT || patspec == RP_LONG_RIGHT)
+ patstr++;
+
+ /* Need to pass getpattern newly-allocated memory in case of expansion --
+ the expansion code will free the passed string on an error. */
+ temp1 = savestring (patstr);
+ pattern = getpattern (temp1, quoted, 1);
+ free (temp1);
+
+ temp1 = (char *)NULL; /* shut up gcc */
+ switch (vtype)
+ {
+ case VT_VARIABLE:
+ case VT_ARRAYMEMBER:
+ temp1 = remove_pattern (val, pattern, patspec);
+ if (vtype == VT_VARIABLE)
+ FREE (val);
+ if (temp1)
+ {
+ val = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+ ? quote_string (temp1)
+ : quote_escapes (temp1);
+ free (temp1);
+ temp1 = val;
+ }
+ break;
+#if defined (ARRAY_VARS)
+ case VT_ARRAYVAR:
+ temp1 = array_remove_pattern (v, pattern, patspec, varname, quoted);
+ if (temp1 && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0))
+ {
+ val = quote_escapes (temp1);
+ free (temp1);
+ temp1 = val;
+ }
+ break;
+#endif
+ case VT_POSPARMS:
+ temp1 = parameter_list_remove_pattern (varname[0], pattern, patspec, quoted);
+ if (temp1 && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0))
+ {
+ val = quote_escapes (temp1);
+ free (temp1);
+ temp1 = val;
+ }
+ break;
+ }
+
+ FREE (pattern);
+ return temp1;
+}
+
+/*******************************************
+ * *
+ * Functions to expand WORD_DESCs *
+ * *
+ *******************************************/
+
+/* Expand WORD, performing word splitting on the result. This does
+ parameter expansion, command substitution, arithmetic expansion,
+ word splitting, and quote removal. */
+
+WORD_LIST *
+expand_word (word, quoted)
+ WORD_DESC *word;
+ int quoted;
+{
+ WORD_LIST *result, *tresult;
+
+ tresult = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL);
+ result = word_list_split (tresult);
+ dispose_words (tresult);
+ return (result ? dequote_list (result) : result);
+}
+
+/* Expand WORD, but do not perform word splitting on the result. This
+ does parameter expansion, command substitution, arithmetic expansion,
+ and quote removal. */
+WORD_LIST *
+expand_word_unsplit (word, quoted)
+ WORD_DESC *word;
+ int quoted;
+{
+ WORD_LIST *result;
+
+ expand_no_split_dollar_star = 1;
+#if defined (HANDLE_MULTIBYTE)
+ if (ifs_firstc[0] == 0)
+#else
+ if (ifs_firstc == 0)
+#endif
+ word->flags |= W_NOSPLIT;
+ result = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL);
+ expand_no_split_dollar_star = 0;
+
+ return (result ? dequote_list (result) : result);
+}
+
+/* Perform shell expansions on WORD, but do not perform word splitting or
+ quote removal on the result. Virtually identical to expand_word_unsplit;
+ could be combined if implementations don't diverge. */
+WORD_LIST *
+expand_word_leave_quoted (word, quoted)
+ WORD_DESC *word;
+ int quoted;
+{
+ WORD_LIST *result;
+
+ expand_no_split_dollar_star = 1;
+#if defined (HANDLE_MULTIBYTE)
+ if (ifs_firstc[0] == 0)
+#else
+ if (ifs_firstc == 0)
+#endif
+ word->flags |= W_NOSPLIT;
+ word->flags |= W_NOSPLIT2;
+ result = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL);
+ expand_no_split_dollar_star = 0;
+
+ return result;
+}
+
+#if defined (PROCESS_SUBSTITUTION)
+
+/*****************************************************************/
+/* */
+/* Hacking Process Substitution */
+/* */
+/*****************************************************************/
+
+#if !defined (HAVE_DEV_FD)
+/* Named pipes must be removed explicitly with `unlink'. This keeps a list
+ of FIFOs the shell has open. unlink_fifo_list will walk the list and
+ unlink all of them. add_fifo_list adds the name of an open FIFO to the
+ list. NFIFO is a count of the number of FIFOs in the list. */
+#define FIFO_INCR 20
+
+struct temp_fifo {
+ char *file;
+ pid_t proc;
+};
+
+static struct temp_fifo *fifo_list = (struct temp_fifo *)NULL;
+static int nfifo;
+static int fifo_list_size;
+
+static void
+add_fifo_list (pathname)
+ char *pathname;
+{
+ if (nfifo >= fifo_list_size - 1)
+ {
+ fifo_list_size += FIFO_INCR;
+ fifo_list = (struct temp_fifo *)xrealloc (fifo_list,
+ fifo_list_size * sizeof (struct temp_fifo));
+ }
+
+ fifo_list[nfifo].file = savestring (pathname);
+ nfifo++;
+}
+
+void
+unlink_fifo_list ()
+{
+ int saved, i, j;
+
+ if (nfifo == 0)
+ return;
+
+ for (i = saved = 0; i < nfifo; i++)
+ {
+ if ((fifo_list[i].proc == -1) || (kill(fifo_list[i].proc, 0) == -1))
+ {
+ unlink (fifo_list[i].file);
+ free (fifo_list[i].file);
+ fifo_list[i].file = (char *)NULL;
+ fifo_list[i].proc = -1;
+ }
+ else
+ saved++;
+ }
+
+ /* If we didn't remove some of the FIFOs, compact the list. */
+ if (saved)
+ {
+ for (i = j = 0; i < nfifo; i++)
+ if (fifo_list[i].file)
+ {
+ fifo_list[j].file = fifo_list[i].file;
+ fifo_list[j].proc = fifo_list[i].proc;
+ j++;
+ }
+ nfifo = j;
+ }
+ else
+ nfifo = 0;
+}
+
+int
+fifos_pending ()
+{
+ return nfifo;
+}
+
+static char *
+make_named_pipe ()
+{
+ char *tname;
+
+ tname = sh_mktmpname ("sh-np", MT_USERANDOM|MT_USETMPDIR);
+ if (mkfifo (tname, 0600) < 0)
+ {
+ free (tname);
+ return ((char *)NULL);
+ }
+
+ add_fifo_list (tname);
+ return (tname);
+}
+
+#else /* HAVE_DEV_FD */
+
+/* DEV_FD_LIST is a bitmap of file descriptors attached to pipes the shell
+ has open to children. NFDS is a count of the number of bits currently
+ set in DEV_FD_LIST. TOTFDS is a count of the highest possible number
+ of open files. */
+static char *dev_fd_list = (char *)NULL;
+static int nfds;
+static int totfds; /* The highest possible number of open files. */
+
+static void
+add_fifo_list (fd)
+ int fd;
+{
+ if (!dev_fd_list || fd >= totfds)
+ {
+ int ofds;
+
+ ofds = totfds;
+ totfds = getdtablesize ();
+ if (totfds < 0 || totfds > 256)
+ totfds = 256;
+ if (fd >= totfds)
+ totfds = fd + 2;
+
+ dev_fd_list = (char *)xrealloc (dev_fd_list, totfds);
+ memset (dev_fd_list + ofds, '\0', totfds - ofds);
+ }
+
+ dev_fd_list[fd] = 1;
+ nfds++;
+}
+
+int
+fifos_pending ()
+{
+ return 0; /* used for cleanup; not needed with /dev/fd */
+}
+
+void
+unlink_fifo_list ()
+{
+ register int i;
+
+ if (nfds == 0)
+ return;
+
+ for (i = 0; nfds && i < totfds; i++)
+ if (dev_fd_list[i])
+ {
+ close (i);
+ dev_fd_list[i] = 0;
+ nfds--;
+ }
+
+ nfds = 0;
+}
+
+#if defined (NOTDEF)
+print_dev_fd_list ()
+{
+ register int i;
+
+ fprintf (stderr, "pid %ld: dev_fd_list:", (long)getpid ());
+ fflush (stderr);
+
+ for (i = 0; i < totfds; i++)
+ {
+ if (dev_fd_list[i])
+ fprintf (stderr, " %d", i);
+ }
+ fprintf (stderr, "\n");
+}
+#endif /* NOTDEF */
+
+static char *
+make_dev_fd_filename (fd)
+ int fd;
+{
+ char *ret, intbuf[INT_STRLEN_BOUND (int) + 1], *p;
+
+ ret = (char *)xmalloc (sizeof (DEV_FD_PREFIX) + 8);
+
+ strcpy (ret, DEV_FD_PREFIX);
+ p = inttostr (fd, intbuf, sizeof (intbuf));
+ strcpy (ret + sizeof (DEV_FD_PREFIX) - 1, p);
+
+ add_fifo_list (fd);
+ return (ret);
+}
+
+#endif /* HAVE_DEV_FD */
+
+/* Return a filename that will open a connection to the process defined by
+ executing STRING. HAVE_DEV_FD, if defined, means open a pipe and return
+ a filename in /dev/fd corresponding to a descriptor that is one of the
+ ends of the pipe. If not defined, we use named pipes on systems that have
+ them. Systems without /dev/fd and named pipes are out of luck.
+
+ OPEN_FOR_READ_IN_CHILD, if 1, means open the named pipe for reading or
+ use the read end of the pipe and dup that file descriptor to fd 0 in
+ the child. If OPEN_FOR_READ_IN_CHILD is 0, we open the named pipe for
+ writing or use the write end of the pipe in the child, and dup that
+ file descriptor to fd 1 in the child. The parent does the opposite. */
+
+static char *
+process_substitute (string, open_for_read_in_child)
+ char *string;
+ int open_for_read_in_child;
+{
+ char *pathname;
+ int fd, result;
+ pid_t old_pid, pid;
+#if defined (HAVE_DEV_FD)
+ int parent_pipe_fd, child_pipe_fd;
+ int fildes[2];
+#endif /* HAVE_DEV_FD */
+#if defined (JOB_CONTROL)
+ pid_t old_pipeline_pgrp;
+#endif
+
+ if (!string || !*string || wordexp_only)
+ return ((char *)NULL);
+
+#if !defined (HAVE_DEV_FD)
+ pathname = make_named_pipe ();
+#else /* HAVE_DEV_FD */
+ if (pipe (fildes) < 0)
+ {
+ sys_error (_("cannot make pipe for process substitution"));
+ return ((char *)NULL);
+ }
+ /* If OPEN_FOR_READ_IN_CHILD == 1, we want to use the write end of
+ the pipe in the parent, otherwise the read end. */
+ parent_pipe_fd = fildes[open_for_read_in_child];
+ child_pipe_fd = fildes[1 - open_for_read_in_child];
+ /* Move the parent end of the pipe to some high file descriptor, to
+ avoid clashes with FDs used by the script. */
+ parent_pipe_fd = move_to_high_fd (parent_pipe_fd, 1, 64);
+
+ pathname = make_dev_fd_filename (parent_pipe_fd);
+#endif /* HAVE_DEV_FD */
+
+ if (pathname == 0)
+ {
+ sys_error (_("cannot make pipe for process substitution"));
+ return ((char *)NULL);
+ }
+
+ old_pid = last_made_pid;
+
+#if defined (JOB_CONTROL)
+ old_pipeline_pgrp = pipeline_pgrp;
+ pipeline_pgrp = shell_pgrp;
+ save_pipeline (1);
+#endif /* JOB_CONTROL */
+
+ pid = make_child ((char *)NULL, 1);
+ if (pid == 0)
+ {
+ reset_terminating_signals (); /* XXX */
+ free_pushed_string_input ();
+ /* Cancel traps, in trap.c. */
+ restore_original_signals ();
+ setup_async_signals ();
+ subshell_environment |= SUBSHELL_COMSUB|SUBSHELL_PROCSUB;
+ }
+
+#if defined (JOB_CONTROL)
+ set_sigchld_handler ();
+ stop_making_children ();
+ /* XXX - should we only do this in the parent? (as in command subst) */
+ pipeline_pgrp = old_pipeline_pgrp;
+#endif /* JOB_CONTROL */
+
+ if (pid < 0)
+ {
+ sys_error (_("cannot make child for process substitution"));
+ free (pathname);
+#if defined (HAVE_DEV_FD)
+ close (parent_pipe_fd);
+ close (child_pipe_fd);
+#endif /* HAVE_DEV_FD */
+ return ((char *)NULL);
+ }
+
+ if (pid > 0)
+ {
+#if defined (JOB_CONTROL)
+ restore_pipeline (1);
+#endif
+
+#if !defined (HAVE_DEV_FD)
+ fifo_list[nfifo-1].proc = pid;
+#endif
+
+ last_made_pid = old_pid;
+
+#if defined (JOB_CONTROL) && defined (PGRP_PIPE)
+ close_pgrp_pipe ();
+#endif /* JOB_CONTROL && PGRP_PIPE */
+
+#if defined (HAVE_DEV_FD)
+ close (child_pipe_fd);
+#endif /* HAVE_DEV_FD */
+
+ return (pathname);
+ }
+
+ set_sigint_handler ();
+
+#if defined (JOB_CONTROL)
+ set_job_control (0);
+#endif /* JOB_CONTROL */
+
+#if !defined (HAVE_DEV_FD)
+ /* Open the named pipe in the child. */
+ fd = open (pathname, open_for_read_in_child ? O_RDONLY|O_NONBLOCK : O_WRONLY);
+ if (fd < 0)
+ {
+ /* Two separate strings for ease of translation. */
+ if (open_for_read_in_child)
+ sys_error (_("cannot open named pipe %s for reading"), pathname);
+ else
+ sys_error (_("cannot open named pipe %s for writing"), pathname);
+
+ exit (127);
+ }
+ if (open_for_read_in_child)
+ {
+ if (sh_unset_nodelay_mode (fd) < 0)
+ {
+ sys_error (_("cannot reset nodelay mode for fd %d"), fd);
+ exit (127);
+ }
+ }
+#else /* HAVE_DEV_FD */
+ fd = child_pipe_fd;
+#endif /* HAVE_DEV_FD */
+
+ if (dup2 (fd, open_for_read_in_child ? 0 : 1) < 0)
+ {
+ sys_error (_("cannot duplicate named pipe %s as fd %d"), pathname,
+ open_for_read_in_child ? 0 : 1);
+ exit (127);
+ }
+
+ if (fd != (open_for_read_in_child ? 0 : 1))
+ close (fd);
+
+ /* Need to close any files that this process has open to pipes inherited
+ from its parent. */
+ if (current_fds_to_close)
+ {
+ close_fd_bitmap (current_fds_to_close);
+ current_fds_to_close = (struct fd_bitmap *)NULL;
+ }
+
+#if defined (HAVE_DEV_FD)
+ /* Make sure we close the parent's end of the pipe and clear the slot
+ in the fd list so it is not closed later, if reallocated by, for
+ instance, pipe(2). */
+ close (parent_pipe_fd);
+ dev_fd_list[parent_pipe_fd] = 0;
+#endif /* HAVE_DEV_FD */
+
+ result = parse_and_execute (string, "process substitution", (SEVAL_NONINT|SEVAL_NOHIST));
+
+#if !defined (HAVE_DEV_FD)
+ /* Make sure we close the named pipe in the child before we exit. */
+ close (open_for_read_in_child ? 0 : 1);
+#endif /* !HAVE_DEV_FD */
+
+ exit (result);
+ /*NOTREACHED*/
+}
+#endif /* PROCESS_SUBSTITUTION */
+
+/***********************************/
+/* */
+/* Command Substitution */
+/* */
+/***********************************/
+
+static char *
+read_comsub (fd, quoted, rflag)
+ int fd, quoted;
+ int *rflag;
+{
+ char *istring, buf[128], *bufp, *s;
+ int istring_index, istring_size, c, tflag, skip_ctlesc, skip_ctlnul;
+ ssize_t bufn;
+
+ istring = (char *)NULL;
+ istring_index = istring_size = bufn = tflag = 0;
+
+ for (skip_ctlesc = skip_ctlnul = 0, s = ifs_value; s && *s; s++)
+ skip_ctlesc |= *s == CTLESC, skip_ctlnul |= *s == CTLNUL;
+
+#ifdef __CYGWIN__
+ setmode (fd, O_TEXT); /* we don't want CR/LF, we want Unix-style */
+#endif
+
+ /* Read the output of the command through the pipe. This may need to be
+ changed to understand multibyte characters in the future. */
+ while (1)
+ {
+ if (fd < 0)
+ break;
+ if (--bufn <= 0)
+ {
+ bufn = zread (fd, buf, sizeof (buf));
+ if (bufn <= 0)
+ break;
+ bufp = buf;
+ }
+ c = *bufp++;
+
+ if (c == 0)
+ {
+#if 0
+ internal_warning ("read_comsub: ignored null byte in input");
+#endif
+ continue;
+ }
+
+ /* Add the character to ISTRING, possibly after resizing it. */
+ RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size, DEFAULT_ARRAY_SIZE);
+
+ /* This is essentially quote_string inline */
+ if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) /* || c == CTLESC || c == CTLNUL */)
+ istring[istring_index++] = CTLESC;
+ /* Escape CTLESC and CTLNUL in the output to protect those characters
+ from the rest of the word expansions (word splitting and globbing.)
+ This is essentially quote_escapes inline. */
+ else if (skip_ctlesc == 0 && c == CTLESC)
+ {
+ tflag |= W_HASCTLESC;
+ istring[istring_index++] = CTLESC;
+ }
+ else if ((skip_ctlnul == 0 && c == CTLNUL) || (c == ' ' && (ifs_value && *ifs_value == 0)))
+ istring[istring_index++] = CTLESC;
+
+ istring[istring_index++] = c;
+
+#if 0
+#if defined (__CYGWIN__)
+ if (c == '\n' && istring_index > 1 && istring[istring_index - 2] == '\r')
+ {
+ istring_index--;
+ istring[istring_index - 1] = '\n';
+ }
+#endif
+#endif
+ }
+
+ if (istring)
+ istring[istring_index] = '\0';
+
+ /* If we read no output, just return now and save ourselves some
+ trouble. */
+ if (istring_index == 0)
+ {
+ FREE (istring);
+ if (rflag)
+ *rflag = tflag;
+ return (char *)NULL;
+ }
+
+ /* Strip trailing newlines from the output of the command. */
+ if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+ {
+ while (istring_index > 0)
+ {
+ if (istring[istring_index - 1] == '\n')
+ {
+ --istring_index;
+
+ /* If the newline was quoted, remove the quoting char. */
+ if (istring[istring_index - 1] == CTLESC)
+ --istring_index;
+ }
+ else
+ break;
+ }
+ istring[istring_index] = '\0';
+ }
+ else
+ strip_trailing (istring, istring_index - 1, 1);
+
+ if (rflag)
+ *rflag = tflag;
+ return istring;
+}
+
+/* Perform command substitution on STRING. This returns a WORD_DESC * with the
+ contained string possibly quoted. */
+WORD_DESC *
+command_substitute (string, quoted)
+ char *string;
+ int quoted;
+{
+ pid_t pid, old_pid, old_pipeline_pgrp, old_async_pid;
+ char *istring;
+ int result, fildes[2], function_value, pflags, rc, tflag;
+ WORD_DESC *ret;
+
+ istring = (char *)NULL;
+
+ /* Don't fork () if there is no need to. In the case of no command to
+ run, just return NULL. */
+ if (!string || !*string || (string[0] == '\n' && !string[1]))
+ return ((WORD_DESC *)NULL);
+
+ if (wordexp_only && read_but_dont_execute)
+ {
+ last_command_exit_value = EX_WEXPCOMSUB;
+ jump_to_top_level (EXITPROG);
+ }
+
+ /* We're making the assumption here that the command substitution will
+ eventually run a command from the file system. Since we'll run
+ maybe_make_export_env in this subshell before executing that command,
+ the parent shell and any other shells it starts will have to remake
+ the environment. If we make it before we fork, other shells won't
+ have to. Don't bother if we have any temporary variable assignments,
+ though, because the export environment will be remade after this
+ command completes anyway, but do it if all the words to be expanded
+ are variable assignments. */
+ if (subst_assign_varlist == 0 || garglist == 0)
+ maybe_make_export_env (); /* XXX */
+
+ /* Flags to pass to parse_and_execute() */
+ pflags = (interactive && sourcelevel == 0) ? SEVAL_RESETLINE : 0;
+
+ /* Pipe the output of executing STRING into the current shell. */
+ if (pipe (fildes) < 0)
+ {
+ sys_error (_("cannot make pipe for command substitution"));
+ goto error_exit;
+ }
+
+ old_pid = last_made_pid;
+#if defined (JOB_CONTROL)
+ old_pipeline_pgrp = pipeline_pgrp;
+ /* Don't reset the pipeline pgrp if we're already a subshell in a pipeline. */
+ if ((subshell_environment & SUBSHELL_PIPE) == 0)
+ pipeline_pgrp = shell_pgrp;
+ cleanup_the_pipeline ();
+#endif /* JOB_CONTROL */
+
+ old_async_pid = last_asynchronous_pid;
+ pid = make_child ((char *)NULL, subshell_environment&SUBSHELL_ASYNC);
+ last_asynchronous_pid = old_async_pid;
+
+ if (pid == 0)
+ /* Reset the signal handlers in the child, but don't free the
+ trap strings. */
+ reset_signal_handlers ();
+
+#if defined (JOB_CONTROL)
+ /* XXX DO THIS ONLY IN PARENT ? XXX */
+ set_sigchld_handler ();
+ stop_making_children ();
+ if (pid != 0)
+ pipeline_pgrp = old_pipeline_pgrp;
+#else
+ stop_making_children ();
+#endif /* JOB_CONTROL */
+
+ if (pid < 0)
+ {
+ sys_error (_("cannot make child for command substitution"));
+ error_exit:
+
+ FREE (istring);
+ close (fildes[0]);
+ close (fildes[1]);
+ return ((WORD_DESC *)NULL);
+ }
+
+ if (pid == 0)
+ {
+ set_sigint_handler (); /* XXX */
+
+ free_pushed_string_input ();
+
+ if (dup2 (fildes[1], 1) < 0)
+ {
+ sys_error (_("command_substitute: cannot duplicate pipe as fd 1"));
+ exit (EXECUTION_FAILURE);
+ }
+
+ /* If standard output is closed in the parent shell
+ (such as after `exec >&-'), file descriptor 1 will be
+ the lowest available file descriptor, and end up in
+ fildes[0]. This can happen for stdin and stderr as well,
+ but stdout is more important -- it will cause no output
+ to be generated from this command. */
+ if ((fildes[1] != fileno (stdin)) &&
+ (fildes[1] != fileno (stdout)) &&
+ (fildes[1] != fileno (stderr)))
+ close (fildes[1]);
+
+ if ((fildes[0] != fileno (stdin)) &&
+ (fildes[0] != fileno (stdout)) &&
+ (fildes[0] != fileno (stderr)))
+ close (fildes[0]);
+
+ /* The currently executing shell is not interactive. */
+ interactive = 0;
+
+ /* This is a subshell environment. */
+ subshell_environment |= SUBSHELL_COMSUB;
+
+ /* When not in POSIX mode, command substitution does not inherit
+ the -e flag. */
+ if (posixly_correct == 0)
+ exit_immediately_on_error = 0;
+
+ remove_quoted_escapes (string);
+
+ startup_state = 2; /* see if we can avoid a fork */
+ /* Give command substitution a place to jump back to on failure,
+ so we don't go back up to main (). */
+ result = setjmp (top_level);
+
+ /* If we're running a command substitution inside a shell function,
+ trap `return' so we don't return from the function in the subshell
+ and go off to never-never land. */
+ if (result == 0 && return_catch_flag)
+ function_value = setjmp (return_catch);
+ else
+ function_value = 0;
+
+ if (result == ERREXIT)
+ rc = last_command_exit_value;
+ else if (result == EXITPROG)
+ rc = last_command_exit_value;
+ else if (result)
+ rc = EXECUTION_FAILURE;
+ else if (function_value)
+ rc = return_catch_value;
+ else
+ {
+ subshell_level++;
+ rc = parse_and_execute (string, "command substitution", pflags|SEVAL_NOHIST);
+ subshell_level--;
+ }
+
+ last_command_exit_value = rc;
+ rc = run_exit_trap ();
+#if defined (PROCESS_SUBSTITUTION)
+ unlink_fifo_list ();
+#endif
+ exit (rc);
+ }
+ else
+ {
+#if defined (JOB_CONTROL) && defined (PGRP_PIPE)
+ close_pgrp_pipe ();
+#endif /* JOB_CONTROL && PGRP_PIPE */
+
+ close (fildes[1]);
+
+ tflag = 0;
+ istring = read_comsub (fildes[0], quoted, &tflag);
+
+ close (fildes[0]);
+
+ current_command_subst_pid = pid;
+ last_command_exit_value = wait_for (pid);
+ last_command_subst_pid = pid;
+ last_made_pid = old_pid;
+
+#if defined (JOB_CONTROL)
+ /* If last_command_exit_value > 128, then the substituted command
+ was terminated by a signal. If that signal was SIGINT, then send
+ SIGINT to ourselves. This will break out of loops, for instance. */
+ if (last_command_exit_value == (128 + SIGINT) && last_command_exit_signal == SIGINT)
+ kill (getpid (), SIGINT);
+
+ /* wait_for gives the terminal back to shell_pgrp. If some other
+ process group should have it, give it away to that group here.
+ pipeline_pgrp is non-zero only while we are constructing a
+ pipline, so what we are concerned about is whether or not that
+ pipeline was started in the background. A pipeline started in
+ the background should never get the tty back here. */
+#if 0
+ if (interactive && pipeline_pgrp != (pid_t)0 && pipeline_pgrp != last_asynchronous_pid)
+#else
+ if (interactive && pipeline_pgrp != (pid_t)0 && (subshell_environment & SUBSHELL_ASYNC) == 0)
+#endif
+ give_terminal_to (pipeline_pgrp, 0);
+#endif /* JOB_CONTROL */
+
+ ret = alloc_word_desc ();
+ ret->word = istring;
+ ret->flags = tflag;
+
+ return ret;
+ }
+}
+
+/********************************************************
+ * *
+ * Utility functions for parameter expansion *
+ * *
+ ********************************************************/
+
+#if defined (ARRAY_VARS)
+
+static arrayind_t
+array_length_reference (s)
+ char *s;
+{
+ int len;
+ arrayind_t ind;
+ char *akey;
+ char *t, c;
+ ARRAY *array;
+ HASH_TABLE *h;
+ SHELL_VAR *var;
+
+ var = array_variable_part (s, &t, &len);
+
+ /* If unbound variables should generate an error, report one and return
+ failure. */
+ if ((var == 0 || (assoc_p (var) == 0 && array_p (var) == 0)) && unbound_vars_is_error)
+ {
+ c = *--t;
+ *t = '\0';
+ last_command_exit_value = EXECUTION_FAILURE;
+ err_unboundvar (s);
+ *t = c;
+ return (-1);
+ }
+ else if (var == 0)
+ return 0;
+
+ /* We support a couple of expansions for variables that are not arrays.
+ We'll return the length of the value for v[0], and 1 for v[@] or
+ v[*]. Return 0 for everything else. */
+
+ array = array_p (var) ? array_cell (var) : (ARRAY *)NULL;
+ h = assoc_p (var) ? assoc_cell (var) : (HASH_TABLE *)NULL;
+
+ if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']')
+ {
+ if (assoc_p (var))
+ return (h ? assoc_num_elements (h) : 0);
+ else if (array_p (var))
+ return (array ? array_num_elements (array) : 0);
+ else
+ return (value_cell (var) ? 1 : 0);
+ }
+
+ if (assoc_p (var))
+ {
+ t[len - 1] = '\0';
+ akey = expand_assignment_string_to_string (t, 0); /* [ */
+ t[len - 1] = ']';
+ if (akey == 0 || *akey == 0)
+ {
+ err_badarraysub (t);
+ return (-1);
+ }
+ t = assoc_reference (assoc_cell (var), akey);
+ }
+ else
+ {
+ ind = array_expand_index (t, len);
+ if (ind < 0)
+ {
+ err_badarraysub (t);
+ return (-1);
+ }
+ if (array_p (var))
+ t = array_reference (array, ind);
+ else
+ t = (ind == 0) ? value_cell (var) : (char *)NULL;
+ }
+
+ len = MB_STRLEN (t);
+ return (len);
+}
+#endif /* ARRAY_VARS */
+
+static int
+valid_brace_expansion_word (name, var_is_special)
+ char *name;
+ int var_is_special;
+{
+ if (DIGIT (*name) && all_digits (name))
+ return 1;
+ else if (var_is_special)
+ return 1;
+#if defined (ARRAY_VARS)
+ else if (valid_array_reference (name))
+ return 1;
+#endif /* ARRAY_VARS */
+ else if (legal_identifier (name))
+ return 1;
+ else
+ return 0;
+}
+
+static int
+chk_atstar (name, quoted, quoted_dollar_atp, contains_dollar_at)
+ char *name;
+ int quoted;
+ int *quoted_dollar_atp, *contains_dollar_at;
+{
+ char *temp1;
+
+ if (name == 0)
+ {
+ if (quoted_dollar_atp)
+ *quoted_dollar_atp = 0;
+ if (contains_dollar_at)
+ *contains_dollar_at = 0;
+ return 0;
+ }
+
+ /* check for $@ and $* */
+ if (name[0] == '@' && name[1] == 0)
+ {
+ if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
+ *quoted_dollar_atp = 1;
+ if (contains_dollar_at)
+ *contains_dollar_at = 1;
+ return 1;
+ }
+ else if (name[0] == '*' && name[1] == '\0' && quoted == 0)
+ {
+ if (contains_dollar_at)
+ *contains_dollar_at = 1;
+ return 1;
+ }
+
+ /* Now check for ${array[@]} and ${array[*]} */
+#if defined (ARRAY_VARS)
+ else if (valid_array_reference (name))
+ {
+ temp1 = mbschr (name, '[');
+ if (temp1 && temp1[1] == '@' && temp1[2] == ']')
+ {
+ if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
+ *quoted_dollar_atp = 1;
+ if (contains_dollar_at)
+ *contains_dollar_at = 1;
+ return 1;
+ } /* [ */
+ /* ${array[*]}, when unquoted, should be treated like ${array[@]},
+ which should result in separate words even when IFS is unset. */
+ if (temp1 && temp1[1] == '*' && temp1[2] == ']' && quoted == 0)
+ {
+ if (contains_dollar_at)
+ *contains_dollar_at = 1;
+ return 1;
+ }
+ }
+#endif
+ return 0;
+}
+
+/* Parameter expand NAME, and return a new string which is the expansion,
+ or NULL if there was no expansion.
+ VAR_IS_SPECIAL is non-zero if NAME is one of the special variables in
+ the shell, e.g., "@", "$", "*", etc. QUOTED, if non-zero, means that
+ NAME was found inside of a double-quoted expression. */
+static WORD_DESC *
+parameter_brace_expand_word (name, var_is_special, quoted, pflags)
+ char *name;
+ int var_is_special, quoted, pflags;
+{
+ WORD_DESC *ret;
+ char *temp, *tt;
+ intmax_t arg_index;
+ SHELL_VAR *var;
+ int atype, rflags;
+
+ ret = 0;
+ temp = 0;
+ rflags = 0;
+
+ /* Handle multiple digit arguments, as in ${11}. */
+ if (legal_number (name, &arg_index))
+ {
+ tt = get_dollar_var_value (arg_index);
+ if (tt)
+ temp = (*tt && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+ ? quote_string (tt)
+ : quote_escapes (tt);
+ else
+ temp = (char *)NULL;
+ FREE (tt);
+ }
+ else if (var_is_special) /* ${@} */
+ {
+ int sindex;
+ tt = (char *)xmalloc (2 + strlen (name));
+ tt[sindex = 0] = '$';
+ strcpy (tt + 1, name);
+
+ ret = param_expand (tt, &sindex, quoted, (int *)NULL, (int *)NULL,
+ (int *)NULL, (int *)NULL, pflags);
+ free (tt);
+ }
+#if defined (ARRAY_VARS)
+ else if (valid_array_reference (name))
+ {
+ temp = array_value (name, quoted, &atype, (arrayind_t *)NULL);
+ if (atype == 0 && temp)
+ temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+ ? quote_string (temp)
+ : quote_escapes (temp);
+ else if (atype == 1 && temp && QUOTED_NULL (temp) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+ rflags |= W_HASQUOTEDNULL;
+ }
+#endif
+ else if (var = find_variable (name))
+ {
+ if (var_isset (var) && invisible_p (var) == 0)
+ {
+#if defined (ARRAY_VARS)
+ if (assoc_p (var))
+ temp = assoc_reference (assoc_cell (var), "0");
+ else if (array_p (var))
+ temp = array_reference (array_cell (var), 0);
+ else
+ temp = value_cell (var);
+#else
+ temp = value_cell (var);
+#endif
+
+ if (temp)
+ temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+ ? quote_string (temp)
+ : quote_escapes (temp);
+ }
+ else
+ temp = (char *)NULL;
+ }
+ else
+ temp = (char *)NULL;
+
+ if (ret == 0)
+ {
+ ret = alloc_word_desc ();
+ ret->word = temp;
+ ret->flags |= rflags;
+ }
+ return ret;
+}
+
+/* Expand an indirect reference to a variable: ${!NAME} expands to the
+ value of the variable whose name is the value of NAME. */
+static WORD_DESC *
+parameter_brace_expand_indir (name, var_is_special, quoted, quoted_dollar_atp, contains_dollar_at)
+ char *name;
+ int var_is_special, quoted;
+ int *quoted_dollar_atp, *contains_dollar_at;
+{
+ char *temp, *t;
+ WORD_DESC *w;
+
+ w = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND);
+ t = w->word;
+ /* Have to dequote here if necessary */
+ if (t)
+ {
+ temp = (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))
+ ? dequote_string (t)
+ : dequote_escapes (t);
+ free (t);
+ t = temp;
+ }
+ dispose_word_desc (w);
+
+ chk_atstar (t, quoted, quoted_dollar_atp, contains_dollar_at);
+ if (t == 0)
+ return (WORD_DESC *)NULL;
+
+ w = parameter_brace_expand_word (t, SPECIAL_VAR(t, 0), quoted, 0);
+ free (t);
+
+ return w;
+}
+
+/* Expand the right side of a parameter expansion of the form ${NAMEcVALUE},
+ depending on the value of C, the separating character. C can be one of
+ "-", "+", or "=". QUOTED is true if the entire brace expression occurs
+ between double quotes. */
+static WORD_DESC *
+parameter_brace_expand_rhs (name, value, c, quoted, qdollaratp, hasdollarat)
+ char *name, *value;
+ int c, quoted, *qdollaratp, *hasdollarat;
+{
+ WORD_DESC *w;
+ WORD_LIST *l;
+ char *t, *t1, *temp;
+ int hasdol;
+
+ /* If the entire expression is between double quotes, we want to treat
+ the value as a double-quoted string, with the exception that we strip
+ embedded unescaped double quotes (for sh backwards compatibility). */
+ if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && *value)
+ {
+ hasdol = 0;
+ temp = string_extract_double_quoted (value, &hasdol, 1);
+ }
+ else
+ temp = value;
+
+ w = alloc_word_desc ();
+ hasdol = 0;
+ /* XXX was 0 not quoted */
+ l = *temp ? expand_string_for_rhs (temp, quoted, &hasdol, (int *)NULL)
+ : (WORD_LIST *)0;
+ if (hasdollarat)
+ *hasdollarat = hasdol || (l && l->next);
+ if (temp != value)
+ free (temp);
+ if (l)
+ {
+ /* The expansion of TEMP returned something. We need to treat things
+ slightly differently if HASDOL is non-zero. If we have "$@", the
+ individual words have already been quoted. We need to turn them
+ into a string with the words separated by the first character of
+ $IFS without any additional quoting, so string_list_dollar_at won't
+ do the right thing. We use string_list_dollar_star instead. */
+ temp = (hasdol || l->next) ? string_list_dollar_star (l) : string_list (l);
+
+ /* If l->next is not null, we know that TEMP contained "$@", since that
+ is the only expansion that creates more than one word. */
+ if (qdollaratp && ((hasdol && quoted) || l->next))
+ *qdollaratp = 1;
+ dispose_words (l);
+ }
+ else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && hasdol)
+ {
+ /* The brace expansion occurred between double quotes and there was
+ a $@ in TEMP. It does not matter if the $@ is quoted, as long as
+ it does not expand to anything. In this case, we want to return
+ a quoted empty string. */
+ temp = make_quoted_char ('\0');
+ w->flags |= W_HASQUOTEDNULL;
+ }
+ else
+ temp = (char *)NULL;
+
+ if (c == '-' || c == '+')
+ {
+ w->word = temp;
+ return w;
+ }
+
+ /* c == '=' */
+ t = temp ? savestring (temp) : savestring ("");
+ t1 = dequote_string (t);
+ free (t);
+#if defined (ARRAY_VARS)
+ if (valid_array_reference (name))
+ assign_array_element (name, t1, 0);
+ else
+#endif /* ARRAY_VARS */
+ bind_variable (name, t1, 0);
+ free (t1);
+
+ w->word = temp;
+ return w;
+}
+
+/* Deal with the right hand side of a ${name:?value} expansion in the case
+ that NAME is null or not set. If VALUE is non-null it is expanded and
+ used as the error message to print, otherwise a standard message is
+ printed. */
+static void
+parameter_brace_expand_error (name, value)
+ char *name, *value;
+{
+ WORD_LIST *l;
+ char *temp;
+
+ if (value && *value)
+ {
+ l = expand_string (value, 0);
+ temp = string_list (l);
+ report_error ("%s: %s", name, temp ? temp : ""); /* XXX was value not "" */
+ FREE (temp);
+ dispose_words (l);
+ }
+ else
+ report_error (_("%s: parameter null or not set"), name);
+
+ /* Free the data we have allocated during this expansion, since we
+ are about to longjmp out. */
+ free (name);
+ FREE (value);
+}
+
+/* Return 1 if NAME is something for which parameter_brace_expand_length is
+ OK to do. */
+static int
+valid_length_expression (name)
+ char *name;
+{
+ return (name[1] == '\0' || /* ${#} */
+ ((sh_syntaxtab[(unsigned char) name[1]] & CSPECVAR) && name[2] == '\0') || /* special param */
+ (DIGIT (name[1]) && all_digits (name + 1)) || /* ${#11} */
+#if defined (ARRAY_VARS)
+ valid_array_reference (name + 1) || /* ${#a[7]} */
+#endif
+ legal_identifier (name + 1)); /* ${#PS1} */
+}
+
+#if defined (HANDLE_MULTIBYTE)
+size_t
+mbstrlen (s)
+ const char *s;
+{
+ size_t clen, nc;
+ mbstate_t mbs, mbsbak;
+
+ nc = 0;
+ memset (&mbs, 0, sizeof (mbs));
+ mbsbak = mbs;
+ while ((clen = mbrlen(s, MB_CUR_MAX, &mbs)) != 0)
+ {
+ if (MB_INVALIDCH(clen))
+ {
+ clen = 1; /* assume single byte */
+ mbs = mbsbak;
+ }
+
+ s += clen;
+ nc++;
+ mbsbak = mbs;
+ }
+ return nc;
+}
+#endif
+
+
+/* Handle the parameter brace expansion that requires us to return the
+ length of a parameter. */
+static intmax_t
+parameter_brace_expand_length (name)
+ char *name;
+{
+ char *t, *newname;
+ intmax_t number, arg_index;
+ WORD_LIST *list;
+#if defined (ARRAY_VARS)
+ SHELL_VAR *var;
+#endif
+
+ if (name[1] == '\0') /* ${#} */
+ number = number_of_args ();
+ else if ((name[1] == '@' || name[1] == '*') && name[2] == '\0') /* ${#@}, ${#*} */
+ number = number_of_args ();
+ else if ((sh_syntaxtab[(unsigned char) name[1]] & CSPECVAR) && name[2] == '\0')
+ {
+ /* Take the lengths of some of the shell's special parameters. */
+ switch (name[1])
+ {
+ case '-':
+ t = which_set_flags ();
+ break;
+ case '?':
+ t = itos (last_command_exit_value);
+ break;
+ case '$':
+ t = itos (dollar_dollar_pid);
+ break;
+ case '!':
+ if (last_asynchronous_pid == NO_PID)
+ t = (char *)NULL;
+ else
+ t = itos (last_asynchronous_pid);
+ break;
+ case '#':
+ t = itos (number_of_args ());
+ break;
+ }
+ number = STRLEN (t);
+ FREE (t);
+ }
+#if defined (ARRAY_VARS)
+ else if (valid_array_reference (name + 1))
+ number = array_length_reference (name + 1);
+#endif /* ARRAY_VARS */
+ else
+ {
+ number = 0;
+
+ if (legal_number (name + 1, &arg_index)) /* ${#1} */
+ {
+ t = get_dollar_var_value (arg_index);
+ number = MB_STRLEN (t);
+ FREE (t);
+ }
+#if defined (ARRAY_VARS)
+ else if ((var = find_variable (name + 1)) && (invisible_p (var) == 0) && (array_p (var) || assoc_p (var)))
+ {
+ if (assoc_p (var))
+ t = assoc_reference (assoc_cell (var), "0");
+ else
+ t = array_reference (array_cell (var), 0);
+ number = MB_STRLEN (t);
+ }
+#endif
+ else /* ${#PS1} */
+ {
+ newname = savestring (name);
+ newname[0] = '$';
+ list = expand_string (newname, Q_DOUBLE_QUOTES);
+ t = list ? string_list (list) : (char *)NULL;
+ free (newname);
+ if (list)
+ dispose_words (list);
+
+ number = MB_STRLEN (t);
+ FREE (t);
+ }
+ }
+
+ return (number);
+}
+
+/* Skip characters in SUBSTR until DELIM. SUBSTR is an arithmetic expression,
+ so we do some ad-hoc parsing of an arithmetic expression to find
+ the first DELIM, instead of using strchr(3). Two rules:
+ 1. If the substring contains a `(', read until closing `)'.
+ 2. If the substring contains a `?', read past one `:' for each `?'.
+*/
+
+static char *
+skiparith (substr, delim)
+ char *substr;
+ int delim;
+{
+ size_t sublen;
+ int skipcol, pcount, i;
+ DECLARE_MBSTATE;
+
+ sublen = strlen (substr);
+ i = skipcol = pcount = 0;
+ while (substr[i])
+ {
+ /* Balance parens */
+ if (substr[i] == LPAREN)
+ {
+ pcount++;
+ i++;
+ continue;
+ }
+ if (substr[i] == RPAREN && pcount)
+ {
+ pcount--;
+ i++;
+ continue;
+ }
+ if (pcount)
+ {
+ ADVANCE_CHAR (substr, sublen, i);
+ continue;
+ }
+
+ /* Skip one `:' for each `?' */
+ if (substr[i] == ':' && skipcol)
+ {
+ skipcol--;
+ i++;
+ continue;
+ }
+ if (substr[i] == delim)
+ break;
+ if (substr[i] == '?')
+ {
+ skipcol++;
+ i++;
+ continue;
+ }
+ ADVANCE_CHAR (substr, sublen, i);
+ }
+
+ return (substr + i);
+}
+
+/* Verify and limit the start and end of the desired substring. If
+ VTYPE == 0, a regular shell variable is being used; if it is 1,
+ then the positional parameters are being used; if it is 2, then
+ VALUE is really a pointer to an array variable that should be used.
+ Return value is 1 if both values were OK, 0 if there was a problem
+ with an invalid expression, or -1 if the values were out of range. */
+static int
+verify_substring_values (v, value, substr, vtype, e1p, e2p)
+ SHELL_VAR *v;
+ char *value, *substr;
+ int vtype;
+ intmax_t *e1p, *e2p;
+{
+ char *t, *temp1, *temp2;
+ arrayind_t len;
+ int expok;
+#if defined (ARRAY_VARS)
+ ARRAY *a;
+ HASH_TABLE *h;
+#endif
+
+ /* duplicate behavior of strchr(3) */
+ t = skiparith (substr, ':');
+ if (*t && *t == ':')
+ *t = '\0';
+ else
+ t = (char *)0;
+
+ temp1 = expand_arith_string (substr, Q_DOUBLE_QUOTES);
+ *e1p = evalexp (temp1, &expok);
+ free (temp1);
+ if (expok == 0)
+ return (0);
+
+ len = -1; /* paranoia */
+ switch (vtype)
+ {
+ case VT_VARIABLE:
+ case VT_ARRAYMEMBER:
+ len = MB_STRLEN (value);
+ break;
+ case VT_POSPARMS:
+ len = number_of_args () + 1;
+ if (*e1p == 0)
+ len++; /* add one arg if counting from $0 */
+ break;
+#if defined (ARRAY_VARS)
+ case VT_ARRAYVAR:
+ /* For arrays, the first value deals with array indices. Negative
+ offsets count from one past the array's maximum index. Associative
+ arrays treat the number of elements as the maximum index. */
+ if (assoc_p (v))
+ {
+ h = assoc_cell (v);
+ len = assoc_num_elements (h) + (*e1p < 0);
+ }
+ else
+ {
+ a = (ARRAY *)value;
+ len = array_max_index (a) + (*e1p < 0); /* arrays index from 0 to n - 1 */
+ }
+ break;
+#endif
+ }
+
+ if (len == -1) /* paranoia */
+ return -1;
+
+ if (*e1p < 0) /* negative offsets count from end */
+ *e1p += len;
+
+ if (*e1p > len || *e1p < 0)
+ return (-1);
+
+#if defined (ARRAY_VARS)
+ /* For arrays, the second offset deals with the number of elements. */
+ if (vtype == VT_ARRAYVAR)
+ len = assoc_p (v) ? assoc_num_elements (h) : array_num_elements (a);
+#endif
+
+ if (t)
+ {
+ t++;
+ temp2 = savestring (t);
+ temp1 = expand_arith_string (temp2, Q_DOUBLE_QUOTES);
+ free (temp2);
+ t[-1] = ':';
+ *e2p = evalexp (temp1, &expok);
+ free (temp1);
+ if (expok == 0)
+ return (0);
+ if (*e2p < 0)
+ {
+ internal_error (_("%s: substring expression < 0"), t);
+ return (0);
+ }
+#if defined (ARRAY_VARS)
+ /* In order to deal with sparse arrays, push the intelligence about how
+ to deal with the number of elements desired down to the array-
+ specific functions. */
+ if (vtype != VT_ARRAYVAR)
+#endif
+ {
+ *e2p += *e1p; /* want E2 chars starting at E1 */
+ if (*e2p > len)
+ *e2p = len;
+ }
+ }
+ else
+ *e2p = len;
+
+ return (1);
+}
+
+/* Return the type of variable specified by VARNAME (simple variable,
+ positional param, or array variable). Also return the value specified
+ by VARNAME (value of a variable or a reference to an array element).
+ If this returns VT_VARIABLE, the caller assumes that CTLESC and CTLNUL
+ characters in the value are quoted with CTLESC and takes appropriate
+ steps. For convenience, *VALP is set to the dequoted VALUE. */
+static int
+get_var_and_type (varname, value, quoted, varp, valp)
+ char *varname, *value;
+ int quoted;
+ SHELL_VAR **varp;
+ char **valp;
+{
+ int vtype;
+ char *temp;
+#if defined (ARRAY_VARS)
+ SHELL_VAR *v;
+#endif
+
+ /* This sets vtype to VT_VARIABLE or VT_POSPARMS */
+ vtype = (varname[0] == '@' || varname[0] == '*') && varname[1] == '\0';
+ if (vtype == VT_POSPARMS && varname[0] == '*')
+ vtype |= VT_STARSUB;
+ *varp = (SHELL_VAR *)NULL;
+
+#if defined (ARRAY_VARS)
+ if (valid_array_reference (varname))
+ {
+ v = array_variable_part (varname, &temp, (int *)0);
+ if (v && (array_p (v) || assoc_p (v)))
+ { /* [ */
+ if (ALL_ELEMENT_SUB (temp[0]) && temp[1] == ']')
+ {
+ /* Callers have to differentiate betwen indexed and associative */
+ vtype = VT_ARRAYVAR;
+ if (temp[0] == '*')
+ vtype |= VT_STARSUB;
+ *valp = array_p (v) ? (char *)array_cell (v) : (char *)assoc_cell (v);
+ }
+ else
+ {
+ vtype = VT_ARRAYMEMBER;
+ *valp = array_value (varname, 1, (int *)NULL, (arrayind_t *)NULL);
+ }
+ *varp = v;
+ }
+ else if (v && (ALL_ELEMENT_SUB (temp[0]) && temp[1] == ']'))
+ {
+ vtype = VT_VARIABLE;
+ *varp = v;
+ if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))
+ *valp = dequote_string (value);
+ else
+ *valp = dequote_escapes (value);
+ }
+ else
+ {
+ vtype = VT_ARRAYMEMBER;
+ *varp = v;
+ *valp = array_value (varname, 1, (int *)NULL, (arrayind_t *)NULL);
+ }
+ }
+ else if ((v = find_variable (varname)) && (invisible_p (v) == 0) && (assoc_p (v) || array_p (v)))
+ {
+ vtype = VT_ARRAYMEMBER;
+ *varp = v;
+ *valp = assoc_p (v) ? assoc_reference (assoc_cell (v), "0") : array_reference (array_cell (v), 0);
+ }
+ else
+#endif
+ {
+ if (value && vtype == VT_VARIABLE)
+ {
+ if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))
+ *valp = dequote_string (value);
+ else
+ *valp = dequote_escapes (value);
+ }
+ else
+ *valp = value;
+ }
+
+ return vtype;
+}
+
+/******************************************************/
+/* */
+/* Functions to extract substrings of variable values */
+/* */
+/******************************************************/
+
+#if defined (HANDLE_MULTIBYTE)
+/* Character-oriented rather than strictly byte-oriented substrings. S and
+ E, rather being strict indices into STRING, indicate character (possibly
+ multibyte character) positions that require calculation.
+ Used by the ${param:offset[:length]} expansion. */
+static char *
+mb_substring (string, s, e)
+ char *string;
+ int s, e;
+{
+ char *tt;
+ int start, stop, i, slen;
+ DECLARE_MBSTATE;
+
+ start = 0;
+ /* Don't need string length in ADVANCE_CHAR unless multibyte chars possible. */
+ slen = (MB_CUR_MAX > 1) ? STRLEN (string) : 0;
+
+ i = s;
+ while (string[start] && i--)
+ ADVANCE_CHAR (string, slen, start);
+ stop = start;
+ i = e - s;
+ while (string[stop] && i--)
+ ADVANCE_CHAR (string, slen, stop);
+ tt = substring (string, start, stop);
+ return tt;
+}
+#endif
+
+/* Process a variable substring expansion: ${name:e1[:e2]}. If VARNAME
+ is `@', use the positional parameters; otherwise, use the value of
+ VARNAME. If VARNAME is an array variable, use the array elements. */
+
+static char *
+parameter_brace_substring (varname, value, substr, quoted)
+ char *varname, *value, *substr;
+ int quoted;
+{
+ intmax_t e1, e2;
+ int vtype, r, starsub;
+ char *temp, *val, *tt, *oname;
+ SHELL_VAR *v;
+
+ if (value == 0)
+ return ((char *)NULL);
+
+ oname = this_command_name;
+ this_command_name = varname;
+
+ vtype = get_var_and_type (varname, value, quoted, &v, &val);
+ if (vtype == -1)
+ {
+ this_command_name = oname;
+ return ((char *)NULL);
+ }
+
+ starsub = vtype & VT_STARSUB;
+ vtype &= ~VT_STARSUB;
+
+ r = verify_substring_values (v, val, substr, vtype, &e1, &e2);
+ this_command_name = oname;
+ if (r <= 0)
+ return ((r == 0) ? &expand_param_error : (char *)NULL);
+
+ switch (vtype)
+ {
+ case VT_VARIABLE:
+ case VT_ARRAYMEMBER:
+#if defined (HANDLE_MULTIBYTE)
+ if (MB_CUR_MAX > 1)
+ tt = mb_substring (val, e1, e2);
+ else
+#endif
+ tt = substring (val, e1, e2);
+
+ if (vtype == VT_VARIABLE)
+ FREE (val);
+ if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))
+ temp = quote_string (tt);
+ else
+ temp = tt ? quote_escapes (tt) : (char *)NULL;
+ FREE (tt);
+ break;
+ case VT_POSPARMS:
+ tt = pos_params (varname, e1, e2, quoted);
+ if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0)
+ {
+ temp = tt ? quote_escapes (tt) : (char *)NULL;
+ FREE (tt);
+ }
+ else
+ temp = tt;
+ break;
+#if defined (ARRAY_VARS)
+ case VT_ARRAYVAR:
+ if (assoc_p (v))
+ /* we convert to list and take first e2 elements starting at e1th
+ element -- officially undefined for now */
+ temp = assoc_subrange (assoc_cell (v), e1, e2, starsub, quoted);
+ else
+ /* We want E2 to be the number of elements desired (arrays can be sparse,
+ so verify_substring_values just returns the numbers specified and we
+ rely on array_subrange to understand how to deal with them). */
+ temp = array_subrange (array_cell (v), e1, e2, starsub, quoted);
+ /* array_subrange now calls array_quote_escapes as appropriate, so the
+ caller no longer needs to. */
+ break;
+#endif
+ default:
+ temp = (char *)NULL;
+ }
+
+ return temp;
+}
+
+/****************************************************************/
+/* */
+/* Functions to perform pattern substitution on variable values */
+/* */
+/****************************************************************/
+
+char *
+pat_subst (string, pat, rep, mflags)
+ char *string, *pat, *rep;
+ int mflags;
+{
+ char *ret, *s, *e, *str;
+ int rsize, rptr, l, replen, mtype;
+
+ mtype = mflags & MATCH_TYPEMASK;
+
+ /* Special cases:
+ * 1. A null pattern with mtype == MATCH_BEG means to prefix STRING
+ * with REP and return the result.
+ * 2. A null pattern with mtype == MATCH_END means to append REP to
+ * STRING and return the result.
+ */
+ if ((pat == 0 || *pat == 0) && (mtype == MATCH_BEG || mtype == MATCH_END))
+ {
+ replen = STRLEN (rep);
+ l = strlen (string);
+ ret = (char *)xmalloc (replen + l + 2);
+ if (replen == 0)
+ strcpy (ret, string);
+ else if (mtype == MATCH_BEG)
+ {
+ strcpy (ret, rep);
+ strcpy (ret + replen, string);
+ }
+ else
+ {
+ strcpy (ret, string);
+ strcpy (ret + l, rep);
+ }
+ return (ret);
+ }
+
+ ret = (char *)xmalloc (rsize = 64);
+ ret[0] = '\0';
+
+ for (replen = STRLEN (rep), rptr = 0, str = string;;)
+ {
+ if (match_pattern (str, pat, mtype, &s, &e) == 0)
+ break;
+ l = s - str;
+ RESIZE_MALLOCED_BUFFER (ret, rptr, (l + replen), rsize, 64);
+
+ /* OK, now copy the leading unmatched portion of the string (from
+ str to s) to ret starting at rptr (the current offset). Then copy
+ the replacement string at ret + rptr + (s - str). Increment
+ rptr (if necessary) and str and go on. */
+ if (l)
+ {
+ strncpy (ret + rptr, str, l);
+ rptr += l;
+ }
+ if (replen)
+ {
+ strncpy (ret + rptr, rep, replen);
+ rptr += replen;
+ }
+ str = e; /* e == end of match */
+
+ if (((mflags & MATCH_GLOBREP) == 0) || mtype != MATCH_ANY)
+ break;
+
+ if (s == e)
+ {
+ /* On a zero-length match, make sure we copy one character, since
+ we increment one character to avoid infinite recursion. */
+ RESIZE_MALLOCED_BUFFER (ret, rptr, 1, rsize, 64);
+ ret[rptr++] = *str++;
+ e++; /* avoid infinite recursion on zero-length match */
+ }
+ }
+
+ /* Now copy the unmatched portion of the input string */
+ if (*str)
+ {
+ RESIZE_MALLOCED_BUFFER (ret, rptr, STRLEN(str) + 1, rsize, 64);
+ strcpy (ret + rptr, str);
+ }
+ else
+ ret[rptr] = '\0';
+
+ return ret;
+}
+
+/* Do pattern match and replacement on the positional parameters. */
+static char *
+pos_params_pat_subst (string, pat, rep, mflags)
+ char *string, *pat, *rep;
+ int mflags;
+{
+ WORD_LIST *save, *params;
+ WORD_DESC *w;
+ char *ret;
+ int pchar, qflags;
+
+ save = params = list_rest_of_args ();
+ if (save == 0)
+ return ((char *)NULL);
+
+ for ( ; params; params = params->next)
+ {
+ ret = pat_subst (params->word->word, pat, rep, mflags);
+ w = alloc_word_desc ();
+ w->word = ret ? ret : savestring ("");
+ dispose_word (params->word);
+ params->word = w;
+ }
+
+ pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@';
+ qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0;
+
+#if 0
+ if ((mflags & (MATCH_QUOTED|MATCH_STARSUB)) == (MATCH_QUOTED|MATCH_STARSUB))
+ ret = string_list_dollar_star (quote_list (save));
+ else if ((mflags & MATCH_STARSUB) == MATCH_STARSUB)
+ ret = string_list_dollar_star (save);
+ else if ((mflags & MATCH_QUOTED) == MATCH_QUOTED)
+ ret = string_list_dollar_at (save, qflags);
+ else
+ ret = string_list_dollar_star (save);
+#else
+ ret = string_list_pos_params (pchar, save, qflags);
+#endif
+
+ dispose_words (save);
+
+ return (ret);
+}
+
+/* Perform pattern substitution on VALUE, which is the expansion of
+ VARNAME. PATSUB is an expression supplying the pattern to match
+ and the string to substitute. QUOTED is a flags word containing
+ the type of quoting currently in effect. */
+static char *
+parameter_brace_patsub (varname, value, patsub, quoted)
+ char *varname, *value, *patsub;
+ int quoted;
+{
+ int vtype, mflags, starsub, delim;
+ char *val, *temp, *pat, *rep, *p, *lpatsub, *tt;
+ SHELL_VAR *v;
+
+ if (value == 0)
+ return ((char *)NULL);
+
+ this_command_name = varname;
+
+ vtype = get_var_and_type (varname, value, quoted, &v, &val);
+ if (vtype == -1)
+ return ((char *)NULL);
+
+ starsub = vtype & VT_STARSUB;
+ vtype &= ~VT_STARSUB;
+
+ mflags = 0;
+ if (patsub && *patsub == '/')
+ {
+ mflags |= MATCH_GLOBREP;
+ patsub++;
+ }
+
+ /* Malloc this because expand_string_if_necessary or one of the expansion
+ functions in its call chain may free it on a substitution error. */
+ lpatsub = savestring (patsub);
+
+ if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+ mflags |= MATCH_QUOTED;
+
+ if (starsub)
+ mflags |= MATCH_STARSUB;
+
+ /* If the pattern starts with a `/', make sure we skip over it when looking
+ for the replacement delimiter. */
+#if 0
+ if (rep = quoted_strchr ((*patsub == '/') ? lpatsub+1 : lpatsub, '/', ST_BACKSL))
+ *rep++ = '\0';
+ else
+ rep = (char *)NULL;
+#else
+ delim = skip_to_delim (lpatsub, ((*patsub == '/') ? 1 : 0), "/", 0);
+ if (lpatsub[delim] == '/')
+ {
+ lpatsub[delim] = 0;
+ rep = lpatsub + delim + 1;
+ }
+ else
+ rep = (char *)NULL;
+#endif
+
+ if (rep && *rep == '\0')
+ rep = (char *)NULL;
+
+ /* Perform the same expansions on the pattern as performed by the
+ pattern removal expansions. */
+ pat = getpattern (lpatsub, quoted, 1);
+
+ if (rep)
+ {
+ if ((mflags & MATCH_QUOTED) == 0)
+ rep = expand_string_if_necessary (rep, quoted, expand_string_unsplit);
+ else
+ rep = expand_string_to_string_internal (rep, quoted, expand_string_unsplit);
+ }
+
+ /* ksh93 doesn't allow the match specifier to be a part of the expanded
+ pattern. This is an extension. Make sure we don't anchor the pattern
+ at the beginning or end of the string if we're doing global replacement,
+ though. */
+ p = pat;
+ if (mflags & MATCH_GLOBREP)
+ mflags |= MATCH_ANY;
+ else if (pat && pat[0] == '#')
+ {
+ mflags |= MATCH_BEG;
+ p++;
+ }
+ else if (pat && pat[0] == '%')
+ {
+ mflags |= MATCH_END;
+ p++;
+ }
+ else
+ mflags |= MATCH_ANY;
+
+ /* OK, we now want to substitute REP for PAT in VAL. If
+ flags & MATCH_GLOBREP is non-zero, the substitution is done
+ everywhere, otherwise only the first occurrence of PAT is
+ replaced. The pattern matching code doesn't understand
+ CTLESC quoting CTLESC and CTLNUL so we use the dequoted variable
+ values passed in (VT_VARIABLE) so the pattern substitution
+ code works right. We need to requote special chars after
+ we're done for VT_VARIABLE and VT_ARRAYMEMBER, and for the
+ other cases if QUOTED == 0, since the posparams and arrays
+ indexed by * or @ do special things when QUOTED != 0. */
+
+ switch (vtype)
+ {
+ case VT_VARIABLE:
+ case VT_ARRAYMEMBER:
+ temp = pat_subst (val, p, rep, mflags);
+ if (vtype == VT_VARIABLE)
+ FREE (val);
+ if (temp)
+ {
+ tt = (mflags & MATCH_QUOTED) ? quote_string (temp) : quote_escapes (temp);
+ free (temp);
+ temp = tt;
+ }
+ break;
+ case VT_POSPARMS:
+ temp = pos_params_pat_subst (val, p, rep, mflags);
+ if (temp && (mflags & MATCH_QUOTED) == 0)
+ {
+ tt = quote_escapes (temp);
+ free (temp);
+ temp = tt;
+ }
+ break;
+#if defined (ARRAY_VARS)
+ case VT_ARRAYVAR:
+ temp = assoc_p (v) ? assoc_patsub (assoc_cell (v), p, rep, mflags)
+ : array_patsub (array_cell (v), p, rep, mflags);
+ /* Don't call quote_escapes anymore; array_patsub calls
+ array_quote_escapes as appropriate before adding the
+ space separators; ditto for assoc_patsub. */
+ break;
+#endif
+ }
+
+ FREE (pat);
+ FREE (rep);
+ free (lpatsub);
+
+ return temp;
+}
+
+/****************************************************************/
+/* */
+/* Functions to perform case modification on variable values */
+/* */
+/****************************************************************/
+
+/* Do case modification on the positional parameters. */
+
+static char *
+pos_params_modcase (string, pat, modop, mflags)
+ char *string, *pat;
+ int modop;
+ int mflags;
+{
+ WORD_LIST *save, *params;
+ WORD_DESC *w;
+ char *ret;
+ int pchar, qflags;
+
+ save = params = list_rest_of_args ();
+ if (save == 0)
+ return ((char *)NULL);
+
+ for ( ; params; params = params->next)
+ {
+ ret = sh_modcase (params->word->word, pat, modop);
+ w = alloc_word_desc ();
+ w->word = ret ? ret : savestring ("");
+ dispose_word (params->word);
+ params->word = w;
+ }
+
+ pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@';
+ qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0;
+
+ ret = string_list_pos_params (pchar, save, qflags);
+ dispose_words (save);
+
+ return (ret);
+}
+
+/* Perform case modification on VALUE, which is the expansion of
+ VARNAME. MODSPEC is an expression supplying the type of modification
+ to perform. QUOTED is a flags word containing the type of quoting
+ currently in effect. */
+static char *
+parameter_brace_casemod (varname, value, modspec, patspec, quoted)
+ char *varname, *value;
+ int modspec;
+ char *patspec;
+ int quoted;
+{
+ int vtype, starsub, modop, mflags, x;
+ char *val, *temp, *pat, *p, *lpat, *tt;
+ SHELL_VAR *v;
+
+ if (value == 0)
+ return ((char *)NULL);
+
+ this_command_name = varname;
+
+ vtype = get_var_and_type (varname, value, quoted, &v, &val);
+ if (vtype == -1)
+ return ((char *)NULL);
+
+ starsub = vtype & VT_STARSUB;
+ vtype &= ~VT_STARSUB;
+
+ modop = 0;
+ mflags = 0;
+ if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+ mflags |= MATCH_QUOTED;
+ if (starsub)
+ mflags |= MATCH_STARSUB;
+
+ p = patspec;
+ if (modspec == '^')
+ {
+ x = p && p[0] == modspec;
+ modop = x ? CASE_UPPER : CASE_UPFIRST;
+ p += x;
+ }
+ else if (modspec == ',')
+ {
+ x = p && p[0] == modspec;
+ modop = x ? CASE_LOWER : CASE_LOWFIRST;
+ p += x;
+ }
+ else if (modspec == '~')
+ {
+ x = p && p[0] == modspec;
+ modop = x ? CASE_TOGGLEALL : CASE_TOGGLE;
+ p += x;
+ }
+
+ lpat = p ? savestring (p) : 0;
+ /* Perform the same expansions on the pattern as performed by the
+ pattern removal expansions. FOR LATER */
+ pat = lpat ? getpattern (lpat, quoted, 1) : 0;
+
+ /* OK, now we do the case modification. */
+ switch (vtype)
+ {
+ case VT_VARIABLE:
+ case VT_ARRAYMEMBER:
+ temp = sh_modcase (val, pat, modop);
+ if (vtype == VT_VARIABLE)
+ FREE (val);
+ if (temp)
+ {
+ tt = (mflags & MATCH_QUOTED) ? quote_string (temp) : quote_escapes (temp);
+ free (temp);
+ temp = tt;
+ }
+ break;
+
+ case VT_POSPARMS:
+ temp = pos_params_modcase (val, pat, modop, mflags);
+ if (temp && (mflags & MATCH_QUOTED) == 0)
+ {
+ tt = quote_escapes (temp);
+ free (temp);
+ temp = tt;
+ }
+ break;
+
+#if defined (ARRAY_VARS)
+ case VT_ARRAYVAR:
+ temp = assoc_p (v) ? assoc_modcase (assoc_cell (v), pat, modop, mflags)
+ : array_modcase (array_cell (v), pat, modop, mflags);
+ /* Don't call quote_escapes; array_modcase calls array_quote_escapes
+ as appropriate before adding the space separators; ditto for
+ assoc_modcase. */
+ break;
+#endif
+ }
+
+ FREE (pat);
+ free (lpat);
+
+ return temp;
+}
+
+/* Check for unbalanced parens in S, which is the contents of $(( ... )). If
+ any occur, this must be a nested command substitution, so return 0.
+ Otherwise, return 1. A valid arithmetic expression must always have a
+ ( before a matching ), so any cases where there are more right parens
+ means that this must not be an arithmetic expression, though the parser
+ will not accept it without a balanced total number of parens. */
+static int
+chk_arithsub (s, len)
+ const char *s;
+ int len;
+{
+ int i, count;
+ DECLARE_MBSTATE;
+
+ i = count = 0;
+ while (i < len)
+ {
+ if (s[i] == LPAREN)
+ count++;
+ else if (s[i] == RPAREN)
+ {
+ count--;
+ if (count < 0)
+ return 0;
+ }
+
+ switch (s[i])
+ {
+ default:
+ ADVANCE_CHAR (s, len, i);
+ break;
+
+ case '\\':
+ i++;
+ if (s[i])
+ ADVANCE_CHAR (s, len, i);
+ break;
+
+ case '\'':
+ i = skip_single_quoted (s, len, ++i);
+ break;
+
+ case '"':
+ i = skip_double_quoted ((char *)s, len, ++i);
+ break;
+ }
+ }
+
+ return (count == 0);
+}
+
+/****************************************************************/
+/* */
+/* Functions to perform parameter expansion on a string */
+/* */
+/****************************************************************/
+
+/* ${[#][!]name[[:][^[^]][,[,]]#[#]%[%]-=?+[word][:e1[:e2]]]} */
+static WORD_DESC *
+parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, contains_dollar_at)
+ char *string;
+ int *indexp, quoted, *quoted_dollar_atp, *contains_dollar_at, pflags;
+{
+ int check_nullness, var_is_set, var_is_null, var_is_special;
+ int want_substring, want_indir, want_patsub, want_casemod;
+ char *name, *value, *temp, *temp1;
+ WORD_DESC *tdesc, *ret;
+ int t_index, sindex, c, tflag, modspec;
+ intmax_t number;
+
+ temp = temp1 = value = (char *)NULL;
+ var_is_set = var_is_null = var_is_special = check_nullness = 0;
+ want_substring = want_indir = want_patsub = want_casemod = 0;
+
+ sindex = *indexp;
+ t_index = ++sindex;
+ /* ${#var} doesn't have any of the other parameter expansions on it. */
+ if (string[t_index] == '#' && legal_variable_starter (string[t_index+1])) /* {{ */
+ name = string_extract (string, &t_index, "}", SX_VARNAME);
+ else
+#if defined (CASEMOD_EXPANSIONS)
+ /* To enable case-toggling expansions using the `~' operator character
+ change the 1 to 0. */
+# if defined (CASEMOD_CAPCASE)
+ name = string_extract (string, &t_index, "#%^,~:-=?+/}", SX_VARNAME);
+# else
+ name = string_extract (string, &t_index, "#%^,:-=?+/}", SX_VARNAME);
+# endif /* CASEMOD_CAPCASE */
+#else
+ name = string_extract (string, &t_index, "#%:-=?+/}", SX_VARNAME);
+#endif /* CASEMOD_EXPANSIONS */
+
+ ret = 0;
+ tflag = 0;
+
+ /* If the name really consists of a special variable, then make sure
+ that we have the entire name. We don't allow indirect references
+ to special variables except `#', `?', `@' and `*'. */
+ if ((sindex == t_index &&
+ (string[t_index] == '-' ||
+ string[t_index] == '?' ||
+ string[t_index] == '#')) ||
+ (sindex == t_index - 1 && string[sindex] == '!' &&
+ (string[t_index] == '#' ||
+ string[t_index] == '?' ||
+ string[t_index] == '@' ||
+ string[t_index] == '*')))
+ {
+ t_index++;
+ free (name);
+ temp1 = string_extract (string, &t_index, "#%:-=?+/}", 0);
+ name = (char *)xmalloc (3 + (strlen (temp1)));
+ *name = string[sindex];
+ if (string[sindex] == '!')
+ {
+ /* indirect reference of $#, $?, $@, or $* */
+ name[1] = string[sindex + 1];
+ strcpy (name + 2, temp1);
+ }
+ else
+ strcpy (name + 1, temp1);
+ free (temp1);
+ }
+ sindex = t_index;
+
+ /* Find out what character ended the variable name. Then
+ do the appropriate thing. */
+ if (c = string[sindex])
+ sindex++;
+
+ /* If c is followed by one of the valid parameter expansion
+ characters, move past it as normal. If not, assume that
+ a substring specification is being given, and do not move
+ past it. */
+ if (c == ':' && VALID_PARAM_EXPAND_CHAR (string[sindex]))
+ {
+ check_nullness++;
+ if (c = string[sindex])
+ sindex++;
+ }
+ else if (c == ':' && string[sindex] != RBRACE)
+ want_substring = 1;
+ else if (c == '/' && string[sindex] != RBRACE)
+ want_patsub = 1;
+#if defined (CASEMOD_EXPANSIONS)
+ else if (c == '^' || c == ',' || c == '~')
+ {
+ modspec = c;
+ want_casemod = 1;
+ }
+#endif
+
+ /* Catch the valid and invalid brace expressions that made it through the
+ tests above. */
+ /* ${#-} is a valid expansion and means to take the length of $-.
+ Similarly for ${#?} and ${##}... */
+ if (name[0] == '#' && name[1] == '\0' && check_nullness == 0 &&
+ VALID_SPECIAL_LENGTH_PARAM (c) && string[sindex] == RBRACE)
+ {
+ name = (char *)xrealloc (name, 3);
+ name[1] = c;
+ name[2] = '\0';
+ c = string[sindex++];
+ }
+
+ /* ...but ${#%}, ${#:}, ${#=}, ${#+}, and ${#/} are errors. */
+ if (name[0] == '#' && name[1] == '\0' && check_nullness == 0 &&
+ member (c, "%:=+/") && string[sindex] == RBRACE)
+ {
+ temp = (char *)NULL;
+ goto bad_substitution;
+ }
+
+ /* Indirect expansion begins with a `!'. A valid indirect expansion is
+ either a variable name, one of the positional parameters or a special
+ variable that expands to one of the positional parameters. */
+ want_indir = *name == '!' &&
+ (legal_variable_starter ((unsigned char)name[1]) || DIGIT (name[1])
+ || VALID_INDIR_PARAM (name[1]));
+
+ /* Determine the value of this variable. */
+
+ /* Check for special variables, directly referenced. */
+ if (SPECIAL_VAR (name, want_indir))
+ var_is_special++;
+
+ /* Check for special expansion things, like the length of a parameter */
+ if (*name == '#' && name[1])
+ {
+ /* If we are not pointing at the character just after the
+ closing brace, then we haven't gotten all of the name.
+ Since it begins with a special character, this is a bad
+ substitution. Also check NAME for validity before trying
+ to go on. */
+ if (string[sindex - 1] != RBRACE || (valid_length_expression (name) == 0))
+ {
+ temp = (char *)NULL;
+ goto bad_substitution;
+ }
+
+ number = parameter_brace_expand_length (name);
+ free (name);
+
+ *indexp = sindex;
+ if (number < 0)
+ return (&expand_wdesc_error);
+ else
+ {
+ ret = alloc_word_desc ();
+ ret->word = itos (number);
+ return ret;
+ }
+ }
+
+ /* ${@} is identical to $@. */
+ if (name[0] == '@' && name[1] == '\0')
+ {
+ if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
+ *quoted_dollar_atp = 1;
+
+ if (contains_dollar_at)
+ *contains_dollar_at = 1;
+ }
+
+ /* Process ${!PREFIX*} expansion. */
+ if (want_indir && string[sindex - 1] == RBRACE &&
+ (string[sindex - 2] == '*' || string[sindex - 2] == '@') &&
+ legal_variable_starter ((unsigned char) name[1]))
+ {
+ char **x;
+ WORD_LIST *xlist;
+
+ temp1 = savestring (name + 1);
+ number = strlen (temp1);
+ temp1[number - 1] = '\0';
+ x = all_variables_matching_prefix (temp1);
+ xlist = strvec_to_word_list (x, 0, 0);
+ if (string[sindex - 2] == '*')
+ temp = string_list_dollar_star (xlist);
+ else
+ {
+ temp = string_list_dollar_at (xlist, quoted);
+ if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
+ *quoted_dollar_atp = 1;
+ if (contains_dollar_at)
+ *contains_dollar_at = 1;
+ }
+ free (x);
+ dispose_words (xlist);
+ free (temp1);
+ *indexp = sindex;
+
+ ret = alloc_word_desc ();
+ ret->word = temp;
+ return ret;
+ }
+
+#if defined (ARRAY_VARS)
+ /* Process ${!ARRAY[@]} and ${!ARRAY[*]} expansion. */ /* [ */
+ if (want_indir && string[sindex - 1] == RBRACE &&
+ string[sindex - 2] == ']' && valid_array_reference (name+1))
+ {
+ char *x, *x1;
+
+ temp1 = savestring (name + 1);
+ x = array_variable_name (temp1, &x1, (int *)0); /* [ */
+ FREE (x);
+ if (ALL_ELEMENT_SUB (x1[0]) && x1[1] == ']')
+ {
+ temp = array_keys (temp1, quoted); /* handles assoc vars too */
+ if (x1[0] == '@')
+ {
+ if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
+ *quoted_dollar_atp = 1;
+ if (contains_dollar_at)
+ *contains_dollar_at = 1;
+ }
+
+ free (temp1);
+ *indexp = sindex;
+
+ ret = alloc_word_desc ();
+ ret->word = temp;
+ return ret;
+ }
+
+ free (temp1);
+ }
+#endif /* ARRAY_VARS */
+
+ /* Make sure that NAME is valid before trying to go on. */
+ if (valid_brace_expansion_word (want_indir ? name + 1 : name,
+ var_is_special) == 0)
+ {
+ temp = (char *)NULL;
+ goto bad_substitution;
+ }
+
+ if (want_indir)
+ tdesc = parameter_brace_expand_indir (name + 1, var_is_special, quoted, quoted_dollar_atp, contains_dollar_at);
+ else
+ tdesc = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND|(pflags&PF_NOSPLIT2));
+
+ if (tdesc)
+ {
+ temp = tdesc->word;
+ tflag = tdesc->flags;
+ dispose_word_desc (tdesc);
+ }
+ else
+ temp = (char *)0;
+
+#if defined (ARRAY_VARS)
+ if (valid_array_reference (name))
+ chk_atstar (name, quoted, quoted_dollar_atp, contains_dollar_at);
+#endif
+
+ var_is_set = temp != (char *)0;
+ var_is_null = check_nullness && (var_is_set == 0 || *temp == 0);
+
+ /* Get the rest of the stuff inside the braces. */
+ if (c && c != RBRACE)
+ {
+ /* Extract the contents of the ${ ... } expansion
+ according to the Posix.2 rules. */
+ value = extract_dollar_brace_string (string, &sindex, quoted, 0);
+ if (string[sindex] == RBRACE)
+ sindex++;
+ else
+ goto bad_substitution;
+ }
+ else
+ value = (char *)NULL;
+
+ *indexp = sindex;
+
+ /* If this is a substring spec, process it and add the result. */
+ if (want_substring)
+ {
+ temp1 = parameter_brace_substring (name, temp, value, quoted);
+ FREE (name);
+ FREE (value);
+ FREE (temp);
+
+ if (temp1 == &expand_param_error)
+ return (&expand_wdesc_error);
+ else if (temp1 == &expand_param_fatal)
+ return (&expand_wdesc_fatal);
+
+ ret = alloc_word_desc ();
+ ret->word = temp1;
+ if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+ ret->flags |= W_QUOTED|W_HASQUOTEDNULL;
+ return ret;
+ }
+ else if (want_patsub)
+ {
+ temp1 = parameter_brace_patsub (name, temp, value, quoted);
+ FREE (name);
+ FREE (value);
+ FREE (temp);
+
+ if (temp1 == &expand_param_error)
+ return (&expand_wdesc_error);
+ else if (temp1 == &expand_param_fatal)
+ return (&expand_wdesc_fatal);
+
+ ret = alloc_word_desc ();
+ ret->word = temp1;
+ ret = alloc_word_desc ();
+ ret->word = temp1;
+ if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+ ret->flags |= W_QUOTED|W_HASQUOTEDNULL;
+ return ret;
+ }
+#if defined (CASEMOD_EXPANSIONS)
+ else if (want_casemod)
+ {
+ temp1 = parameter_brace_casemod (name, temp, modspec, value, quoted);
+ FREE (name);
+ FREE (value);
+ FREE (temp);
+
+ if (temp1 == &expand_param_error)
+ return (&expand_wdesc_error);
+ else if (temp1 == &expand_param_fatal)
+ return (&expand_wdesc_fatal);
+
+ ret = alloc_word_desc ();
+ ret->word = temp1;
+ if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+ ret->flags |= W_QUOTED|W_HASQUOTEDNULL;
+ return ret;
+ }
+#endif
+
+ /* Do the right thing based on which character ended the variable name. */
+ switch (c)
+ {
+ default:
+ case '\0':
+ bad_substitution:
+ report_error (_("%s: bad substitution"), string ? string : "??");
+ FREE (value);
+ FREE (temp);
+ free (name);
+ return &expand_wdesc_error;
+
+ case RBRACE:
+ if (var_is_set == 0 && unbound_vars_is_error && ((name[0] != '@' && name[0] != '*') || name[1]))
+ {
+ last_command_exit_value = EXECUTION_FAILURE;
+ err_unboundvar (name);
+ FREE (value);
+ FREE (temp);
+ free (name);
+ return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+ }
+ break;
+
+ case '#': /* ${param#[#]pattern} */
+ case '%': /* ${param%[%]pattern} */
+ if (value == 0 || *value == '\0' || temp == 0 || *temp == '\0')
+ {
+ FREE (value);
+ break;
+ }
+ temp1 = parameter_brace_remove_pattern (name, temp, value, c, quoted);
+ free (temp);
+ free (value);
+
+ ret = alloc_word_desc ();
+ ret->word = temp1;
+ if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+ ret->flags |= W_QUOTED|W_HASQUOTEDNULL;
+ return ret;
+
+ case '-':
+ case '=':
+ case '?':
+ case '+':
+ if (var_is_set && var_is_null == 0)
+ {
+ /* If the operator is `+', we don't want the value of the named
+ variable for anything, just the value of the right hand side. */
+
+ if (c == '+')
+ {
+ /* XXX -- if we're double-quoted and the named variable is "$@",
+ we want to turn off any special handling of "$@" --
+ we're not using it, so whatever is on the rhs applies. */
+ if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
+ *quoted_dollar_atp = 0;
+ if (contains_dollar_at)
+ *contains_dollar_at = 0;
+
+ FREE (temp);
+ if (value)
+ {
+ ret = parameter_brace_expand_rhs (name, value, c,
+ quoted,
+ quoted_dollar_atp,
+ contains_dollar_at);
+ /* XXX - fix up later, esp. noting presence of
+ W_HASQUOTEDNULL in ret->flags */
+ free (value);
+ }
+ else
+ temp = (char *)NULL;
+ }
+ else
+ {
+ FREE (value);
+ }
+ /* Otherwise do nothing; just use the value in TEMP. */
+ }
+ else /* VAR not set or VAR is NULL. */
+ {
+ FREE (temp);
+ temp = (char *)NULL;
+ if (c == '=' && var_is_special)
+ {
+ report_error (_("$%s: cannot assign in this way"), name);
+ free (name);
+ free (value);
+ return &expand_wdesc_error;
+ }
+ else if (c == '?')
+ {
+ parameter_brace_expand_error (name, value);
+ return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+ }
+ else if (c != '+')
+ {
+ /* XXX -- if we're double-quoted and the named variable is "$@",
+ we want to turn off any special handling of "$@" --
+ we're not using it, so whatever is on the rhs applies. */
+ if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
+ *quoted_dollar_atp = 0;
+ if (contains_dollar_at)
+ *contains_dollar_at = 0;
+
+ ret = parameter_brace_expand_rhs (name, value, c, quoted,
+ quoted_dollar_atp,
+ contains_dollar_at);
+ /* XXX - fix up later, esp. noting presence of
+ W_HASQUOTEDNULL in tdesc->flags */
+ }
+ free (value);
+ }
+
+ break;
+ }
+ free (name);
+
+ if (ret == 0)
+ {
+ ret = alloc_word_desc ();
+ ret->flags = tflag;
+ ret->word = temp;
+ }
+ return (ret);
+}
+
+/* Expand a single ${xxx} expansion. The braces are optional. When
+ the braces are used, parameter_brace_expand() does the work,
+ possibly calling param_expand recursively. */
+static WORD_DESC *
+param_expand (string, sindex, quoted, expanded_something,
+ contains_dollar_at, quoted_dollar_at_p, had_quoted_null_p,
+ pflags)
+ char *string;
+ int *sindex, quoted, *expanded_something, *contains_dollar_at;
+ int *quoted_dollar_at_p, *had_quoted_null_p, pflags;
+{
+ char *temp, *temp1, uerror[3];
+ int zindex, t_index, expok;
+ unsigned char c;
+ intmax_t number;
+ SHELL_VAR *var;
+ WORD_LIST *list;
+ WORD_DESC *tdesc, *ret;
+ int tflag;
+
+ zindex = *sindex;
+ c = string[++zindex];
+
+ temp = (char *)NULL;
+ ret = tdesc = (WORD_DESC *)NULL;
+ tflag = 0;
+
+ /* Do simple cases first. Switch on what follows '$'. */
+ switch (c)
+ {
+ /* $0 .. $9? */
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ temp1 = dollar_vars[TODIGIT (c)];
+ if (unbound_vars_is_error && temp1 == (char *)NULL)
+ {
+ uerror[0] = '$';
+ uerror[1] = c;
+ uerror[2] = '\0';
+ last_command_exit_value = EXECUTION_FAILURE;
+ err_unboundvar (uerror);
+ return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+ }
+ if (temp1)
+ temp = (*temp1 && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+ ? quote_string (temp1)
+ : quote_escapes (temp1);
+ else
+ temp = (char *)NULL;
+
+ break;
+
+ /* $$ -- pid of the invoking shell. */
+ case '$':
+ temp = itos (dollar_dollar_pid);
+ break;
+
+ /* $# -- number of positional parameters. */
+ case '#':
+ temp = itos (number_of_args ());
+ break;
+
+ /* $? -- return value of the last synchronous command. */
+ case '?':
+ temp = itos (last_command_exit_value);
+ break;
+
+ /* $- -- flags supplied to the shell on invocation or by `set'. */
+ case '-':
+ temp = which_set_flags ();
+ break;
+
+ /* $! -- Pid of the last asynchronous command. */
+ case '!':
+ /* If no asynchronous pids have been created, expand to nothing.
+ If `set -u' has been executed, and no async processes have
+ been created, this is an expansion error. */
+ if (last_asynchronous_pid == NO_PID)
+ {
+ if (expanded_something)
+ *expanded_something = 0;
+ temp = (char *)NULL;
+ if (unbound_vars_is_error)
+ {
+ uerror[0] = '$';
+ uerror[1] = c;
+ uerror[2] = '\0';
+ last_command_exit_value = EXECUTION_FAILURE;
+ err_unboundvar (uerror);
+ return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+ }
+ }
+ else
+ temp = itos (last_asynchronous_pid);
+ break;
+
+ /* The only difference between this and $@ is when the arg is quoted. */
+ case '*': /* `$*' */
+ list = list_rest_of_args ();
+
+#if 0
+ /* According to austin-group posix proposal by Geoff Clare in
+ <20090505091501.GA10097@squonk.masqnet> of 5 May 2009:
+
+ "The shell shall write a message to standard error and
+ immediately exit when it tries to expand an unset parameter
+ other than the '@' and '*' special parameters."
+ */
+
+ if (list == 0 && unbound_vars_is_error && (pflags & PF_IGNUNBOUND) == 0)
+ {
+ uerror[0] = '$';
+ uerror[1] = '*';
+ uerror[2] = '\0';
+ last_command_exit_value = EXECUTION_FAILURE;
+ err_unboundvar (uerror);
+ return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+ }
+#endif
+
+ /* If there are no command-line arguments, this should just
+ disappear if there are other characters in the expansion,
+ even if it's quoted. */
+ if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && list == 0)
+ temp = (char *)NULL;
+ else if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES|Q_PATQUOTE))
+ {
+ /* If we have "$*" we want to make a string of the positional
+ parameters, separated by the first character of $IFS, and
+ quote the whole string, including the separators. If IFS
+ is unset, the parameters are separated by ' '; if $IFS is
+ null, the parameters are concatenated. */
+ temp = (quoted & (Q_DOUBLE_QUOTES|Q_PATQUOTE)) ? string_list_dollar_star (list) : string_list (list);
+ temp1 = quote_string (temp);
+ if (*temp == 0)
+ tflag |= W_HASQUOTEDNULL;
+ free (temp);
+ temp = temp1;
+ }
+ else
+ {
+ /* We check whether or not we're eventually going to split $* here,
+ for example when IFS is empty and we are processing the rhs of
+ an assignment statement. In that case, we don't separate the
+ arguments at all. Otherwise, if the $* is not quoted it is
+ identical to $@ */
+#if 1
+# if defined (HANDLE_MULTIBYTE)
+ if (expand_no_split_dollar_star && ifs_firstc[0] == 0)
+# else
+ if (expand_no_split_dollar_star && ifs_firstc == 0)
+# endif
+ temp = string_list_dollar_star (list);
+ else
+ temp = string_list_dollar_at (list, quoted);
+#else
+ temp = string_list_dollar_at (list, quoted);
+#endif
+ if (expand_no_split_dollar_star == 0 && contains_dollar_at)
+ *contains_dollar_at = 1;
+ }
+
+ dispose_words (list);
+ break;
+
+ /* When we have "$@" what we want is "$1" "$2" "$3" ... This
+ means that we have to turn quoting off after we split into
+ the individually quoted arguments so that the final split
+ on the first character of $IFS is still done. */
+ case '@': /* `$@' */
+ list = list_rest_of_args ();
+
+#if 0
+ /* According to austin-group posix proposal by Geoff Clare in
+ <20090505091501.GA10097@squonk.masqnet> of 5 May 2009:
+
+ "The shell shall write a message to standard error and
+ immediately exit when it tries to expand an unset parameter
+ other than the '@' and '*' special parameters."
+ */
+
+ if (list == 0 && unbound_vars_is_error && (pflags & PF_IGNUNBOUND) == 0)
+ {
+ uerror[0] = '$';
+ uerror[1] = '@';
+ uerror[2] = '\0';
+ last_command_exit_value = EXECUTION_FAILURE;
+ err_unboundvar (uerror);
+ return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+ }
+#endif
+
+ /* We want to flag the fact that we saw this. We can't turn
+ off quoting entirely, because other characters in the
+ string might need it (consider "\"$@\""), but we need some
+ way to signal that the final split on the first character
+ of $IFS should be done, even though QUOTED is 1. */
+ /* XXX - should this test include Q_PATQUOTE? */
+ if (quoted_dollar_at_p && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+ *quoted_dollar_at_p = 1;
+ if (contains_dollar_at)
+ *contains_dollar_at = 1;
+
+#if 0
+ if (pflags & PF_NOSPLIT2)
+ temp = string_list_internal (quoted ? quote_list (list) : list, " ");
+ else
+#endif
+ /* We want to separate the positional parameters with the first
+ character of $IFS in case $IFS is something other than a space.
+ We also want to make sure that splitting is done no matter what --
+ according to POSIX.2, this expands to a list of the positional
+ parameters no matter what IFS is set to. */
+ temp = string_list_dollar_at (list, quoted);
+
+ dispose_words (list);
+ break;
+
+ case LBRACE:
+ tdesc = parameter_brace_expand (string, &zindex, quoted, pflags,
+ quoted_dollar_at_p,
+ contains_dollar_at);
+
+ if (tdesc == &expand_wdesc_error || tdesc == &expand_wdesc_fatal)
+ return (tdesc);
+ temp = tdesc ? tdesc->word : (char *)0;
+
+ /* XXX */
+ /* Quoted nulls should be removed if there is anything else
+ in the string. */
+ /* Note that we saw the quoted null so we can add one back at
+ the end of this function if there are no other characters
+ in the string, discard TEMP, and go on. The exception to
+ this is when we have "${@}" and $1 is '', since $@ needs
+ special handling. */
+ if (tdesc && tdesc->word && (tdesc->flags & W_HASQUOTEDNULL) && QUOTED_NULL (temp))
+ {
+ if (had_quoted_null_p)
+ *had_quoted_null_p = 1;
+ if (*quoted_dollar_at_p == 0)
+ {
+ free (temp);
+ tdesc->word = temp = (char *)NULL;
+ }
+
+ }
+
+ ret = tdesc;
+ goto return0;
+
+ /* Do command or arithmetic substitution. */
+ case LPAREN:
+ /* We have to extract the contents of this paren substitution. */
+ t_index = zindex + 1;
+ temp = extract_command_subst (string, &t_index, 0);
+ zindex = t_index;
+
+ /* For Posix.2-style `$(( ))' arithmetic substitution,
+ extract the expression and pass it to the evaluator. */
+ if (temp && *temp == LPAREN)
+ {
+ char *temp2;
+ temp1 = temp + 1;
+ temp2 = savestring (temp1);
+ t_index = strlen (temp2) - 1;
+
+ if (temp2[t_index] != RPAREN)
+ {
+ free (temp2);
+ goto comsub;
+ }
+
+ /* Cut off ending `)' */
+ temp2[t_index] = '\0';
+
+ if (chk_arithsub (temp2, t_index) == 0)
+ {
+ free (temp2);
+#if 0
+ internal_warning (_("future versions of the shell will force evaluation as an arithmetic substitution"));
+#endif
+ goto comsub;
+ }
+
+ /* Expand variables found inside the expression. */
+ temp1 = expand_arith_string (temp2, Q_DOUBLE_QUOTES);
+ free (temp2);
+
+arithsub:
+ /* No error messages. */
+ this_command_name = (char *)NULL;
+ number = evalexp (temp1, &expok);
+ free (temp);
+ free (temp1);
+ if (expok == 0)
+ {
+ if (interactive_shell == 0 && posixly_correct)
+ {
+ last_command_exit_value = EXECUTION_FAILURE;
+ return (&expand_wdesc_fatal);
+ }
+ else
+ return (&expand_wdesc_error);
+ }
+ temp = itos (number);
+ break;
+ }
+
+comsub:
+ if (pflags & PF_NOCOMSUB)
+ /* we need zindex+1 because string[zindex] == RPAREN */
+ temp1 = substring (string, *sindex, zindex+1);
+ else
+ {
+ tdesc = command_substitute (temp, quoted);
+ temp1 = tdesc ? tdesc->word : (char *)NULL;
+ if (tdesc)
+ dispose_word_desc (tdesc);
+ }
+ FREE (temp);
+ temp = temp1;
+ break;
+
+ /* Do POSIX.2d9-style arithmetic substitution. This will probably go
+ away in a future bash release. */
+ case '[':
+ /* Extract the contents of this arithmetic substitution. */
+ t_index = zindex + 1;
+ temp = extract_arithmetic_subst (string, &t_index);
+ zindex = t_index;
+ if (temp == 0)
+ {
+ temp = savestring (string);
+ if (expanded_something)
+ *expanded_something = 0;
+ goto return0;
+ }
+
+ /* Do initial variable expansion. */
+ temp1 = expand_arith_string (temp, Q_DOUBLE_QUOTES);
+
+ goto arithsub;
+
+ default:
+ /* Find the variable in VARIABLE_LIST. */
+ temp = (char *)NULL;
+
+ for (t_index = zindex; (c = string[zindex]) && legal_variable_char (c); zindex++)
+ ;
+ temp1 = (zindex > t_index) ? substring (string, t_index, zindex) : (char *)NULL;
+
+ /* If this isn't a variable name, then just output the `$'. */
+ if (temp1 == 0 || *temp1 == '\0')
+ {
+ FREE (temp1);
+ temp = (char *)xmalloc (2);
+ temp[0] = '$';
+ temp[1] = '\0';
+ if (expanded_something)
+ *expanded_something = 0;
+ goto return0;
+ }
+
+ /* If the variable exists, return its value cell. */
+ var = find_variable (temp1);
+
+ if (var && invisible_p (var) == 0 && var_isset (var))
+ {
+#if defined (ARRAY_VARS)
+ if (assoc_p (var) || array_p (var))
+ {
+ temp = array_p (var) ? array_reference (array_cell (var), 0)
+ : assoc_reference (assoc_cell (var), "0");
+ if (temp)
+ temp = (*temp && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+ ? quote_string (temp)
+ : quote_escapes (temp);
+ else if (unbound_vars_is_error)
+ goto unbound_variable;
+ }
+ else
+#endif
+ {
+ temp = value_cell (var);
+
+ temp = (*temp && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+ ? quote_string (temp)
+ : quote_escapes (temp);
+ }
+
+ free (temp1);
+
+ goto return0;
+ }
+
+ temp = (char *)NULL;
+
+unbound_variable:
+ if (unbound_vars_is_error)
+ {
+ last_command_exit_value = EXECUTION_FAILURE;
+ err_unboundvar (temp1);
+ }
+ else
+ {
+ free (temp1);
+ goto return0;
+ }
+
+ free (temp1);
+ last_command_exit_value = EXECUTION_FAILURE;
+ return ((unbound_vars_is_error && interactive_shell == 0)
+ ? &expand_wdesc_fatal
+ : &expand_wdesc_error);
+ }
+
+ if (string[zindex])
+ zindex++;
+
+return0:
+ *sindex = zindex;
+
+ if (ret == 0)
+ {
+ ret = alloc_word_desc ();
+ ret->flags = tflag; /* XXX */
+ ret->word = temp;
+ }
+ return ret;
+}
+
+/* Make a word list which is the result of parameter and variable
+ expansion, command substitution, arithmetic substitution, and
+ quote removal of WORD. Return a pointer to a WORD_LIST which is
+ the result of the expansion. If WORD contains a null word, the
+ word list returned is also null.
+
+ QUOTED contains flag values defined in shell.h.
+
+ ISEXP is used to tell expand_word_internal that the word should be
+ treated as the result of an expansion. This has implications for
+ how IFS characters in the word are treated.
+
+ CONTAINS_DOLLAR_AT and EXPANDED_SOMETHING are return values; when non-null
+ they point to an integer value which receives information about expansion.
+ CONTAINS_DOLLAR_AT gets non-zero if WORD contained "$@", else zero.
+ EXPANDED_SOMETHING get non-zero if WORD contained any parameter expansions,
+ else zero.
+
+ This only does word splitting in the case of $@ expansion. In that
+ case, we split on ' '. */
+
+/* Values for the local variable quoted_state. */
+#define UNQUOTED 0
+#define PARTIALLY_QUOTED 1
+#define WHOLLY_QUOTED 2
+
+static WORD_LIST *
+expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_something)
+ WORD_DESC *word;
+ int quoted, isexp;
+ int *contains_dollar_at;
+ int *expanded_something;
+{
+ WORD_LIST *list;
+ WORD_DESC *tword;
+
+ /* The intermediate string that we build while expanding. */
+ char *istring;
+
+ /* The current size of the above object. */
+ int istring_size;
+
+ /* Index into ISTRING. */
+ int istring_index;
+
+ /* Temporary string storage. */
+ char *temp, *temp1;
+
+ /* The text of WORD. */
+ register char *string;
+
+ /* The size of STRING. */
+ size_t string_size;
+
+ /* The index into STRING. */
+ int sindex;
+
+ /* This gets 1 if we see a $@ while quoted. */
+ int quoted_dollar_at;
+
+ /* One of UNQUOTED, PARTIALLY_QUOTED, or WHOLLY_QUOTED, depending on
+ whether WORD contains no quoting characters, a partially quoted
+ string (e.g., "xx"ab), or is fully quoted (e.g., "xxab"). */
+ int quoted_state;
+
+ /* State flags */
+ int had_quoted_null;
+ int has_dollar_at;
+ int tflag;
+ int pflags; /* flags passed to param_expand */
+
+ int assignoff; /* If assignment, offset of `=' */
+
+ register unsigned char c; /* Current character. */
+ int t_index; /* For calls to string_extract_xxx. */
+
+ char twochars[2];
+
+ DECLARE_MBSTATE;
+
+ istring = (char *)xmalloc (istring_size = DEFAULT_INITIAL_ARRAY_SIZE);
+ istring[istring_index = 0] = '\0';
+ quoted_dollar_at = had_quoted_null = has_dollar_at = 0;
+ quoted_state = UNQUOTED;
+
+ string = word->word;
+ if (string == 0)
+ goto finished_with_string;
+ /* Don't need the string length for the SADD... and COPY_ macros unless
+ multibyte characters are possible. */
+ string_size = (MB_CUR_MAX > 1) ? strlen (string) : 1;
+
+ if (contains_dollar_at)
+ *contains_dollar_at = 0;
+
+ assignoff = -1;
+
+ /* Begin the expansion. */
+
+ for (sindex = 0; ;)
+ {
+ c = string[sindex];
+
+ /* Case on toplevel character. */
+ switch (c)
+ {
+ case '\0':
+ goto finished_with_string;
+
+ case CTLESC:
+ sindex++;
+#if HANDLE_MULTIBYTE
+ if (MB_CUR_MAX > 1 && string[sindex])
+ {
+ SADD_MBQCHAR_BODY(temp, string, sindex, string_size);
+ }
+ else
+#endif
+ {
+ temp = (char *)xmalloc (3);
+ temp[0] = CTLESC;
+ temp[1] = c = string[sindex];
+ temp[2] = '\0';
+ }
+
+dollar_add_string:
+ if (string[sindex])
+ sindex++;
+
+add_string:
+ if (temp)
+ {
+ istring = sub_append_string (temp, istring, &istring_index, &istring_size);
+ temp = (char *)0;
+ }
+
+ break;
+
+#if defined (PROCESS_SUBSTITUTION)
+ /* Process substitution. */
+ case '<':
+ case '>':
+ {
+ if (string[++sindex] != LPAREN || (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (word->flags & (W_DQUOTE|W_NOPROCSUB)) || posixly_correct)
+ {
+ sindex--; /* add_character: label increments sindex */
+ goto add_character;
+ }
+ else
+ t_index = sindex + 1; /* skip past both '<' and LPAREN */
+
+ temp1 = extract_process_subst (string, (c == '<') ? "<(" : ">(", &t_index); /*))*/
+ sindex = t_index;
+
+ /* If the process substitution specification is `<()', we want to
+ open the pipe for writing in the child and produce output; if
+ it is `>()', we want to open the pipe for reading in the child
+ and consume input. */
+ temp = temp1 ? process_substitute (temp1, (c == '>')) : (char *)0;
+
+ FREE (temp1);
+
+ goto dollar_add_string;
+ }
+#endif /* PROCESS_SUBSTITUTION */
+
+ case '=':
+ /* Posix.2 section 3.6.1 says that tildes following `=' in words
+ which are not assignment statements are not expanded. If the
+ shell isn't in posix mode, though, we perform tilde expansion
+ on `likely candidate' unquoted assignment statements (flags
+ include W_ASSIGNMENT but not W_QUOTED). A likely candidate
+ contains an unquoted :~ or =~. Something to think about: we
+ now have a flag that says to perform tilde expansion on arguments
+ to `assignment builtins' like declare and export that look like
+ assignment statements. We now do tilde expansion on such words
+ even in POSIX mode. */
+ if (word->flags & (W_ASSIGNRHS|W_NOTILDE))
+ {
+ if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c))
+ goto add_ifs_character;
+ else
+ goto add_character;
+ }
+ /* If we're not in posix mode or forcing assignment-statement tilde
+ expansion, note where the `=' appears in the word and prepare to
+ do tilde expansion following the first `='. */
+ if ((word->flags & W_ASSIGNMENT) &&
+ (posixly_correct == 0 || (word->flags & W_TILDEEXP)) &&
+ assignoff == -1 && sindex > 0)
+ assignoff = sindex;
+ if (sindex == assignoff && string[sindex+1] == '~') /* XXX */
+ word->flags |= W_ITILDE;
+#if 0
+ else if ((word->flags & W_ASSIGNMENT) &&
+ (posixly_correct == 0 || (word->flags & W_TILDEEXP)) &&
+ string[sindex+1] == '~')
+ word->flags |= W_ITILDE;
+#endif
+ if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c))
+ goto add_ifs_character;
+ else
+ goto add_character;
+
+ case ':':
+ if (word->flags & W_NOTILDE)
+ {
+ if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c))
+ goto add_ifs_character;
+ else
+ goto add_character;
+ }
+
+ if ((word->flags & (W_ASSIGNMENT|W_ASSIGNRHS|W_TILDEEXP)) &&
+ string[sindex+1] == '~')
+ word->flags |= W_ITILDE;
+
+ if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c))
+ goto add_ifs_character;
+ else
+ goto add_character;
+
+ case '~':
+ /* If the word isn't supposed to be tilde expanded, or we're not
+ at the start of a word or after an unquoted : or = in an
+ assignment statement, we don't do tilde expansion. */
+ if ((word->flags & (W_NOTILDE|W_DQUOTE)) ||
+ (sindex > 0 && ((word->flags & W_ITILDE) == 0)) ||
+ (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+ {
+ word->flags &= ~W_ITILDE;
+ if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0)
+ goto add_ifs_character;
+ else
+ goto add_character;
+ }
+
+ if (word->flags & W_ASSIGNRHS)
+ tflag = 2;
+ else if (word->flags & (W_ASSIGNMENT|W_TILDEEXP))
+ tflag = 1;
+ else
+ tflag = 0;
+
+ temp = bash_tilde_find_word (string + sindex, tflag, &t_index);
+
+ word->flags &= ~W_ITILDE;
+
+ if (temp && *temp && t_index > 0)
+ {
+ temp1 = bash_tilde_expand (temp, tflag);
+ if (temp1 && *temp1 == '~' && STREQ (temp, temp1))
+ {
+ FREE (temp);
+ FREE (temp1);
+ goto add_character; /* tilde expansion failed */
+ }
+ free (temp);
+ temp = temp1;
+ sindex += t_index;
+ goto add_quoted_string; /* XXX was add_string */
+ }
+ else
+ {
+ FREE (temp);
+ goto add_character;
+ }
+
+ case '$':
+ if (expanded_something)
+ *expanded_something = 1;
+
+ has_dollar_at = 0;
+ pflags = (word->flags & W_NOCOMSUB) ? PF_NOCOMSUB : 0;
+ if (word->flags & W_NOSPLIT2)
+ pflags |= PF_NOSPLIT2;
+ tword = param_expand (string, &sindex, quoted, expanded_something,
+ &has_dollar_at, "ed_dollar_at,
+ &had_quoted_null, pflags);
+
+ if (tword == &expand_wdesc_error || tword == &expand_wdesc_fatal)
+ {
+ free (string);
+ free (istring);
+ return ((tword == &expand_wdesc_error) ? &expand_word_error
+ : &expand_word_fatal);
+ }
+ if (contains_dollar_at && has_dollar_at)
+ *contains_dollar_at = 1;
+
+ if (tword && (tword->flags & W_HASQUOTEDNULL))
+ had_quoted_null = 1;
+
+ temp = tword->word;
+ dispose_word_desc (tword);
+
+ goto add_string;
+ break;
+
+ case '`': /* Backquoted command substitution. */
+ {
+ t_index = sindex++;
+
+ temp = string_extract (string, &sindex, "`", SX_REQMATCH);
+ /* The test of sindex against t_index is to allow bare instances of
+ ` to pass through, for backwards compatibility. */
+ if (temp == &extract_string_error || temp == &extract_string_fatal)
+ {
+ if (sindex - 1 == t_index)
+ {
+ sindex = t_index;
+ goto add_character;
+ }
+ report_error (_("bad substitution: no closing \"`\" in %s") , string+t_index);
+ free (string);
+ free (istring);
+ return ((temp == &extract_string_error) ? &expand_word_error
+ : &expand_word_fatal);
+ }
+
+ if (expanded_something)
+ *expanded_something = 1;
+
+ if (word->flags & W_NOCOMSUB)
+ /* sindex + 1 because string[sindex] == '`' */
+ temp1 = substring (string, t_index, sindex + 1);
+ else
+ {
+ de_backslash (temp);
+ tword = command_substitute (temp, quoted);
+ temp1 = tword ? tword->word : (char *)NULL;
+ if (tword)
+ dispose_word_desc (tword);
+ }
+ FREE (temp);
+ temp = temp1;
+ goto dollar_add_string;
+ }
+
+ case '\\':
+ if (string[sindex + 1] == '\n')
+ {
+ sindex += 2;
+ continue;
+ }
+
+ c = string[++sindex];
+
+ if (quoted & Q_HERE_DOCUMENT)
+ tflag = CBSHDOC;
+ else if (quoted & Q_DOUBLE_QUOTES)
+ tflag = CBSDQUOTE;
+ else
+ tflag = 0;
+
+ if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && ((sh_syntaxtab[c] & tflag) == 0))
+ {
+ SCOPY_CHAR_I (twochars, '\\', c, string, sindex, string_size);
+ }
+ else if (c == 0)
+ {
+ c = CTLNUL;
+ sindex--; /* add_character: label increments sindex */
+ goto add_character;
+ }
+ else
+ {
+ SCOPY_CHAR_I (twochars, CTLESC, c, string, sindex, string_size);
+ }
+
+ sindex++;
+add_twochars:
+ /* BEFORE jumping here, we need to increment sindex if appropriate */
+ RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size,
+ DEFAULT_ARRAY_SIZE);
+ istring[istring_index++] = twochars[0];
+ istring[istring_index++] = twochars[1];
+ istring[istring_index] = '\0';
+
+ break;
+
+ case '"':
+#if 0
+ if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (word->flags & W_DQUOTE))
+#else
+ if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+#endif
+ goto add_character;
+
+ t_index = ++sindex;
+ temp = string_extract_double_quoted (string, &sindex, 0);
+
+ /* If the quotes surrounded the entire string, then the
+ whole word was quoted. */
+ quoted_state = (t_index == 1 && string[sindex] == '\0')
+ ? WHOLLY_QUOTED
+ : PARTIALLY_QUOTED;
+
+ if (temp && *temp)
+ {
+ tword = alloc_word_desc ();
+ tword->word = temp;
+
+ temp = (char *)NULL;
+
+ has_dollar_at = 0;
+ /* Need to get W_HASQUOTEDNULL flag through this function. */
+ list = expand_word_internal (tword, Q_DOUBLE_QUOTES, 0, &has_dollar_at, (int *)NULL);
+
+ if (list == &expand_word_error || list == &expand_word_fatal)
+ {
+ free (istring);
+ free (string);
+ /* expand_word_internal has already freed temp_word->word
+ for us because of the way it prints error messages. */
+ tword->word = (char *)NULL;
+ dispose_word (tword);
+ return list;
+ }
+
+ dispose_word (tword);
+
+ /* "$@" (a double-quoted dollar-at) expands into nothing,
+ not even a NULL word, when there are no positional
+ parameters. */
+ if (list == 0 && has_dollar_at)
+ {
+ quoted_dollar_at++;
+ break;
+ }
+
+ /* If we get "$@", we know we have expanded something, so we
+ need to remember it for the final split on $IFS. This is
+ a special case; it's the only case where a quoted string
+ can expand into more than one word. It's going to come back
+ from the above call to expand_word_internal as a list with
+ a single word, in which all characters are quoted and
+ separated by blanks. What we want to do is to turn it back
+ into a list for the next piece of code. */
+ if (list)
+ dequote_list (list);
+
+ if (list && list->word && (list->word->flags & W_HASQUOTEDNULL))
+ had_quoted_null = 1;
+
+ if (has_dollar_at)
+ {
+ quoted_dollar_at++;
+ if (contains_dollar_at)
+ *contains_dollar_at = 1;
+ if (expanded_something)
+ *expanded_something = 1;
+ }
+ }
+ else
+ {
+ /* What we have is "". This is a minor optimization. */
+ FREE (temp);
+ list = (WORD_LIST *)NULL;
+ }
+
+ /* The code above *might* return a list (consider the case of "$@",
+ where it returns "$1", "$2", etc.). We can't throw away the
+ rest of the list, and we have to make sure each word gets added
+ as quoted. We test on tresult->next: if it is non-NULL, we
+ quote the whole list, save it to a string with string_list, and
+ add that string. We don't need to quote the results of this
+ (and it would be wrong, since that would quote the separators
+ as well), so we go directly to add_string. */
+ if (list)
+ {
+ if (list->next)
+ {
+#if 0
+ if (quoted_dollar_at && word->flags & W_NOSPLIT2)
+ temp = string_list_internal (quote_list (list), " ");
+ else
+#endif
+ /* Testing quoted_dollar_at makes sure that "$@" is
+ split correctly when $IFS does not contain a space. */
+ temp = quoted_dollar_at
+ ? string_list_dollar_at (list, Q_DOUBLE_QUOTES)
+ : string_list (quote_list (list));
+ dispose_words (list);
+ goto add_string;
+ }
+ else
+ {
+ temp = savestring (list->word->word);
+ tflag = list->word->flags;
+ dispose_words (list);
+
+ /* If the string is not a quoted null string, we want
+ to remove any embedded unquoted CTLNUL characters.
+ We do not want to turn quoted null strings back into
+ the empty string, though. We do this because we
+ want to remove any quoted nulls from expansions that
+ contain other characters. For example, if we have
+ x"$*"y or "x$*y" and there are no positional parameters,
+ the $* should expand into nothing. */
+ /* We use the W_HASQUOTEDNULL flag to differentiate the
+ cases: a quoted null character as above and when
+ CTLNUL is contained in the (non-null) expansion
+ of some variable. We use the had_quoted_null flag to
+ pass the value through this function to its caller. */
+ if ((tflag & W_HASQUOTEDNULL) && QUOTED_NULL (temp) == 0)
+ remove_quoted_nulls (temp); /* XXX */
+ }
+ }
+ else
+ temp = (char *)NULL;
+
+ /* We do not want to add quoted nulls to strings that are only
+ partially quoted; we can throw them away. */
+ if (temp == 0 && quoted_state == PARTIALLY_QUOTED)
+ continue;
+
+ add_quoted_string:
+
+ if (temp)
+ {
+ temp1 = temp;
+ temp = quote_string (temp);
+ free (temp1);
+ goto add_string;
+ }
+ else
+ {
+ /* Add NULL arg. */
+ c = CTLNUL;
+ sindex--; /* add_character: label increments sindex */
+ goto add_character;
+ }
+
+ /* break; */
+
+ case '\'':
+#if 0
+ if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (word->flags & W_DQUOTE))
+#else
+ if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+#endif
+ goto add_character;
+
+ t_index = ++sindex;
+ temp = string_extract_single_quoted (string, &sindex);
+
+ /* If the entire STRING was surrounded by single quotes,
+ then the string is wholly quoted. */
+ quoted_state = (t_index == 1 && string[sindex] == '\0')
+ ? WHOLLY_QUOTED
+ : PARTIALLY_QUOTED;
+
+ /* If all we had was '', it is a null expansion. */
+ if (*temp == '\0')
+ {
+ free (temp);
+ temp = (char *)NULL;
+ }
+ else
+ remove_quoted_escapes (temp); /* ??? */
+
+ /* We do not want to add quoted nulls to strings that are only
+ partially quoted; such nulls are discarded. */
+ if (temp == 0 && (quoted_state == PARTIALLY_QUOTED))
+ continue;
+
+ /* If we have a quoted null expansion, add a quoted NULL to istring. */
+ if (temp == 0)
+ {
+ c = CTLNUL;
+ sindex--; /* add_character: label increments sindex */
+ goto add_character;
+ }
+ else
+ goto add_quoted_string;
+
+ /* break; */
+
+ default:
+ /* This is the fix for " $@ " */
+ add_ifs_character:
+ if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (isexp == 0 && isifs (c)))
+ {
+ if (string[sindex]) /* from old goto dollar_add_string */
+ sindex++;
+ if (c == 0)
+ {
+ c = CTLNUL;
+ goto add_character;
+ }
+ else
+ {
+#if HANDLE_MULTIBYTE
+ if (MB_CUR_MAX > 1)
+ sindex--;
+
+ if (MB_CUR_MAX > 1)
+ {
+ SADD_MBQCHAR_BODY(temp, string, sindex, string_size);
+ }
+ else
+#endif
+ {
+ twochars[0] = CTLESC;
+ twochars[1] = c;
+ goto add_twochars;
+ }
+ }
+ }
+
+ SADD_MBCHAR (temp, string, sindex, string_size);
+
+ add_character:
+ RESIZE_MALLOCED_BUFFER (istring, istring_index, 1, istring_size,
+ DEFAULT_ARRAY_SIZE);
+ istring[istring_index++] = c;
+ istring[istring_index] = '\0';
+
+ /* Next character. */
+ sindex++;
+ }
+ }
+
+finished_with_string:
+ /* OK, we're ready to return. If we have a quoted string, and
+ quoted_dollar_at is not set, we do no splitting at all; otherwise
+ we split on ' '. The routines that call this will handle what to
+ do if nothing has been expanded. */
+
+ /* Partially and wholly quoted strings which expand to the empty
+ string are retained as an empty arguments. Unquoted strings
+ which expand to the empty string are discarded. The single
+ exception is the case of expanding "$@" when there are no
+ positional parameters. In that case, we discard the expansion. */
+
+ /* Because of how the code that handles "" and '' in partially
+ quoted strings works, we need to make ISTRING into a QUOTED_NULL
+ if we saw quoting characters, but the expansion was empty.
+ "" and '' are tossed away before we get to this point when
+ processing partially quoted strings. This makes "" and $xxx""
+ equivalent when xxx is unset. We also look to see whether we
+ saw a quoted null from a ${} expansion and add one back if we
+ need to. */
+
+ /* If we expand to nothing and there were no single or double quotes
+ in the word, we throw it away. Otherwise, we return a NULL word.
+ The single exception is for $@ surrounded by double quotes when
+ there are no positional parameters. In that case, we also throw
+ the word away. */
+
+ if (*istring == '\0')
+ {
+ if (quoted_dollar_at == 0 && (had_quoted_null || quoted_state == PARTIALLY_QUOTED))
+ {
+ istring[0] = CTLNUL;
+ istring[1] = '\0';
+ tword = make_bare_word (istring);
+ tword->flags |= W_HASQUOTEDNULL; /* XXX */
+ list = make_word_list (tword, (WORD_LIST *)NULL);
+ if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+ tword->flags |= W_QUOTED;
+ }
+ /* According to sh, ksh, and Posix.2, if a word expands into nothing
+ and a double-quoted "$@" appears anywhere in it, then the entire
+ word is removed. */
+ else if (quoted_state == UNQUOTED || quoted_dollar_at)
+ list = (WORD_LIST *)NULL;
+#if 0
+ else
+ {
+ tword = make_bare_word (istring);
+ if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+ tword->flags |= W_QUOTED;
+ list = make_word_list (tword, (WORD_LIST *)NULL);
+ }
+#else
+ else
+ list = (WORD_LIST *)NULL;
+#endif
+ }
+ else if (word->flags & W_NOSPLIT)
+ {
+ tword = make_bare_word (istring);
+ if (word->flags & W_ASSIGNMENT)
+ tword->flags |= W_ASSIGNMENT; /* XXX */
+ if (word->flags & W_COMPASSIGN)
+ tword->flags |= W_COMPASSIGN; /* XXX */
+ if (word->flags & W_NOGLOB)
+ tword->flags |= W_NOGLOB; /* XXX */
+ if (word->flags & W_NOEXPAND)
+ tword->flags |= W_NOEXPAND; /* XXX */
+ if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+ tword->flags |= W_QUOTED;
+ if (had_quoted_null)
+ tword->flags |= W_HASQUOTEDNULL;
+ list = make_word_list (tword, (WORD_LIST *)NULL);
+ }
+ else
+ {
+ char *ifs_chars;
+
+ ifs_chars = (quoted_dollar_at || has_dollar_at) ? ifs_value : (char *)NULL;
+
+ /* If we have $@, we need to split the results no matter what. If
+ IFS is unset or NULL, string_list_dollar_at has separated the
+ positional parameters with a space, so we split on space (we have
+ set ifs_chars to " \t\n" above if ifs is unset). If IFS is set,
+ string_list_dollar_at has separated the positional parameters
+ with the first character of $IFS, so we split on $IFS. */
+ if (has_dollar_at && ifs_chars)
+ list = list_string (istring, *ifs_chars ? ifs_chars : " ", 1);
+ else
+ {
+ tword = make_bare_word (istring);
+ if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (quoted_state == WHOLLY_QUOTED))
+ tword->flags |= W_QUOTED;
+ if (word->flags & W_ASSIGNMENT)
+ tword->flags |= W_ASSIGNMENT;
+ if (word->flags & W_COMPASSIGN)
+ tword->flags |= W_COMPASSIGN;
+ if (word->flags & W_NOGLOB)
+ tword->flags |= W_NOGLOB;
+ if (word->flags & W_NOEXPAND)
+ tword->flags |= W_NOEXPAND;
+ if (had_quoted_null)
+ tword->flags |= W_HASQUOTEDNULL; /* XXX */
+ list = make_word_list (tword, (WORD_LIST *)NULL);
+ }
+ }
+
+ free (istring);
+ return (list);
+}
+
+/* **************************************************************** */
+/* */
+/* Functions for Quote Removal */
+/* */
+/* **************************************************************** */
+
+/* Perform quote removal on STRING. If QUOTED > 0, assume we are obeying the
+ backslash quoting rules for within double quotes or a here document. */
+char *
+string_quote_removal (string, quoted)
+ char *string;
+ int quoted;
+{
+ size_t slen;
+ char *r, *result_string, *temp, *send;
+ int sindex, tindex, dquote;
+ unsigned char c;
+ DECLARE_MBSTATE;
+
+ /* The result can be no longer than the original string. */
+ slen = strlen (string);
+ send = string + slen;
+
+ r = result_string = (char *)xmalloc (slen + 1);
+
+ for (dquote = sindex = 0; c = string[sindex];)
+ {
+ switch (c)
+ {
+ case '\\':
+ c = string[++sindex];
+ if (c == 0)
+ {
+ *r++ = '\\';
+ break;
+ }
+ if (((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote) && (sh_syntaxtab[c] & CBSDQUOTE) == 0)
+ *r++ = '\\';
+ /* FALLTHROUGH */
+
+ default:
+ SCOPY_CHAR_M (r, string, send, sindex);
+ break;
+
+ case '\'':
+ if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote)
+ {
+ *r++ = c;
+ sindex++;
+ break;
+ }
+ tindex = sindex + 1;
+ temp = string_extract_single_quoted (string, &tindex);
+ if (temp)
+ {
+ strcpy (r, temp);
+ r += strlen (r);
+ free (temp);
+ }
+ sindex = tindex;
+ break;
+
+ case '"':
+ dquote = 1 - dquote;
+ sindex++;
+ break;
+ }
+ }
+ *r = '\0';
+ return (result_string);
+}
+
+#if 0
+/* UNUSED */
+/* Perform quote removal on word WORD. This allocates and returns a new
+ WORD_DESC *. */
+WORD_DESC *
+word_quote_removal (word, quoted)
+ WORD_DESC *word;
+ int quoted;
+{
+ WORD_DESC *w;
+ char *t;
+
+ t = string_quote_removal (word->word, quoted);
+ w = alloc_word_desc ();
+ w->word = t ? t : savestring ("");
+ return (w);
+}
+
+/* Perform quote removal on all words in LIST. If QUOTED is non-zero,
+ the members of the list are treated as if they are surrounded by
+ double quotes. Return a new list, or NULL if LIST is NULL. */
+WORD_LIST *
+word_list_quote_removal (list, quoted)
+ WORD_LIST *list;
+ int quoted;
+{
+ WORD_LIST *result, *t, *tresult, *e;
+
+ for (t = list, result = (WORD_LIST *)NULL; t; t = t->next)
+ {
+ tresult = make_word_list (word_quote_removal (t->word, quoted), (WORD_LIST *)NULL);
+#if 0
+ result = (WORD_LIST *) list_append (result, tresult);
+#else
+ if (result == 0)
+ result = e = tresult;
+ else
+ {
+ e->next = tresult;
+ while (e->next)
+ e = e->next;
+ }
+#endif
+ }
+ return (result);
+}
+#endif
+
+/*******************************************
+ * *
+ * Functions to perform word splitting *
+ * *
+ *******************************************/
+
+void
+setifs (v)
+ SHELL_VAR *v;
+{
+ char *t;
+ unsigned char uc;
+
+ ifs_var = v;
+ ifs_value = (v && value_cell (v)) ? value_cell (v) : " \t\n";
+
+ /* Should really merge ifs_cmap with sh_syntaxtab. XXX - doesn't yet
+ handle multibyte chars in IFS */
+ memset (ifs_cmap, '\0', sizeof (ifs_cmap));
+ for (t = ifs_value ; t && *t; t++)
+ {
+ uc = *t;
+ ifs_cmap[uc] = 1;
+ }
+
+#if defined (HANDLE_MULTIBYTE)
+ if (ifs_value == 0)
+ {
+ ifs_firstc[0] = '\0';
+ ifs_firstc_len = 1;
+ }
+ else
+ {
+ size_t ifs_len;
+ ifs_len = strnlen (ifs_value, MB_CUR_MAX);
+ ifs_firstc_len = MBLEN (ifs_value, ifs_len);
+ if (ifs_firstc_len == 1 || ifs_firstc_len == 0 || MB_INVALIDCH (ifs_firstc_len))
+ {
+ ifs_firstc[0] = ifs_value[0];
+ ifs_firstc[1] = '\0';
+ ifs_firstc_len = 1;
+ }
+ else
+ memcpy (ifs_firstc, ifs_value, ifs_firstc_len);
+ }
+#else
+ ifs_firstc = ifs_value ? *ifs_value : 0;
+#endif
+}
+
+char *
+getifs ()
+{
+ return ifs_value;
+}
+
+/* This splits a single word into a WORD LIST on $IFS, but only if the word
+ is not quoted. list_string () performs quote removal for us, even if we
+ don't do any splitting. */
+WORD_LIST *
+word_split (w, ifs_chars)
+ WORD_DESC *w;
+ char *ifs_chars;
+{
+ WORD_LIST *result;
+
+ if (w)
+ {
+ char *xifs;
+
+ xifs = ((w->flags & W_QUOTED) || ifs_chars == 0) ? "" : ifs_chars;
+ result = list_string (w->word, xifs, w->flags & W_QUOTED);
+ }
+ else
+ result = (WORD_LIST *)NULL;
+
+ return (result);
+}
+
+/* Perform word splitting on LIST and return the RESULT. It is possible
+ to return (WORD_LIST *)NULL. */
+static WORD_LIST *
+word_list_split (list)
+ WORD_LIST *list;
+{
+ WORD_LIST *result, *t, *tresult, *e;
+
+ for (t = list, result = (WORD_LIST *)NULL; t; t = t->next)
+ {
+ tresult = word_split (t->word, ifs_value);
+ if (result == 0)
+ result = e = tresult;
+ else
+ {
+ e->next = tresult;
+ while (e->next)
+ e = e->next;
+ }
+ }
+ return (result);
+}
+
+/**************************************************
+ * *
+ * Functions to expand an entire WORD_LIST *
+ * *
+ **************************************************/
+
+/* Do any word-expansion-specific cleanup and jump to top_level */
+static void
+exp_jump_to_top_level (v)
+ int v;
+{
+ set_pipestatus_from_exit (last_command_exit_value);
+
+ /* Cleanup code goes here. */
+ expand_no_split_dollar_star = 0; /* XXX */
+ expanding_redir = 0;
+ assigning_in_environment = 0;
+
+ if (parse_and_execute_level == 0)
+ top_level_cleanup (); /* from sig.c */
+
+ jump_to_top_level (v);
+}
+
+/* Put NLIST (which is a WORD_LIST * of only one element) at the front of
+ ELIST, and set ELIST to the new list. */
+#define PREPEND_LIST(nlist, elist) \
+ do { nlist->next = elist; elist = nlist; } while (0)
+
+/* Separate out any initial variable assignments from TLIST. If set -k has
+ been executed, remove all assignment statements from TLIST. Initial
+ variable assignments and other environment assignments are placed
+ on SUBST_ASSIGN_VARLIST. */
+static WORD_LIST *
+separate_out_assignments (tlist)
+ WORD_LIST *tlist;
+{
+ register WORD_LIST *vp, *lp;
+
+ if (tlist == 0)
+ return ((WORD_LIST *)NULL);
+
+ if (subst_assign_varlist)
+ dispose_words (subst_assign_varlist); /* Clean up after previous error */
+
+ subst_assign_varlist = (WORD_LIST *)NULL;
+ vp = lp = tlist;
+
+ /* Separate out variable assignments at the start of the command.
+ Loop invariant: vp->next == lp
+ Loop postcondition:
+ lp = list of words left after assignment statements skipped
+ tlist = original list of words
+ */
+ while (lp && (lp->word->flags & W_ASSIGNMENT))
+ {
+ vp = lp;
+ lp = lp->next;
+ }
+
+ /* If lp != tlist, we have some initial assignment statements.
+ We make SUBST_ASSIGN_VARLIST point to the list of assignment
+ words and TLIST point to the remaining words. */
+ if (lp != tlist)
+ {
+ subst_assign_varlist = tlist;
+ /* ASSERT(vp->next == lp); */
+ vp->next = (WORD_LIST *)NULL; /* terminate variable list */
+ tlist = lp; /* remainder of word list */
+ }
+
+ /* vp == end of variable list */
+ /* tlist == remainder of original word list without variable assignments */
+ if (!tlist)
+ /* All the words in tlist were assignment statements */
+ return ((WORD_LIST *)NULL);
+
+ /* ASSERT(tlist != NULL); */
+ /* ASSERT((tlist->word->flags & W_ASSIGNMENT) == 0); */
+
+ /* If the -k option is in effect, we need to go through the remaining
+ words, separate out the assignment words, and place them on
+ SUBST_ASSIGN_VARLIST. */
+ if (place_keywords_in_env)
+ {
+ WORD_LIST *tp; /* tp == running pointer into tlist */
+
+ tp = tlist;
+ lp = tlist->next;
+
+ /* Loop Invariant: tp->next == lp */
+ /* Loop postcondition: tlist == word list without assignment statements */
+ while (lp)
+ {
+ if (lp->word->flags & W_ASSIGNMENT)
+ {
+ /* Found an assignment statement, add this word to end of
+ subst_assign_varlist (vp). */
+ if (!subst_assign_varlist)
+ subst_assign_varlist = vp = lp;
+ else
+ {
+ vp->next = lp;
+ vp = lp;
+ }
+
+ /* Remove the word pointed to by LP from TLIST. */
+ tp->next = lp->next;
+ /* ASSERT(vp == lp); */
+ lp->next = (WORD_LIST *)NULL;
+ lp = tp->next;
+ }
+ else
+ {
+ tp = lp;
+ lp = lp->next;
+ }
+ }
+ }
+ return (tlist);
+}
+
+#define WEXP_VARASSIGN 0x001
+#define WEXP_BRACEEXP 0x002
+#define WEXP_TILDEEXP 0x004
+#define WEXP_PARAMEXP 0x008
+#define WEXP_PATHEXP 0x010
+
+/* All of the expansions, including variable assignments at the start of
+ the list. */
+#define WEXP_ALL (WEXP_VARASSIGN|WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP|WEXP_PATHEXP)
+
+/* All of the expansions except variable assignments at the start of
+ the list. */
+#define WEXP_NOVARS (WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP|WEXP_PATHEXP)
+
+/* All of the `shell expansions': brace expansion, tilde expansion, parameter
+ expansion, command substitution, arithmetic expansion, word splitting, and
+ quote removal. */
+#define WEXP_SHELLEXP (WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP)
+
+/* Take the list of words in LIST and do the various substitutions. Return
+ a new list of words which is the expanded list, and without things like
+ variable assignments. */
+
+WORD_LIST *
+expand_words (list)
+ WORD_LIST *list;
+{
+ return (expand_word_list_internal (list, WEXP_ALL));
+}
+
+/* Same as expand_words (), but doesn't hack variable or environment
+ variables. */
+WORD_LIST *
+expand_words_no_vars (list)
+ WORD_LIST *list;
+{
+ return (expand_word_list_internal (list, WEXP_NOVARS));
+}
+
+WORD_LIST *
+expand_words_shellexp (list)
+ WORD_LIST *list;
+{
+ return (expand_word_list_internal (list, WEXP_SHELLEXP));
+}
+
+static WORD_LIST *
+glob_expand_word_list (tlist, eflags)
+ WORD_LIST *tlist;
+ int eflags;
+{
+ char **glob_array, *temp_string;
+ register int glob_index;
+ WORD_LIST *glob_list, *output_list, *disposables, *next;
+ WORD_DESC *tword;
+
+ output_list = disposables = (WORD_LIST *)NULL;
+ glob_array = (char **)NULL;
+ while (tlist)
+ {
+ /* For each word, either globbing is attempted or the word is
+ added to orig_list. If globbing succeeds, the results are
+ added to orig_list and the word (tlist) is added to the list
+ of disposable words. If globbing fails and failed glob
+ expansions are left unchanged (the shell default), the
+ original word is added to orig_list. If globbing fails and
+ failed glob expansions are removed, the original word is
+ added to the list of disposable words. orig_list ends up
+ in reverse order and requires a call to REVERSE_LIST to
+ be set right. After all words are examined, the disposable
+ words are freed. */
+ next = tlist->next;
+
+ /* If the word isn't an assignment and contains an unquoted
+ pattern matching character, then glob it. */
+ if ((tlist->word->flags & W_NOGLOB) == 0 &&
+ unquoted_glob_pattern_p (tlist->word->word))
+ {
+ glob_array = shell_glob_filename (tlist->word->word);
+
+ /* Handle error cases.
+ I don't think we should report errors like "No such file
+ or directory". However, I would like to report errors
+ like "Read failed". */
+
+ if (glob_array == 0 || GLOB_FAILED (glob_array))
+ {
+ glob_array = (char **)xmalloc (sizeof (char *));
+ glob_array[0] = (char *)NULL;
+ }
+
+ /* Dequote the current word in case we have to use it. */
+ if (glob_array[0] == NULL)
+ {
+ temp_string = dequote_string (tlist->word->word);
+ free (tlist->word->word);
+ tlist->word->word = temp_string;
+ }
+
+ /* Make the array into a word list. */
+ glob_list = (WORD_LIST *)NULL;
+ for (glob_index = 0; glob_array[glob_index]; glob_index++)
+ {
+ tword = make_bare_word (glob_array[glob_index]);
+ tword->flags |= W_GLOBEXP; /* XXX */
+ glob_list = make_word_list (tword, glob_list);
+ }
+
+ if (glob_list)
+ {
+ output_list = (WORD_LIST *)list_append (glob_list, output_list);
+ PREPEND_LIST (tlist, disposables);
+ }
+ else if (fail_glob_expansion != 0)
+ {
+ report_error (_("no match: %s"), tlist->word->word);
+ exp_jump_to_top_level (DISCARD);
+ }
+ else if (allow_null_glob_expansion == 0)
+ {
+ /* Failed glob expressions are left unchanged. */
+ PREPEND_LIST (tlist, output_list);
+ }
+ else
+ {
+ /* Failed glob expressions are removed. */
+ PREPEND_LIST (tlist, disposables);
+ }
+ }
+ else
+ {
+ /* Dequote the string. */
+ temp_string = dequote_string (tlist->word->word);
+ free (tlist->word->word);
+ tlist->word->word = temp_string;
+ PREPEND_LIST (tlist, output_list);
+ }
+
+ strvec_dispose (glob_array);
+ glob_array = (char **)NULL;
+
+ tlist = next;
+ }
+
+ if (disposables)
+ dispose_words (disposables);
+
+ if (output_list)
+ output_list = REVERSE_LIST (output_list, WORD_LIST *);
+
+ return (output_list);
+}
+
+#if defined (BRACE_EXPANSION)
+static WORD_LIST *
+brace_expand_word_list (tlist, eflags)
+ WORD_LIST *tlist;
+ int eflags;
+{
+ register char **expansions;
+ char *temp_string;
+ WORD_LIST *disposables, *output_list, *next;
+ WORD_DESC *w;
+ int eindex;
+
+ for (disposables = output_list = (WORD_LIST *)NULL; tlist; tlist = next)
+ {
+ next = tlist->next;
+
+ if ((tlist->word->flags & (W_COMPASSIGN|W_ASSIGNARG)) == (W_COMPASSIGN|W_ASSIGNARG))
+ {
+/*itrace("brace_expand_word_list: %s: W_COMPASSIGN|W_ASSIGNARG", tlist->word->word);*/
+ PREPEND_LIST (tlist, output_list);
+ continue;
+ }
+
+ /* Only do brace expansion if the word has a brace character. If
+ not, just add the word list element to BRACES and continue. In
+ the common case, at least when running shell scripts, this will
+ degenerate to a bunch of calls to `mbschr', and then what is
+ basically a reversal of TLIST into BRACES, which is corrected
+ by a call to REVERSE_LIST () on BRACES when the end of TLIST
+ is reached. */
+ if (mbschr (tlist->word->word, LBRACE))
+ {
+ expansions = brace_expand (tlist->word->word);
+
+ for (eindex = 0; temp_string = expansions[eindex]; eindex++)
+ {
+ w = make_word (temp_string);
+ /* If brace expansion didn't change the word, preserve
+ the flags. We may want to preserve the flags
+ unconditionally someday -- XXX */
+ if (STREQ (temp_string, tlist->word->word))
+ w->flags = tlist->word->flags;
+ output_list = make_word_list (w, output_list);
+ free (expansions[eindex]);
+ }
+ free (expansions);
+
+ /* Add TLIST to the list of words to be freed after brace
+ expansion has been performed. */
+ PREPEND_LIST (tlist, disposables);
+ }
+ else
+ PREPEND_LIST (tlist, output_list);
+ }
+
+ if (disposables)
+ dispose_words (disposables);
+
+ if (output_list)
+ output_list = REVERSE_LIST (output_list, WORD_LIST *);
+
+ return (output_list);
+}
+#endif
+
+#if defined (ARRAY_VARS)
+/* Take WORD, a compound associative array assignment, and internally run
+ 'declare -A w', where W is the variable name portion of WORD. */
+static int
+make_internal_declare (word, option)
+ char *word;
+ char *option;
+{
+ int t;
+ WORD_LIST *wl;
+ WORD_DESC *w;
+
+ w = make_word (word);
+
+ t = assignment (w->word, 0);
+ w->word[t] = '\0';
+
+ wl = make_word_list (w, (WORD_LIST *)NULL);
+ wl = make_word_list (make_word (option), wl);
+
+ return (declare_builtin (wl));
+}
+#endif
+
+static WORD_LIST *
+shell_expand_word_list (tlist, eflags)
+ WORD_LIST *tlist;
+ int eflags;
+{
+ WORD_LIST *expanded, *orig_list, *new_list, *next, *temp_list;
+ int expanded_something, has_dollar_at;
+ char *temp_string;
+
+ /* We do tilde expansion all the time. This is what 1003.2 says. */
+ new_list = (WORD_LIST *)NULL;
+ for (orig_list = tlist; tlist; tlist = next)
+ {
+ temp_string = tlist->word->word;
+
+ next = tlist->next;
+
+#if defined (ARRAY_VARS)
+ /* If this is a compound array assignment to a builtin that accepts
+ such assignments (e.g., `declare'), take the assignment and perform
+ it separately, handling the semantics of declarations inside shell
+ functions. This avoids the double-evaluation of such arguments,
+ because `declare' does some evaluation of compound assignments on
+ its own. */
+ if ((tlist->word->flags & (W_COMPASSIGN|W_ASSIGNARG)) == (W_COMPASSIGN|W_ASSIGNARG))
+ {
+ int t;
+
+ if (tlist->word->flags & W_ASSIGNASSOC)
+ make_internal_declare (tlist->word->word, "-A");
+
+ t = do_word_assignment (tlist->word);
+ if (t == 0)
+ {
+ last_command_exit_value = EXECUTION_FAILURE;
+ exp_jump_to_top_level (DISCARD);
+ }
+
+ /* Now transform the word as ksh93 appears to do and go on */
+ t = assignment (tlist->word->word, 0);
+ tlist->word->word[t] = '\0';
+ tlist->word->flags &= ~(W_ASSIGNMENT|W_NOSPLIT|W_COMPASSIGN|W_ASSIGNARG|W_ASSIGNASSOC);
+ }
+#endif
+
+ expanded_something = 0;
+ expanded = expand_word_internal
+ (tlist->word, 0, 0, &has_dollar_at, &expanded_something);
+
+ if (expanded == &expand_word_error || expanded == &expand_word_fatal)
+ {
+ /* By convention, each time this error is returned,
+ tlist->word->word has already been freed. */
+ tlist->word->word = (char *)NULL;
+
+ /* Dispose our copy of the original list. */
+ dispose_words (orig_list);
+ /* Dispose the new list we're building. */
+ dispose_words (new_list);
+
+ last_command_exit_value = EXECUTION_FAILURE;
+ if (expanded == &expand_word_error)
+ exp_jump_to_top_level (DISCARD);
+ else
+ exp_jump_to_top_level (FORCE_EOF);
+ }
+
+ /* Don't split words marked W_NOSPLIT. */
+ if (expanded_something && (tlist->word->flags & W_NOSPLIT) == 0)
+ {
+ temp_list = word_list_split (expanded);
+ dispose_words (expanded);
+ }
+ else
+ {
+ /* If no parameter expansion, command substitution, process
+ substitution, or arithmetic substitution took place, then
+ do not do word splitting. We still have to remove quoted
+ null characters from the result. */
+ word_list_remove_quoted_nulls (expanded);
+ temp_list = expanded;
+ }
+
+ expanded = REVERSE_LIST (temp_list, WORD_LIST *);
+ new_list = (WORD_LIST *)list_append (expanded, new_list);
+ }
+
+ if (orig_list)
+ dispose_words (orig_list);
+
+ if (new_list)
+ new_list = REVERSE_LIST (new_list, WORD_LIST *);
+
+ return (new_list);
+}
+
+/* The workhorse for expand_words () and expand_words_no_vars ().
+ First arg is LIST, a WORD_LIST of words.
+ Second arg EFLAGS is a flags word controlling which expansions are
+ performed.
+
+ This does all of the substitutions: brace expansion, tilde expansion,
+ parameter expansion, command substitution, arithmetic expansion,
+ process substitution, word splitting, and pathname expansion, according
+ to the bits set in EFLAGS. Words with the W_QUOTED or W_NOSPLIT bits
+ set, or for which no expansion is done, do not undergo word splitting.
+ Words with the W_NOGLOB bit set do not undergo pathname expansion. */
+static WORD_LIST *
+expand_word_list_internal (list, eflags)
+ WORD_LIST *list;
+ int eflags;
+{
+ WORD_LIST *new_list, *temp_list;
+ int tint;
+
+ if (list == 0)
+ return ((WORD_LIST *)NULL);
+
+ garglist = new_list = copy_word_list (list);
+ if (eflags & WEXP_VARASSIGN)
+ {
+ garglist = new_list = separate_out_assignments (new_list);
+ if (new_list == 0)
+ {
+ if (subst_assign_varlist)
+ {
+ /* All the words were variable assignments, so they are placed
+ into the shell's environment. */
+ for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next)
+ {
+ this_command_name = (char *)NULL; /* no arithmetic errors */
+ tint = do_word_assignment (temp_list->word);
+ /* Variable assignment errors in non-interactive shells
+ running in Posix.2 mode cause the shell to exit. */
+ if (tint == 0)
+ {
+ last_command_exit_value = EXECUTION_FAILURE;
+ if (interactive_shell == 0 && posixly_correct)
+ exp_jump_to_top_level (FORCE_EOF);
+ else
+ exp_jump_to_top_level (DISCARD);
+ }
+ }
+ dispose_words (subst_assign_varlist);
+ subst_assign_varlist = (WORD_LIST *)NULL;
+ }
+ return ((WORD_LIST *)NULL);
+ }
+ }
+
+ /* Begin expanding the words that remain. The expansions take place on
+ things that aren't really variable assignments. */
+
+#if defined (BRACE_EXPANSION)
+ /* Do brace expansion on this word if there are any brace characters
+ in the string. */
+ if ((eflags & WEXP_BRACEEXP) && brace_expansion && new_list)
+ new_list = brace_expand_word_list (new_list, eflags);
+#endif /* BRACE_EXPANSION */
+
+ /* Perform the `normal' shell expansions: tilde expansion, parameter and
+ variable substitution, command substitution, arithmetic expansion,
+ and word splitting. */
+ new_list = shell_expand_word_list (new_list, eflags);
+
+ /* Okay, we're almost done. Now let's just do some filename
+ globbing. */
+ if (new_list)
+ {
+ if ((eflags & WEXP_PATHEXP) && disallow_filename_globbing == 0)
+ /* Glob expand the word list unless globbing has been disabled. */
+ new_list = glob_expand_word_list (new_list, eflags);
+ else
+ /* Dequote the words, because we're not performing globbing. */
+ new_list = dequote_list (new_list);
+ }
+
+ if ((eflags & WEXP_VARASSIGN) && subst_assign_varlist)
+ {
+ sh_wassign_func_t *assign_func;
+
+ /* If the remainder of the words expand to nothing, Posix.2 requires
+ that the variable and environment assignments affect the shell's
+ environment. */
+ assign_func = new_list ? assign_in_env : do_word_assignment;
+ tempenv_assign_error = 0;
+
+ for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next)
+ {
+ this_command_name = (char *)NULL;
+ assigning_in_environment = (assign_func == assign_in_env);
+ tint = (*assign_func) (temp_list->word);
+ assigning_in_environment = 0;
+ /* Variable assignment errors in non-interactive shells running
+ in Posix.2 mode cause the shell to exit. */
+ if (tint == 0)
+ {
+ if (assign_func == do_word_assignment)
+ {
+ last_command_exit_value = EXECUTION_FAILURE;
+ if (interactive_shell == 0 && posixly_correct)
+ exp_jump_to_top_level (FORCE_EOF);
+ else
+ exp_jump_to_top_level (DISCARD);
+ }
+ else
+ tempenv_assign_error++;
+ }
+ }
+
+ dispose_words (subst_assign_varlist);
+ subst_assign_varlist = (WORD_LIST *)NULL;
+ }
+
+#if 0
+ tint = list_length (new_list) + 1;
+ RESIZE_MALLOCED_BUFFER (glob_argv_flags, 0, tint, glob_argv_flags_size, 16);
+ for (tint = 0, temp_list = new_list; temp_list; temp_list = temp_list->next)
+ glob_argv_flags[tint++] = (temp_list->word->flags & W_GLOBEXP) ? '1' : '0';
+ glob_argv_flags[tint] = '\0';
+#endif
+
+ return (new_list);
+}
-BUILD_DIR=/usr/local/build/chet/bash/bash-current
+BUILD_DIR=/usr/local/build/bash/bash-current
THIS_SH=$BUILD_DIR/bash
PATH=$PATH:$BUILD_DIR
--- /dev/null
+BUILD_DIR=/usr/local/build/chet/bash/bash-current
+THIS_SH=$BUILD_DIR/bash
+PATH=$PATH:$BUILD_DIR
+
+export THIS_SH PATH
+
+rm -f /tmp/xx
+
+/bin/sh "$@"
rv = get_random_number ();
last_random_value = rv;
-fprintf(stderr, "get_random: rv = %d\n", rv);
p = itos (rv);
FREE (value_cell (var));
--- /dev/null
+/* variables.c -- Functions for hacking shell variables. */
+
+/* Copyright (C) 1987-2009 Free Software Foundation, Inc.
+
+ This file is part of GNU Bash, the Bourne Again SHell.
+
+ Bash is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bash is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bash. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#include "bashtypes.h"
+#include "posixstat.h"
+#include "posixtime.h"
+
+#if defined (__QNX__)
+# if defined (__QNXNTO__)
+# include <sys/netmgr.h>
+# else
+# include <sys/vc.h>
+# endif /* !__QNXNTO__ */
+#endif /* __QNX__ */
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include "chartypes.h"
+#if defined (HAVE_PWD_H)
+# include <pwd.h>
+#endif
+#include "bashansi.h"
+#include "bashintl.h"
+
+#define NEED_XTRACE_SET_DECL
+
+#include "shell.h"
+#include "flags.h"
+#include "execute_cmd.h"
+#include "findcmd.h"
+#include "mailcheck.h"
+#include "input.h"
+#include "hashcmd.h"
+#include "pathexp.h"
+#include "alias.h"
+
+#include "builtins/getopt.h"
+#include "builtins/common.h"
+
+#if defined (READLINE)
+# include "bashline.h"
+# include <readline/readline.h>
+#else
+# include <tilde/tilde.h>
+#endif
+
+#if defined (HISTORY)
+# include "bashhist.h"
+# include <readline/history.h>
+#endif /* HISTORY */
+
+#if defined (PROGRAMMABLE_COMPLETION)
+# include "pcomplete.h"
+#endif
+
+#define TEMPENV_HASH_BUCKETS 4 /* must be power of two */
+
+#define ifsname(s) ((s)[0] == 'I' && (s)[1] == 'F' && (s)[2] == 'S' && (s)[3] == '\0')
+
+extern char **environ;
+
+/* Variables used here and defined in other files. */
+extern int posixly_correct;
+extern int line_number;
+extern int subshell_environment, indirection_level, subshell_level;
+extern int build_version, patch_level;
+extern int expanding_redir;
+extern char *dist_version, *release_status;
+extern char *shell_name;
+extern char *primary_prompt, *secondary_prompt;
+extern char *current_host_name;
+extern sh_builtin_func_t *this_shell_builtin;
+extern SHELL_VAR *this_shell_function;
+extern char *the_printed_command_except_trap;
+extern char *this_command_name;
+extern char *command_execution_string;
+extern time_t shell_start_time;
+extern int assigning_in_environment;
+extern int executing_builtin;
+
+#if defined (READLINE)
+extern int no_line_editing;
+extern int perform_hostname_completion;
+#endif
+
+/* The list of shell variables that the user has created at the global
+ scope, or that came from the environment. */
+VAR_CONTEXT *global_variables = (VAR_CONTEXT *)NULL;
+
+/* The current list of shell variables, including function scopes */
+VAR_CONTEXT *shell_variables = (VAR_CONTEXT *)NULL;
+
+/* The list of shell functions that the user has created, or that came from
+ the environment. */
+HASH_TABLE *shell_functions = (HASH_TABLE *)NULL;
+
+#if defined (DEBUGGER)
+/* The table of shell function definitions that the user defined or that
+ came from the environment. */
+HASH_TABLE *shell_function_defs = (HASH_TABLE *)NULL;
+#endif
+
+/* The current variable context. This is really a count of how deep into
+ executing functions we are. */
+int variable_context = 0;
+
+/* The set of shell assignments which are made only in the environment
+ for a single command. */
+HASH_TABLE *temporary_env = (HASH_TABLE *)NULL;
+
+/* Set to non-zero if an assignment error occurs while putting variables
+ into the temporary environment. */
+int tempenv_assign_error;
+
+/* Some funky variables which are known about specially. Here is where
+ "$*", "$1", and all the cruft is kept. */
+char *dollar_vars[10];
+WORD_LIST *rest_of_args = (WORD_LIST *)NULL;
+
+/* The value of $$. */
+pid_t dollar_dollar_pid;
+
+/* Non-zero means that we have to remake EXPORT_ENV. */
+int array_needs_making = 1;
+
+/* The number of times BASH has been executed. This is set
+ by initialize_variables (). */
+int shell_level = 0;
+
+/* An array which is passed to commands as their environment. It is
+ manufactured from the union of the initial environment and the
+ shell variables that are marked for export. */
+char **export_env = (char **)NULL;
+static int export_env_index;
+static int export_env_size;
+
+#if defined (READLINE)
+static int winsize_assignment; /* currently assigning to LINES or COLUMNS */
+static int winsize_assigned; /* assigned to LINES or COLUMNS */
+#endif
+
+/* Some forward declarations. */
+static void create_variable_tables __P((void));
+
+static void set_machine_vars __P((void));
+static void set_home_var __P((void));
+static void set_shell_var __P((void));
+static char *get_bash_name __P((void));
+static void initialize_shell_level __P((void));
+static void uidset __P((void));
+#if defined (ARRAY_VARS)
+static void make_vers_array __P((void));
+#endif
+
+static SHELL_VAR *null_assign __P((SHELL_VAR *, char *, arrayind_t, char *));
+#if defined (ARRAY_VARS)
+static SHELL_VAR *null_array_assign __P((SHELL_VAR *, char *, arrayind_t, char *));
+#endif
+static SHELL_VAR *get_self __P((SHELL_VAR *));
+
+#if defined (ARRAY_VARS)
+static SHELL_VAR *init_dynamic_array_var __P((char *, sh_var_value_func_t *, sh_var_assign_func_t *, int));
+static SHELL_VAR *init_dynamic_assoc_var __P((char *, sh_var_value_func_t *, sh_var_assign_func_t *, int));
+#endif
+
+static SHELL_VAR *assign_seconds __P((SHELL_VAR *, char *, arrayind_t, char *));
+static SHELL_VAR *get_seconds __P((SHELL_VAR *));
+static SHELL_VAR *init_seconds_var __P((void));
+
+static int brand __P((void));
+static void sbrand __P((unsigned long)); /* set bash random number generator. */
+static void seedrand __P((void)); /* seed generator randomly */
+static SHELL_VAR *assign_random __P((SHELL_VAR *, char *, arrayind_t, char *));
+static SHELL_VAR *get_random __P((SHELL_VAR *));
+
+static SHELL_VAR *assign_lineno __P((SHELL_VAR *, char *, arrayind_t, char *));
+static SHELL_VAR *get_lineno __P((SHELL_VAR *));
+
+static SHELL_VAR *assign_subshell __P((SHELL_VAR *, char *, arrayind_t, char *));
+static SHELL_VAR *get_subshell __P((SHELL_VAR *));
+
+static SHELL_VAR *get_bashpid __P((SHELL_VAR *));
+
+#if defined (HISTORY)
+static SHELL_VAR *get_histcmd __P((SHELL_VAR *));
+#endif
+
+#if defined (READLINE)
+static SHELL_VAR *get_comp_wordbreaks __P((SHELL_VAR *));
+static SHELL_VAR *assign_comp_wordbreaks __P((SHELL_VAR *, char *, arrayind_t, char *));
+#endif
+
+#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS)
+static SHELL_VAR *assign_dirstack __P((SHELL_VAR *, char *, arrayind_t, char *));
+static SHELL_VAR *get_dirstack __P((SHELL_VAR *));
+#endif
+
+#if defined (ARRAY_VARS)
+static SHELL_VAR *get_groupset __P((SHELL_VAR *));
+
+static SHELL_VAR *build_hashcmd __P((SHELL_VAR *));
+static SHELL_VAR *get_hashcmd __P((SHELL_VAR *));
+static SHELL_VAR *assign_hashcmd __P((SHELL_VAR *, char *, arrayind_t, char *));
+# if defined (ALIAS)
+static SHELL_VAR *build_aliasvar __P((SHELL_VAR *));
+static SHELL_VAR *get_aliasvar __P((SHELL_VAR *));
+static SHELL_VAR *assign_aliasvar __P((SHELL_VAR *, char *, arrayind_t, char *));
+# endif
+#endif
+
+static SHELL_VAR *get_funcname __P((SHELL_VAR *));
+static SHELL_VAR *init_funcname_var __P((void));
+
+static void initialize_dynamic_variables __P((void));
+
+static SHELL_VAR *hash_lookup __P((const char *, HASH_TABLE *));
+static SHELL_VAR *new_shell_variable __P((const char *));
+static SHELL_VAR *make_new_variable __P((const char *, HASH_TABLE *));
+static SHELL_VAR *bind_variable_internal __P((const char *, char *, HASH_TABLE *, int, int));
+
+static void dispose_variable_value __P((SHELL_VAR *));
+static void free_variable_hash_data __P((PTR_T));
+
+static VARLIST *vlist_alloc __P((int));
+static VARLIST *vlist_realloc __P((VARLIST *, int));
+static void vlist_add __P((VARLIST *, SHELL_VAR *, int));
+
+static void flatten __P((HASH_TABLE *, sh_var_map_func_t *, VARLIST *, int));
+
+static int qsort_var_comp __P((SHELL_VAR **, SHELL_VAR **));
+
+static SHELL_VAR **vapply __P((sh_var_map_func_t *));
+static SHELL_VAR **fapply __P((sh_var_map_func_t *));
+
+static int visible_var __P((SHELL_VAR *));
+static int visible_and_exported __P((SHELL_VAR *));
+static int export_environment_candidate __P((SHELL_VAR *));
+static int local_and_exported __P((SHELL_VAR *));
+static int variable_in_context __P((SHELL_VAR *));
+#if defined (ARRAY_VARS)
+static int visible_array_vars __P((SHELL_VAR *));
+#endif
+
+static SHELL_VAR *bind_tempenv_variable __P((const char *, char *));
+static void push_temp_var __P((PTR_T));
+static void propagate_temp_var __P((PTR_T));
+static void dispose_temporary_env __P((sh_free_func_t *));
+
+static inline char *mk_env_string __P((const char *, const char *));
+static char **make_env_array_from_var_list __P((SHELL_VAR **));
+static char **make_var_export_array __P((VAR_CONTEXT *));
+static char **make_func_export_array __P((void));
+static void add_temp_array_to_env __P((char **, int, int));
+
+static int n_shell_variables __P((void));
+static int set_context __P((SHELL_VAR *));
+
+static void push_func_var __P((PTR_T));
+static void push_exported_var __P((PTR_T));
+
+static inline int find_special_var __P((const char *));
+
+static void
+create_variable_tables ()
+{
+ if (shell_variables == 0)
+ {
+ shell_variables = global_variables = new_var_context ((char *)NULL, 0);
+ shell_variables->scope = 0;
+ shell_variables->table = hash_create (0);
+ }
+
+ if (shell_functions == 0)
+ shell_functions = hash_create (0);
+
+#if defined (DEBUGGER)
+ if (shell_function_defs == 0)
+ shell_function_defs = hash_create (0);
+#endif
+}
+
+/* Initialize the shell variables from the current environment.
+ If PRIVMODE is nonzero, don't import functions from ENV or
+ parse $SHELLOPTS. */
+void
+initialize_shell_variables (env, privmode)
+ char **env;
+ int privmode;
+{
+ char *name, *string, *temp_string;
+ int c, char_index, string_index, string_length;
+ SHELL_VAR *temp_var;
+
+ create_variable_tables ();
+
+ for (string_index = 0; string = env[string_index++]; )
+ {
+ char_index = 0;
+ name = string;
+ while ((c = *string++) && c != '=')
+ ;
+ if (string[-1] == '=')
+ char_index = string - name - 1;
+
+ /* If there are weird things in the environment, like `=xxx' or a
+ string without an `=', just skip them. */
+ if (char_index == 0)
+ continue;
+
+ /* ASSERT(name[char_index] == '=') */
+ name[char_index] = '\0';
+ /* Now, name = env variable name, string = env variable value, and
+ char_index == strlen (name) */
+
+ temp_var = (SHELL_VAR *)NULL;
+
+ /* If exported function, define it now. Don't import functions from
+ the environment in privileged mode. */
+ if (privmode == 0 && read_but_dont_execute == 0 && STREQN ("() {", string, 4))
+ {
+ string_length = strlen (string);
+ temp_string = (char *)xmalloc (3 + string_length + char_index);
+
+ strcpy (temp_string, name);
+ temp_string[char_index] = ' ';
+ strcpy (temp_string + char_index + 1, string);
+
+ parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST);
+
+ /* Ancient backwards compatibility. Old versions of bash exported
+ functions like name()=() {...} */
+ if (name[char_index - 1] == ')' && name[char_index - 2] == '(')
+ name[char_index - 2] = '\0';
+
+ if (temp_var = find_function (name))
+ {
+ VSETATTR (temp_var, (att_exported|att_imported));
+ array_needs_making = 1;
+ }
+ else
+ report_error (_("error importing function definition for `%s'"), name);
+
+ /* ( */
+ if (name[char_index - 1] == ')' && name[char_index - 2] == '\0')
+ name[char_index - 2] = '('; /* ) */
+ }
+#if defined (ARRAY_VARS)
+# if 0
+ /* Array variables may not yet be exported. */
+ else if (*string == '(' && string[1] == '[' && string[strlen (string) - 1] == ')')
+ {
+ string_length = 1;
+ temp_string = extract_array_assignment_list (string, &string_length);
+ temp_var = assign_array_from_string (name, temp_string);
+ FREE (temp_string);
+ VSETATTR (temp_var, (att_exported | att_imported));
+ array_needs_making = 1;
+ }
+# endif
+#endif
+#if 0
+ else if (legal_identifier (name))
+#else
+ else
+#endif
+ {
+ temp_var = bind_variable (name, string, 0);
+ if (legal_identifier (name))
+ VSETATTR (temp_var, (att_exported | att_imported));
+ else
+ VSETATTR (temp_var, (att_exported | att_imported | att_invisible));
+ array_needs_making = 1;
+ }
+
+ name[char_index] = '=';
+ /* temp_var can be NULL if it was an exported function with a syntax
+ error (a different bug, but it still shouldn't dump core). */
+ if (temp_var && function_p (temp_var) == 0) /* XXX not yet */
+ {
+ CACHE_IMPORTSTR (temp_var, name);
+ }
+ }
+
+ set_pwd ();
+
+ /* Set up initial value of $_ */
+ temp_var = set_if_not ("_", dollar_vars[0]);
+
+ /* Remember this pid. */
+ dollar_dollar_pid = getpid ();
+
+ /* Now make our own defaults in case the vars that we think are
+ important are missing. */
+ temp_var = set_if_not ("PATH", DEFAULT_PATH_VALUE);
+#if 0
+ set_auto_export (temp_var); /* XXX */
+#endif
+
+ temp_var = set_if_not ("TERM", "dumb");
+#if 0
+ set_auto_export (temp_var); /* XXX */
+#endif
+
+#if defined (__QNX__)
+ /* set node id -- don't import it from the environment */
+ {
+ char node_name[22];
+# if defined (__QNXNTO__)
+ netmgr_ndtostr(ND2S_LOCAL_STR, ND_LOCAL_NODE, node_name, sizeof(node_name));
+# else
+ qnx_nidtostr (getnid (), node_name, sizeof (node_name));
+# endif
+ temp_var = bind_variable ("NODE", node_name, 0);
+ set_auto_export (temp_var);
+ }
+#endif
+
+ /* set up the prompts. */
+ if (interactive_shell)
+ {
+#if defined (PROMPT_STRING_DECODE)
+ set_if_not ("PS1", primary_prompt);
+#else
+ if (current_user.uid == -1)
+ get_current_user_info ();
+ set_if_not ("PS1", current_user.euid == 0 ? "# " : primary_prompt);
+#endif
+ set_if_not ("PS2", secondary_prompt);
+ }
+ set_if_not ("PS4", "+ ");
+
+ /* Don't allow IFS to be imported from the environment. */
+ temp_var = bind_variable ("IFS", " \t\n", 0);
+ setifs (temp_var);
+
+ /* Magic machine types. Pretty convenient. */
+ set_machine_vars ();
+
+ /* Default MAILCHECK for interactive shells. Defer the creation of a
+ default MAILPATH until the startup files are read, because MAIL
+ names a mail file if MAILPATH is not set, and we should provide a
+ default only if neither is set. */
+ if (interactive_shell)
+ {
+ temp_var = set_if_not ("MAILCHECK", posixly_correct ? "600" : "60");
+ VSETATTR (temp_var, att_integer);
+ }
+
+ /* Do some things with shell level. */
+ initialize_shell_level ();
+
+ set_ppid ();
+
+ /* Initialize the `getopts' stuff. */
+ temp_var = bind_variable ("OPTIND", "1", 0);
+ VSETATTR (temp_var, att_integer);
+ getopts_reset (0);
+ bind_variable ("OPTERR", "1", 0);
+ sh_opterr = 1;
+
+ if (login_shell == 1 && posixly_correct == 0)
+ set_home_var ();
+
+ /* Get the full pathname to THIS shell, and set the BASH variable
+ to it. */
+ name = get_bash_name ();
+ temp_var = bind_variable ("BASH", name, 0);
+ free (name);
+
+ /* Make the exported environment variable SHELL be the user's login
+ shell. Note that the `tset' command looks at this variable
+ to determine what style of commands to output; if it ends in "csh",
+ then C-shell commands are output, else Bourne shell commands. */
+ set_shell_var ();
+
+ /* Make a variable called BASH_VERSION which contains the version info. */
+ bind_variable ("BASH_VERSION", shell_version_string (), 0);
+#if defined (ARRAY_VARS)
+ make_vers_array ();
+#endif
+
+ if (command_execution_string)
+ bind_variable ("BASH_EXECUTION_STRING", command_execution_string, 0);
+
+ /* Find out if we're supposed to be in Posix.2 mode via an
+ environment variable. */
+ temp_var = find_variable ("POSIXLY_CORRECT");
+ if (!temp_var)
+ temp_var = find_variable ("POSIX_PEDANTIC");
+ if (temp_var && imported_p (temp_var))
+ sv_strict_posix (temp_var->name);
+
+#if defined (HISTORY)
+ /* Set history variables to defaults, and then do whatever we would
+ do if the variable had just been set. Do this only in the case
+ that we are remembering commands on the history list. */
+ if (remember_on_history)
+ {
+ name = bash_tilde_expand (posixly_correct ? "~/.sh_history" : "~/.bash_history", 0);
+
+ set_if_not ("HISTFILE", name);
+ free (name);
+
+#if 0
+ set_if_not ("HISTSIZE", "500");
+ sv_histsize ("HISTSIZE");
+#endif
+ }
+#endif /* HISTORY */
+
+ /* Seed the random number generator. */
+ seedrand ();
+
+ /* Handle some "special" variables that we may have inherited from a
+ parent shell. */
+ if (interactive_shell)
+ {
+ temp_var = find_variable ("IGNOREEOF");
+ if (!temp_var)
+ temp_var = find_variable ("ignoreeof");
+ if (temp_var && imported_p (temp_var))
+ sv_ignoreeof (temp_var->name);
+ }
+
+#if defined (HISTORY)
+ if (interactive_shell && remember_on_history)
+ {
+ sv_history_control ("HISTCONTROL");
+ sv_histignore ("HISTIGNORE");
+ sv_histtimefmt ("HISTTIMEFORMAT");
+ }
+#endif /* HISTORY */
+
+#if defined (READLINE) && defined (STRICT_POSIX)
+ /* POSIXLY_CORRECT will only be 1 here if the shell was compiled
+ -DSTRICT_POSIX */
+ if (interactive_shell && posixly_correct && no_line_editing == 0)
+ rl_prefer_env_winsize = 1;
+#endif /* READLINE && STRICT_POSIX */
+
+ /*
+ * 24 October 2001
+ *
+ * I'm tired of the arguing and bug reports. Bash now leaves SSH_CLIENT
+ * and SSH2_CLIENT alone. I'm going to rely on the shell_level check in
+ * isnetconn() to avoid running the startup files more often than wanted.
+ * That will, of course, only work if the user's login shell is bash, so
+ * I've made that behavior conditional on SSH_SOURCE_BASHRC being defined
+ * in config-top.h.
+ */
+#if 0
+ temp_var = find_variable ("SSH_CLIENT");
+ if (temp_var && imported_p (temp_var))
+ {
+ VUNSETATTR (temp_var, att_exported);
+ array_needs_making = 1;
+ }
+ temp_var = find_variable ("SSH2_CLIENT");
+ if (temp_var && imported_p (temp_var))
+ {
+ VUNSETATTR (temp_var, att_exported);
+ array_needs_making = 1;
+ }
+#endif
+
+ /* Get the user's real and effective user ids. */
+ uidset ();
+
+ /* Initialize the dynamic variables, and seed their values. */
+ initialize_dynamic_variables ();
+}
+
+/* **************************************************************** */
+/* */
+/* Setting values for special shell variables */
+/* */
+/* **************************************************************** */
+
+static void
+set_machine_vars ()
+{
+ SHELL_VAR *temp_var;
+
+ temp_var = set_if_not ("HOSTTYPE", HOSTTYPE);
+ temp_var = set_if_not ("OSTYPE", OSTYPE);
+ temp_var = set_if_not ("MACHTYPE", MACHTYPE);
+
+ temp_var = set_if_not ("HOSTNAME", current_host_name);
+}
+
+/* Set $HOME to the information in the password file if we didn't get
+ it from the environment. */
+
+/* This function is not static so the tilde and readline libraries can
+ use it. */
+char *
+sh_get_home_dir ()
+{
+ if (current_user.home_dir == 0)
+ get_current_user_info ();
+ return current_user.home_dir;
+}
+
+static void
+set_home_var ()
+{
+ SHELL_VAR *temp_var;
+
+ temp_var = find_variable ("HOME");
+ if (temp_var == 0)
+ temp_var = bind_variable ("HOME", sh_get_home_dir (), 0);
+#if 0
+ VSETATTR (temp_var, att_exported);
+#endif
+}
+
+/* Set $SHELL to the user's login shell if it is not already set. Call
+ get_current_user_info if we haven't already fetched the shell. */
+static void
+set_shell_var ()
+{
+ SHELL_VAR *temp_var;
+
+ temp_var = find_variable ("SHELL");
+ if (temp_var == 0)
+ {
+ if (current_user.shell == 0)
+ get_current_user_info ();
+ temp_var = bind_variable ("SHELL", current_user.shell, 0);
+ }
+#if 0
+ VSETATTR (temp_var, att_exported);
+#endif
+}
+
+static char *
+get_bash_name ()
+{
+ char *name;
+
+ if ((login_shell == 1) && RELPATH(shell_name))
+ {
+ if (current_user.shell == 0)
+ get_current_user_info ();
+ name = savestring (current_user.shell);
+ }
+ else if (ABSPATH(shell_name))
+ name = savestring (shell_name);
+ else if (shell_name[0] == '.' && shell_name[1] == '/')
+ {
+ /* Fast path for common case. */
+ char *cdir;
+ int len;
+
+ cdir = get_string_value ("PWD");
+ if (cdir)
+ {
+ len = strlen (cdir);
+ name = (char *)xmalloc (len + strlen (shell_name) + 1);
+ strcpy (name, cdir);
+ strcpy (name + len, shell_name + 1);
+ }
+ else
+ name = savestring (shell_name);
+ }
+ else
+ {
+ char *tname;
+ int s;
+
+ tname = find_user_command (shell_name);
+
+ if (tname == 0)
+ {
+ /* Try the current directory. If there is not an executable
+ there, just punt and use the login shell. */
+ s = file_status (shell_name);
+ if (s & FS_EXECABLE)
+ {
+ tname = make_absolute (shell_name, get_string_value ("PWD"));
+ if (*shell_name == '.')
+ {
+ name = sh_canonpath (tname, PATH_CHECKDOTDOT|PATH_CHECKEXISTS);
+ if (name == 0)
+ name = tname;
+ else
+ free (tname);
+ }
+ else
+ name = tname;
+ }
+ else
+ {
+ if (current_user.shell == 0)
+ get_current_user_info ();
+ name = savestring (current_user.shell);
+ }
+ }
+ else
+ {
+ name = full_pathname (tname);
+ free (tname);
+ }
+ }
+
+ return (name);
+}
+
+void
+adjust_shell_level (change)
+ int change;
+{
+ char new_level[5], *old_SHLVL;
+ intmax_t old_level;
+ SHELL_VAR *temp_var;
+
+ old_SHLVL = get_string_value ("SHLVL");
+ if (old_SHLVL == 0 || *old_SHLVL == '\0' || legal_number (old_SHLVL, &old_level) == 0)
+ old_level = 0;
+
+ shell_level = old_level + change;
+ if (shell_level < 0)
+ shell_level = 0;
+ else if (shell_level > 1000)
+ {
+ internal_warning (_("shell level (%d) too high, resetting to 1"), shell_level);
+ shell_level = 1;
+ }
+
+ /* We don't need the full generality of itos here. */
+ if (shell_level < 10)
+ {
+ new_level[0] = shell_level + '0';
+ new_level[1] = '\0';
+ }
+ else if (shell_level < 100)
+ {
+ new_level[0] = (shell_level / 10) + '0';
+ new_level[1] = (shell_level % 10) + '0';
+ new_level[2] = '\0';
+ }
+ else if (shell_level < 1000)
+ {
+ new_level[0] = (shell_level / 100) + '0';
+ old_level = shell_level % 100;
+ new_level[1] = (old_level / 10) + '0';
+ new_level[2] = (old_level % 10) + '0';
+ new_level[3] = '\0';
+ }
+
+ temp_var = bind_variable ("SHLVL", new_level, 0);
+ set_auto_export (temp_var);
+}
+
+static void
+initialize_shell_level ()
+{
+ adjust_shell_level (1);
+}
+
+/* If we got PWD from the environment, update our idea of the current
+ working directory. In any case, make sure that PWD exists before
+ checking it. It is possible for getcwd () to fail on shell startup,
+ and in that case, PWD would be undefined. If this is an interactive
+ login shell, see if $HOME is the current working directory, and if
+ that's not the same string as $PWD, set PWD=$HOME. */
+
+void
+set_pwd ()
+{
+ SHELL_VAR *temp_var, *home_var;
+ char *temp_string, *home_string;
+
+ home_var = find_variable ("HOME");
+ home_string = home_var ? value_cell (home_var) : (char *)NULL;
+
+ temp_var = find_variable ("PWD");
+ if (temp_var && imported_p (temp_var) &&
+ (temp_string = value_cell (temp_var)) &&
+ same_file (temp_string, ".", (struct stat *)NULL, (struct stat *)NULL))
+ set_working_directory (temp_string);
+ else if (home_string && interactive_shell && login_shell &&
+ same_file (home_string, ".", (struct stat *)NULL, (struct stat *)NULL))
+ {
+ set_working_directory (home_string);
+ temp_var = bind_variable ("PWD", home_string, 0);
+ set_auto_export (temp_var);
+ }
+ else
+ {
+ temp_string = get_working_directory ("shell-init");
+ if (temp_string)
+ {
+ temp_var = bind_variable ("PWD", temp_string, 0);
+ set_auto_export (temp_var);
+ free (temp_string);
+ }
+ }
+
+ /* According to the Single Unix Specification, v2, $OLDPWD is an
+ `environment variable' and therefore should be auto-exported.
+ Make a dummy invisible variable for OLDPWD, and mark it as exported. */
+ temp_var = bind_variable ("OLDPWD", (char *)NULL, 0);
+ VSETATTR (temp_var, (att_exported | att_invisible));
+}
+
+/* Make a variable $PPID, which holds the pid of the shell's parent. */
+void
+set_ppid ()
+{
+ char namebuf[INT_STRLEN_BOUND(pid_t) + 1], *name;
+ SHELL_VAR *temp_var;
+
+ name = inttostr (getppid (), namebuf, sizeof(namebuf));
+ temp_var = find_variable ("PPID");
+ if (temp_var)
+ VUNSETATTR (temp_var, (att_readonly | att_exported));
+ temp_var = bind_variable ("PPID", name, 0);
+ VSETATTR (temp_var, (att_readonly | att_integer));
+}
+
+static void
+uidset ()
+{
+ char buff[INT_STRLEN_BOUND(uid_t) + 1], *b;
+ register SHELL_VAR *v;
+
+ b = inttostr (current_user.uid, buff, sizeof (buff));
+ v = find_variable ("UID");
+ if (v == 0)
+ {
+ v = bind_variable ("UID", b, 0);
+ VSETATTR (v, (att_readonly | att_integer));
+ }
+
+ if (current_user.euid != current_user.uid)
+ b = inttostr (current_user.euid, buff, sizeof (buff));
+
+ v = find_variable ("EUID");
+ if (v == 0)
+ {
+ v = bind_variable ("EUID", b, 0);
+ VSETATTR (v, (att_readonly | att_integer));
+ }
+}
+
+#if defined (ARRAY_VARS)
+static void
+make_vers_array ()
+{
+ SHELL_VAR *vv;
+ ARRAY *av;
+ char *s, d[32], b[INT_STRLEN_BOUND(int) + 1];
+
+ unbind_variable ("BASH_VERSINFO");
+
+ vv = make_new_array_variable ("BASH_VERSINFO");
+ av = array_cell (vv);
+ strcpy (d, dist_version);
+ s = strchr (d, '.');
+ if (s)
+ *s++ = '\0';
+ array_insert (av, 0, d);
+ array_insert (av, 1, s);
+ s = inttostr (patch_level, b, sizeof (b));
+ array_insert (av, 2, s);
+ s = inttostr (build_version, b, sizeof (b));
+ array_insert (av, 3, s);
+ array_insert (av, 4, release_status);
+ array_insert (av, 5, MACHTYPE);
+
+ VSETATTR (vv, att_readonly);
+}
+#endif /* ARRAY_VARS */
+
+/* Set the environment variables $LINES and $COLUMNS in response to
+ a window size change. */
+void
+sh_set_lines_and_columns (lines, cols)
+ int lines, cols;
+{
+ char val[INT_STRLEN_BOUND(int) + 1], *v;
+
+#if defined (READLINE)
+ /* If we are currently assigning to LINES or COLUMNS, don't do anything. */
+ if (winsize_assignment)
+ return;
+#endif
+
+ v = inttostr (lines, val, sizeof (val));
+ bind_variable ("LINES", v, 0);
+
+ v = inttostr (cols, val, sizeof (val));
+ bind_variable ("COLUMNS", v, 0);
+}
+
+/* **************************************************************** */
+/* */
+/* Printing variables and values */
+/* */
+/* **************************************************************** */
+
+/* Print LIST (a list of shell variables) to stdout in such a way that
+ they can be read back in. */
+void
+print_var_list (list)
+ register SHELL_VAR **list;
+{
+ register int i;
+ register SHELL_VAR *var;
+
+ for (i = 0; list && (var = list[i]); i++)
+ if (invisible_p (var) == 0)
+ print_assignment (var);
+}
+
+/* Print LIST (a list of shell functions) to stdout in such a way that
+ they can be read back in. */
+void
+print_func_list (list)
+ register SHELL_VAR **list;
+{
+ register int i;
+ register SHELL_VAR *var;
+
+ for (i = 0; list && (var = list[i]); i++)
+ {
+ printf ("%s ", var->name);
+ print_var_function (var);
+ printf ("\n");
+ }
+}
+
+/* Print the value of a single SHELL_VAR. No newline is
+ output, but the variable is printed in such a way that
+ it can be read back in. */
+void
+print_assignment (var)
+ SHELL_VAR *var;
+{
+ if (var_isset (var) == 0)
+ return;
+
+ if (function_p (var))
+ {
+ printf ("%s", var->name);
+ print_var_function (var);
+ printf ("\n");
+ }
+#if defined (ARRAY_VARS)
+ else if (array_p (var))
+ print_array_assignment (var, 0);
+ else if (assoc_p (var))
+ print_assoc_assignment (var, 0);
+#endif /* ARRAY_VARS */
+ else
+ {
+ printf ("%s=", var->name);
+ print_var_value (var, 1);
+ printf ("\n");
+ }
+}
+
+/* Print the value cell of VAR, a shell variable. Do not print
+ the name, nor leading/trailing newline. If QUOTE is non-zero,
+ and the value contains shell metacharacters, quote the value
+ in such a way that it can be read back in. */
+void
+print_var_value (var, quote)
+ SHELL_VAR *var;
+ int quote;
+{
+ char *t;
+
+ if (var_isset (var) == 0)
+ return;
+
+ if (quote && posixly_correct == 0 && ansic_shouldquote (value_cell (var)))
+ {
+ t = ansic_quote (value_cell (var), 0, (int *)0);
+ printf ("%s", t);
+ free (t);
+ }
+ else if (quote && sh_contains_shell_metas (value_cell (var)))
+ {
+ t = sh_single_quote (value_cell (var));
+ printf ("%s", t);
+ free (t);
+ }
+ else
+ printf ("%s", value_cell (var));
+}
+
+/* Print the function cell of VAR, a shell variable. Do not
+ print the name, nor leading/trailing newline. */
+void
+print_var_function (var)
+ SHELL_VAR *var;
+{
+ char *x;
+
+ if (function_p (var) && var_isset (var))
+ {
+ x = named_function_string ((char *)NULL, function_cell(var), FUNC_MULTILINE|FUNC_EXTERNAL);
+ printf ("%s", x);
+ }
+}
+
+/* **************************************************************** */
+/* */
+/* Dynamic Variables */
+/* */
+/* **************************************************************** */
+
+/* DYNAMIC VARIABLES
+
+ These are variables whose values are generated anew each time they are
+ referenced. These are implemented using a pair of function pointers
+ in the struct variable: assign_func, which is called from bind_variable
+ and, if arrays are compiled into the shell, some of the functions in
+ arrayfunc.c, and dynamic_value, which is called from find_variable.
+
+ assign_func is called from bind_variable_internal, if
+ bind_variable_internal discovers that the variable being assigned to
+ has such a function. The function is called as
+ SHELL_VAR *temp = (*(entry->assign_func)) (entry, value, ind)
+ and the (SHELL_VAR *)temp is returned as the value of bind_variable. It
+ is usually ENTRY (self). IND is an index for an array variable, and
+ unused otherwise.
+
+ dynamic_value is called from find_variable_internal to return a `new'
+ value for the specified dynamic varible. If this function is NULL,
+ the variable is treated as a `normal' shell variable. If it is not,
+ however, then this function is called like this:
+ tempvar = (*(var->dynamic_value)) (var);
+
+ Sometimes `tempvar' will replace the value of `var'. Other times, the
+ shell will simply use the string value. Pretty object-oriented, huh?
+
+ Be warned, though: if you `unset' a special variable, it loses its
+ special meaning, even if you subsequently set it.
+
+ The special assignment code would probably have been better put in
+ subst.c: do_assignment_internal, in the same style as
+ stupidly_hack_special_variables, but I wanted the changes as
+ localized as possible. */
+
+#define INIT_DYNAMIC_VAR(var, val, gfunc, afunc) \
+ do \
+ { \
+ v = bind_variable (var, (val), 0); \
+ v->dynamic_value = gfunc; \
+ v->assign_func = afunc; \
+ } \
+ while (0)
+
+#define INIT_DYNAMIC_ARRAY_VAR(var, gfunc, afunc) \
+ do \
+ { \
+ v = make_new_array_variable (var); \
+ v->dynamic_value = gfunc; \
+ v->assign_func = afunc; \
+ } \
+ while (0)
+
+#define INIT_DYNAMIC_ASSOC_VAR(var, gfunc, afunc) \
+ do \
+ { \
+ v = make_new_assoc_variable (var); \
+ v->dynamic_value = gfunc; \
+ v->assign_func = afunc; \
+ } \
+ while (0)
+
+static SHELL_VAR *
+null_assign (self, value, unused, key)
+ SHELL_VAR *self;
+ char *value;
+ arrayind_t unused;
+ char *key;
+{
+ return (self);
+}
+
+#if defined (ARRAY_VARS)
+static SHELL_VAR *
+null_array_assign (self, value, ind, key)
+ SHELL_VAR *self;
+ char *value;
+ arrayind_t ind;
+ char *key;
+{
+ return (self);
+}
+#endif
+
+/* Degenerate `dynamic_value' function; just returns what's passed without
+ manipulation. */
+static SHELL_VAR *
+get_self (self)
+ SHELL_VAR *self;
+{
+ return (self);
+}
+
+#if defined (ARRAY_VARS)
+/* A generic dynamic array variable initializer. Intialize array variable
+ NAME with dynamic value function GETFUNC and assignment function SETFUNC. */
+static SHELL_VAR *
+init_dynamic_array_var (name, getfunc, setfunc, attrs)
+ char *name;
+ sh_var_value_func_t *getfunc;
+ sh_var_assign_func_t *setfunc;
+ int attrs;
+{
+ SHELL_VAR *v;
+
+ v = find_variable (name);
+ if (v)
+ return (v);
+ INIT_DYNAMIC_ARRAY_VAR (name, getfunc, setfunc);
+ if (attrs)
+ VSETATTR (v, attrs);
+ return v;
+}
+
+static SHELL_VAR *
+init_dynamic_assoc_var (name, getfunc, setfunc, attrs)
+ char *name;
+ sh_var_value_func_t *getfunc;
+ sh_var_assign_func_t *setfunc;
+ int attrs;
+{
+ SHELL_VAR *v;
+
+ v = find_variable (name);
+ if (v)
+ return (v);
+ INIT_DYNAMIC_ASSOC_VAR (name, getfunc, setfunc);
+ if (attrs)
+ VSETATTR (v, attrs);
+ return v;
+}
+#endif
+
+/* The value of $SECONDS. This is the number of seconds since shell
+ invocation, or, the number of seconds since the last assignment + the
+ value of the last assignment. */
+static intmax_t seconds_value_assigned;
+
+static SHELL_VAR *
+assign_seconds (self, value, unused, key)
+ SHELL_VAR *self;
+ char *value;
+ arrayind_t unused;
+ char *key;
+{
+ if (legal_number (value, &seconds_value_assigned) == 0)
+ seconds_value_assigned = 0;
+ shell_start_time = NOW;
+ return (self);
+}
+
+static SHELL_VAR *
+get_seconds (var)
+ SHELL_VAR *var;
+{
+ time_t time_since_start;
+ char *p;
+
+ time_since_start = NOW - shell_start_time;
+ p = itos(seconds_value_assigned + time_since_start);
+
+ FREE (value_cell (var));
+
+ VSETATTR (var, att_integer);
+ var_setvalue (var, p);
+ return (var);
+}
+
+static SHELL_VAR *
+init_seconds_var ()
+{
+ SHELL_VAR *v;
+
+ v = find_variable ("SECONDS");
+ if (v)
+ {
+ if (legal_number (value_cell(v), &seconds_value_assigned) == 0)
+ seconds_value_assigned = 0;
+ }
+ INIT_DYNAMIC_VAR ("SECONDS", (v ? value_cell (v) : (char *)NULL), get_seconds, assign_seconds);
+ return v;
+}
+
+/* The random number seed. You can change this by setting RANDOM. */
+static unsigned long rseed = 1;
+static int last_random_value;
+static int seeded_subshell = 0;
+
+/* A linear congruential random number generator based on the example
+ one in the ANSI C standard. This one isn't very good, but a more
+ complicated one is overkill. */
+
+/* Returns a pseudo-random number between 0 and 32767. */
+static int
+brand ()
+{
+#if 0
+ rseed = rseed * 1103515245 + 12345;
+ return ((unsigned int)((rseed >> 16) & 32767)); /* was % 32768 */
+#else
+ /* From "Random number generators: good ones are hard to find",
+ Park and Miller, Communications of the ACM, vol. 31, no. 10,
+ October 1988, p. 1195. filtered through FreeBSD */
+ long h, l;
+
+ if (rseed == 0)
+ seedrand ();
+ h = rseed / 127773;
+ l = rseed % 127773;
+ rseed = 16807 * l - 2836 * h;
+#if 0
+ if (rseed < 0)
+ rseed += 0x7fffffff;
+#endif
+ return ((unsigned int)(rseed & 32767)); /* was % 32768 */
+#endif
+}
+
+/* Set the random number generator seed to SEED. */
+static void
+sbrand (seed)
+ unsigned long seed;
+{
+ rseed = seed;
+ last_random_value = 0;
+}
+
+static void
+seedrand ()
+{
+ struct timeval tv;
+
+ gettimeofday (&tv, NULL);
+ sbrand (tv.tv_sec ^ tv.tv_usec ^ getpid ());
+}
+
+static SHELL_VAR *
+assign_random (self, value, unused, key)
+ SHELL_VAR *self;
+ char *value;
+ arrayind_t unused;
+ char *key;
+{
+ sbrand (strtoul (value, (char **)NULL, 10));
+ if (subshell_environment)
+ seeded_subshell = getpid ();
+ return (self);
+}
+
+int
+get_random_number ()
+{
+ int rv, pid;
+
+ /* Reset for command and process substitution. */
+ pid = getpid ();
+ if (subshell_environment && seeded_subshell != pid)
+ {
+ seedrand ();
+ seeded_subshell = pid;
+ }
+
+ do
+ rv = brand ();
+ while (rv == last_random_value);
+ return rv;
+}
+
+static SHELL_VAR *
+get_random (var)
+ SHELL_VAR *var;
+{
+ int rv;
+ char *p;
+
+ rv = get_random_number ();
+ last_random_value = rv;
+fprintf(stderr, "get_random: rv = %d\n", 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 = new_value;
+ return var;
+}
+
+/* Function which returns the current line number. */
+static SHELL_VAR *
+get_lineno (var)
+ SHELL_VAR *var;
+{
+ char *p;
+ int ln;
+
+ ln = executing_line_number ();
+ p = itos (ln);
+ FREE (value_cell (var));
+ var_setvalue (var, p);
+ return (var);
+}
+
+static SHELL_VAR *
+assign_subshell (var, value, unused, key)
+ SHELL_VAR *var;
+ char *value;
+ arrayind_t unused;
+ char *key;
+{
+ intmax_t new_value;
+
+ if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0)
+ new_value = 0;
+ subshell_level = new_value;
+ return var;
+}
+
+static SHELL_VAR *
+get_subshell (var)
+ SHELL_VAR *var;
+{
+ char *p;
+
+ p = itos (subshell_level);
+ FREE (value_cell (var));
+ var_setvalue (var, p);
+ return (var);
+}
+
+static SHELL_VAR *
+get_bashpid (var)
+ SHELL_VAR *var;
+{
+ int pid;
+ char *p;
+
+ pid = getpid ();
+ p = itos (pid);
+
+ FREE (value_cell (var));
+ VSETATTR (var, att_integer|att_readonly);
+ var_setvalue (var, p);
+ return (var);
+}
+
+static SHELL_VAR *
+get_bash_command (var)
+ SHELL_VAR *var;
+{
+ char *p;
+
+ if (the_printed_command_except_trap)
+ p = savestring (the_printed_command_except_trap);
+ else
+ {
+ p = (char *)xmalloc (1);
+ p[0] = '\0';
+ }
+ FREE (value_cell (var));
+ var_setvalue (var, p);
+ return (var);
+}
+
+#if defined (HISTORY)
+static SHELL_VAR *
+get_histcmd (var)
+ SHELL_VAR *var;
+{
+ char *p;
+
+ p = itos (history_number ());
+ FREE (value_cell (var));
+ var_setvalue (var, p);
+ return (var);
+}
+#endif
+
+#if defined (READLINE)
+/* When this function returns, VAR->value points to malloced memory. */
+static SHELL_VAR *
+get_comp_wordbreaks (var)
+ SHELL_VAR *var;
+{
+ /* If we don't have anything yet, assign a default value. */
+ if (rl_completer_word_break_characters == 0 && bash_readline_initialized == 0)
+ enable_hostname_completion (perform_hostname_completion);
+
+ FREE (value_cell (var));
+ var_setvalue (var, savestring (rl_completer_word_break_characters));
+
+ return (var);
+}
+
+/* When this function returns, rl_completer_word_break_characters points to
+ malloced memory. */
+static SHELL_VAR *
+assign_comp_wordbreaks (self, value, unused, key)
+ SHELL_VAR *self;
+ char *value;
+ arrayind_t unused;
+ char *key;
+{
+ if (rl_completer_word_break_characters &&
+ rl_completer_word_break_characters != rl_basic_word_break_characters)
+ free (rl_completer_word_break_characters);
+
+ rl_completer_word_break_characters = savestring (value);
+ return self;
+}
+#endif /* READLINE */
+
+#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS)
+static SHELL_VAR *
+assign_dirstack (self, value, ind, key)
+ SHELL_VAR *self;
+ char *value;
+ arrayind_t ind;
+ char *key;
+{
+ set_dirstack_element (ind, 1, value);
+ return self;
+}
+
+static SHELL_VAR *
+get_dirstack (self)
+ SHELL_VAR *self;
+{
+ ARRAY *a;
+ WORD_LIST *l;
+
+ l = get_directory_stack (0);
+ a = array_from_word_list (l);
+ array_dispose (array_cell (self));
+ dispose_words (l);
+ var_setarray (self, a);
+ return self;
+}
+#endif /* PUSHD AND POPD && ARRAY_VARS */
+
+#if defined (ARRAY_VARS)
+/* We don't want to initialize the group set with a call to getgroups()
+ unless we're asked to, but we only want to do it once. */
+static SHELL_VAR *
+get_groupset (self)
+ SHELL_VAR *self;
+{
+ register int i;
+ int ng;
+ ARRAY *a;
+ static char **group_set = (char **)NULL;
+
+ if (group_set == 0)
+ {
+ group_set = get_group_list (&ng);
+ a = array_cell (self);
+ for (i = 0; i < ng; i++)
+ array_insert (a, i, group_set[i]);
+ }
+ return (self);
+}
+
+static SHELL_VAR *
+build_hashcmd (self)
+ SHELL_VAR *self;
+{
+ HASH_TABLE *h;
+ int i;
+ char *k, *v;
+ BUCKET_CONTENTS *item;
+
+ h = assoc_cell (self);
+ if (h)
+ assoc_dispose (h);
+
+ if (hashed_filenames == 0 || HASH_ENTRIES (hashed_filenames) == 0)
+ {
+ var_setvalue (self, (char *)NULL);
+ return self;
+ }
+
+ h = assoc_create (hashed_filenames->nbuckets);
+ for (i = 0; i < hashed_filenames->nbuckets; i++)
+ {
+ for (item = hash_items (i, hashed_filenames); item; item = item->next)
+ {
+ k = savestring (item->key);
+ v = pathdata(item)->path;
+ assoc_insert (h, k, v);
+ }
+ }
+
+ var_setvalue (self, (char *)h);
+ return self;
+}
+
+static SHELL_VAR *
+get_hashcmd (self)
+ SHELL_VAR *self;
+{
+ build_hashcmd (self);
+ return (self);
+}
+
+static SHELL_VAR *
+assign_hashcmd (self, value, ind, key)
+ SHELL_VAR *self;
+ char *value;
+ arrayind_t ind;
+ char *key;
+{
+ phash_insert (key, value, 0, 0);
+ return (build_hashcmd (self));
+}
+
+#if defined (ALIAS)
+static SHELL_VAR *
+build_aliasvar (self)
+ SHELL_VAR *self;
+{
+ HASH_TABLE *h;
+ int i;
+ char *k, *v;
+ BUCKET_CONTENTS *item;
+
+ h = assoc_cell (self);
+ if (h)
+ assoc_dispose (h);
+
+ if (aliases == 0 || HASH_ENTRIES (aliases) == 0)
+ {
+ var_setvalue (self, (char *)NULL);
+ return self;
+ }
+
+ h = assoc_create (aliases->nbuckets);
+ for (i = 0; i < aliases->nbuckets; i++)
+ {
+ for (item = hash_items (i, aliases); item; item = item->next)
+ {
+ k = savestring (item->key);
+ v = ((alias_t *)(item->data))->value;
+ assoc_insert (h, k, v);
+ }
+ }
+
+ var_setvalue (self, (char *)h);
+ return self;
+}
+
+static SHELL_VAR *
+get_aliasvar (self)
+ SHELL_VAR *self;
+{
+ build_aliasvar (self);
+ return (self);
+}
+
+static SHELL_VAR *
+assign_aliasvar (self, value, ind, key)
+ SHELL_VAR *self;
+ char *value;
+ arrayind_t ind;
+ char *key;
+{
+ add_alias (key, value);
+ return (build_aliasvar (self));
+}
+#endif /* ALIAS */
+
+#endif /* ARRAY_VARS */
+
+/* If ARRAY_VARS is not defined, this just returns the name of any
+ currently-executing function. If we have arrays, it's a call stack. */
+static SHELL_VAR *
+get_funcname (self)
+ SHELL_VAR *self;
+{
+#if ! defined (ARRAY_VARS)
+ char *t;
+ if (variable_context && this_shell_function)
+ {
+ FREE (value_cell (self));
+ t = savestring (this_shell_function->name);
+ var_setvalue (self, t);
+ }
+#endif
+ return (self);
+}
+
+void
+make_funcname_visible (on_or_off)
+ int on_or_off;
+{
+ SHELL_VAR *v;
+
+ v = find_variable ("FUNCNAME");
+ if (v == 0 || v->dynamic_value == 0)
+ return;
+
+ if (on_or_off)
+ VUNSETATTR (v, att_invisible);
+ else
+ VSETATTR (v, att_invisible);
+}
+
+static SHELL_VAR *
+init_funcname_var ()
+{
+ SHELL_VAR *v;
+
+ v = find_variable ("FUNCNAME");
+ if (v)
+ return v;
+#if defined (ARRAY_VARS)
+ INIT_DYNAMIC_ARRAY_VAR ("FUNCNAME", get_funcname, null_array_assign);
+#else
+ INIT_DYNAMIC_VAR ("FUNCNAME", (char *)NULL, get_funcname, null_assign);
+#endif
+ VSETATTR (v, att_invisible|att_noassign);
+ return v;
+}
+
+static void
+initialize_dynamic_variables ()
+{
+ SHELL_VAR *v;
+
+ v = init_seconds_var ();
+
+ INIT_DYNAMIC_VAR ("BASH_COMMAND", (char *)NULL, get_bash_command, (sh_var_assign_func_t *)NULL);
+ INIT_DYNAMIC_VAR ("BASH_SUBSHELL", (char *)NULL, get_subshell, assign_subshell);
+
+ INIT_DYNAMIC_VAR ("RANDOM", (char *)NULL, get_random, assign_random);
+ VSETATTR (v, att_integer);
+ INIT_DYNAMIC_VAR ("LINENO", (char *)NULL, get_lineno, assign_lineno);
+ VSETATTR (v, att_integer);
+
+ INIT_DYNAMIC_VAR ("BASHPID", (char *)NULL, get_bashpid, null_assign);
+ VSETATTR (v, att_integer|att_readonly);
+
+#if defined (HISTORY)
+ INIT_DYNAMIC_VAR ("HISTCMD", (char *)NULL, get_histcmd, (sh_var_assign_func_t *)NULL);
+ VSETATTR (v, att_integer);
+#endif
+
+#if defined (READLINE)
+ INIT_DYNAMIC_VAR ("COMP_WORDBREAKS", (char *)NULL, get_comp_wordbreaks, assign_comp_wordbreaks);
+#endif
+
+#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS)
+ v = init_dynamic_array_var ("DIRSTACK", get_dirstack, assign_dirstack, 0);
+#endif /* PUSHD_AND_POPD && ARRAY_VARS */
+
+#if defined (ARRAY_VARS)
+ v = init_dynamic_array_var ("GROUPS", get_groupset, null_array_assign, att_noassign);
+
+# if defined (DEBUGGER)
+ v = init_dynamic_array_var ("BASH_ARGC", get_self, null_array_assign, att_noassign|att_nounset);
+ v = init_dynamic_array_var ("BASH_ARGV", get_self, null_array_assign, att_noassign|att_nounset);
+# endif /* DEBUGGER */
+ v = init_dynamic_array_var ("BASH_SOURCE", get_self, null_array_assign, att_noassign|att_nounset);
+ v = init_dynamic_array_var ("BASH_LINENO", get_self, null_array_assign, att_noassign|att_nounset);
+
+ v = init_dynamic_assoc_var ("BASH_CMDS", get_hashcmd, assign_hashcmd, att_nofree);
+# if defined (ALIAS)
+ v = init_dynamic_assoc_var ("BASH_ALIASES", get_aliasvar, assign_aliasvar, att_nofree);
+# endif
+#endif
+
+ v = init_funcname_var ();
+}
+
+/* **************************************************************** */
+/* */
+/* Retrieving variables and values */
+/* */
+/* **************************************************************** */
+
+/* How to get a pointer to the shell variable or function named NAME.
+ HASHED_VARS is a pointer to the hash table containing the list
+ of interest (either variables or functions). */
+
+static SHELL_VAR *
+hash_lookup (name, hashed_vars)
+ const char *name;
+ HASH_TABLE *hashed_vars;
+{
+ BUCKET_CONTENTS *bucket;
+
+ bucket = hash_search (name, hashed_vars, 0);
+ return (bucket ? (SHELL_VAR *)bucket->data : (SHELL_VAR *)NULL);
+}
+
+SHELL_VAR *
+var_lookup (name, vcontext)
+ const char *name;
+ VAR_CONTEXT *vcontext;
+{
+ VAR_CONTEXT *vc;
+ SHELL_VAR *v;
+
+ v = (SHELL_VAR *)NULL;
+ for (vc = vcontext; vc; vc = vc->down)
+ if (v = hash_lookup (name, vc->table))
+ break;
+
+ return v;
+}
+
+/* Look up the variable entry named NAME. If SEARCH_TEMPENV is non-zero,
+ then also search the temporarily built list of exported variables.
+ The lookup order is:
+ temporary_env
+ shell_variables list
+*/
+
+SHELL_VAR *
+find_variable_internal (name, force_tempenv)
+ const char *name;
+ int force_tempenv;
+{
+ SHELL_VAR *var;
+ int search_tempenv;
+
+ var = (SHELL_VAR *)NULL;
+
+ /* If explicitly requested, first look in the temporary environment for
+ the variable. This allows constructs such as "foo=x eval 'echo $foo'"
+ to get the `exported' value of $foo. This happens if we are executing
+ a function or builtin, or if we are looking up a variable in a
+ "subshell environment". */
+ search_tempenv = force_tempenv || (expanding_redir == 0 && subshell_environment);
+
+ if (search_tempenv && temporary_env)
+ var = hash_lookup (name, temporary_env);
+
+ if (var == 0)
+ var = var_lookup (name, shell_variables);
+
+ if (var == 0)
+ return ((SHELL_VAR *)NULL);
+
+ return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var);
+}
+
+/* Look up the variable entry named NAME. Returns the entry or NULL. */
+SHELL_VAR *
+find_variable (name)
+ const char *name;
+{
+ return (find_variable_internal (name, (expanding_redir == 0 && (assigning_in_environment || executing_builtin))));
+}
+
+/* Look up the function entry whose name matches STRING.
+ Returns the entry or NULL. */
+SHELL_VAR *
+find_function (name)
+ const char *name;
+{
+ return (hash_lookup (name, shell_functions));
+}
+
+/* Find the function definition for the shell function named NAME. Returns
+ the entry or NULL. */
+FUNCTION_DEF *
+find_function_def (name)
+ const char *name;
+{
+#if defined (DEBUGGER)
+ return ((FUNCTION_DEF *)hash_lookup (name, shell_function_defs));
+#else
+ return ((FUNCTION_DEF *)0);
+#endif
+}
+
+/* Return the value of VAR. VAR is assumed to have been the result of a
+ lookup without any subscript, if arrays are compiled into the shell. */
+char *
+get_variable_value (var)
+ SHELL_VAR *var;
+{
+ if (var == 0)
+ return ((char *)NULL);
+#if defined (ARRAY_VARS)
+ else if (array_p (var))
+ return (array_reference (array_cell (var), 0));
+ else if (assoc_p (var))
+ return (assoc_reference (assoc_cell (var), "0"));
+#endif
+ else
+ return (value_cell (var));
+}
+
+/* Return the string value of a variable. Return NULL if the variable
+ doesn't exist. Don't cons a new string. This is a potential memory
+ leak if the variable is found in the temporary environment. Since
+ functions and variables have separate name spaces, returns NULL if
+ var_name is a shell function only. */
+char *
+get_string_value (var_name)
+ const char *var_name;
+{
+ SHELL_VAR *var;
+
+ var = find_variable (var_name);
+ return ((var) ? get_variable_value (var) : (char *)NULL);
+}
+
+/* This is present for use by the tilde and readline libraries. */
+char *
+sh_get_env_value (v)
+ const char *v;
+{
+ return get_string_value (v);
+}
+
+/* **************************************************************** */
+/* */
+/* Creating and setting variables */
+/* */
+/* **************************************************************** */
+
+/* Set NAME to VALUE if NAME has no value. */
+SHELL_VAR *
+set_if_not (name, value)
+ char *name, *value;
+{
+ SHELL_VAR *v;
+
+ if (shell_variables == 0)
+ create_variable_tables ();
+
+ v = find_variable (name);
+ if (v == 0)
+ v = bind_variable_internal (name, value, global_variables->table, HASH_NOSRCH, 0);
+ return (v);
+}
+
+/* Create a local variable referenced by NAME. */
+SHELL_VAR *
+make_local_variable (name)
+ const char *name;
+{
+ SHELL_VAR *new_var, *old_var;
+ VAR_CONTEXT *vc;
+ int was_tmpvar;
+ char *tmp_value;
+
+ /* local foo; local foo; is a no-op. */
+ old_var = find_variable (name);
+ if (old_var && local_p (old_var) && old_var->context == variable_context)
+ {
+ VUNSETATTR (old_var, att_invisible);
+ return (old_var);
+ }
+
+ was_tmpvar = old_var && tempvar_p (old_var);
+ if (was_tmpvar)
+ tmp_value = value_cell (old_var);
+
+ for (vc = shell_variables; vc; vc = vc->down)
+ if (vc_isfuncenv (vc) && vc->scope == variable_context)
+ break;
+
+ if (vc == 0)
+ {
+ internal_error (_("make_local_variable: no function context at current scope"));
+ return ((SHELL_VAR *)NULL);
+ }
+ else if (vc->table == 0)
+ vc->table = hash_create (TEMPENV_HASH_BUCKETS);
+
+ /* Since this is called only from the local/declare/typeset code, we can
+ call builtin_error here without worry (of course, it will also work
+ for anything that sets this_command_name). Variables with the `noassign'
+ attribute may not be made local. The test against old_var's context
+ level is to disallow local copies of readonly global variables (since I
+ believe that this could be a security hole). Readonly copies of calling
+ function local variables are OK. */
+ if (old_var && (noassign_p (old_var) ||
+ (readonly_p (old_var) && old_var->context == 0)))
+ {
+ if (readonly_p (old_var))
+ sh_readonly (name);
+ return ((SHELL_VAR *)NULL);
+ }
+
+ if (old_var == 0)
+ new_var = make_new_variable (name, vc->table);
+ else
+ {
+ new_var = make_new_variable (name, vc->table);
+
+ /* If we found this variable in one of the temporary environments,
+ inherit its value. Watch to see if this causes problems with
+ things like `x=4 local x'. */
+ if (was_tmpvar)
+ var_setvalue (new_var, savestring (tmp_value));
+
+ new_var->attributes = exported_p (old_var) ? att_exported : 0;
+ }
+
+ vc->flags |= VC_HASLOCAL;
+
+ new_var->context = variable_context;
+ VSETATTR (new_var, att_local);
+
+ if (ifsname (name))
+ setifs (new_var);
+
+ return (new_var);
+}
+
+/* Create a new shell variable with name NAME. */
+static SHELL_VAR *
+new_shell_variable (name)
+ const char *name;
+{
+ SHELL_VAR *entry;
+
+ entry = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
+
+ entry->name = savestring (name);
+ var_setvalue (entry, (char *)NULL);
+ CLEAR_EXPORTSTR (entry);
+
+ entry->dynamic_value = (sh_var_value_func_t *)NULL;
+ entry->assign_func = (sh_var_assign_func_t *)NULL;
+
+ entry->attributes = 0;
+
+ /* Always assume variables are to be made at toplevel!
+ make_local_variable has the responsibilty of changing the
+ variable context. */
+ entry->context = 0;
+
+ return (entry);
+}
+
+/* Create a new shell variable with name NAME and add it to the hash table
+ TABLE. */
+static SHELL_VAR *
+make_new_variable (name, table)
+ const char *name;
+ HASH_TABLE *table;
+{
+ SHELL_VAR *entry;
+ BUCKET_CONTENTS *elt;
+
+ entry = new_shell_variable (name);
+
+ /* Make sure we have a shell_variables hash table to add to. */
+ if (shell_variables == 0)
+ create_variable_tables ();
+
+ elt = hash_insert (savestring (name), table, HASH_NOSRCH);
+ elt->data = (PTR_T)entry;
+
+ return entry;
+}
+
+#if defined (ARRAY_VARS)
+SHELL_VAR *
+make_new_array_variable (name)
+ char *name;
+{
+ SHELL_VAR *entry;
+ ARRAY *array;
+
+ entry = make_new_variable (name, global_variables->table);
+ array = array_create ();
+
+ var_setarray (entry, array);
+ VSETATTR (entry, att_array);
+ return entry;
+}
+
+SHELL_VAR *
+make_local_array_variable (name)
+ char *name;
+{
+ SHELL_VAR *var;
+ ARRAY *array;
+
+ var = make_local_variable (name);
+ if (var == 0 || array_p (var))
+ return var;
+
+ array = array_create ();
+
+ dispose_variable_value (var);
+ var_setarray (var, array);
+ VSETATTR (var, att_array);
+ return var;
+}
+
+SHELL_VAR *
+make_new_assoc_variable (name)
+ char *name;
+{
+ SHELL_VAR *entry;
+ HASH_TABLE *hash;
+
+ entry = make_new_variable (name, global_variables->table);
+ hash = assoc_create (0);
+
+ var_setassoc (entry, hash);
+ VSETATTR (entry, att_assoc);
+ return entry;
+}
+
+SHELL_VAR *
+make_local_assoc_variable (name)
+ char *name;
+{
+ SHELL_VAR *var;
+ HASH_TABLE *hash;
+
+ var = make_local_variable (name);
+ if (var == 0 || assoc_p (var))
+ return var;
+
+ dispose_variable_value (var);
+ hash = assoc_create (0);
+
+ var_setassoc (var, hash);
+ VSETATTR (var, att_assoc);
+ return var;
+}
+#endif
+
+char *
+make_variable_value (var, value, flags)
+ SHELL_VAR *var;
+ char *value;
+ int flags;
+{
+ char *retval, *oval;
+ intmax_t lval, rval;
+ int expok, olen, op;
+
+ /* If this variable has had its type set to integer (via `declare -i'),
+ then do expression evaluation on it and store the result. The
+ functions in expr.c (evalexp()) and bind_int_variable() are responsible
+ for turning off the integer flag if they don't want further
+ evaluation done. */
+ if (integer_p (var))
+ {
+ if (flags & ASS_APPEND)
+ {
+ oval = value_cell (var);
+ lval = evalexp (oval, &expok); /* ksh93 seems to do this */
+ if (expok == 0)
+ {
+ top_level_cleanup ();
+ jump_to_top_level (DISCARD);
+ }
+ }
+ rval = evalexp (value, &expok);
+ if (expok == 0)
+ {
+ top_level_cleanup ();
+ jump_to_top_level (DISCARD);
+ }
+ if (flags & ASS_APPEND)
+ rval += lval;
+ retval = itos (rval);
+ }
+#if defined (CASEMOD_ATTRS)
+ else if (capcase_p (var) || uppercase_p (var) || lowercase_p (var))
+ {
+ if (flags & ASS_APPEND)
+ {
+ oval = get_variable_value (var);
+ if (oval == 0) /* paranoia */
+ oval = "";
+ olen = STRLEN (oval);
+ retval = (char *)xmalloc (olen + (value ? STRLEN (value) : 0) + 1);
+ strcpy (retval, oval);
+ if (value)
+ strcpy (retval+olen, value);
+ }
+ else if (*value)
+ retval = savestring (value);
+ else
+ {
+ retval = (char *)xmalloc (1);
+ retval[0] = '\0';
+ }
+ op = capcase_p (var) ? CASE_CAPITALIZE
+ : (uppercase_p (var) ? CASE_UPPER : CASE_LOWER);
+ oval = sh_modcase (retval, (char *)0, op);
+ free (retval);
+ retval = oval;
+ }
+#endif /* CASEMOD_ATTRS */
+ else if (value)
+ {
+ if (flags & ASS_APPEND)
+ {
+ oval = get_variable_value (var);
+ if (oval == 0) /* paranoia */
+ oval = "";
+ olen = STRLEN (oval);
+ retval = (char *)xmalloc (olen + (value ? STRLEN (value) : 0) + 1);
+ strcpy (retval, oval);
+ if (value)
+ strcpy (retval+olen, value);
+ }
+ else if (*value)
+ retval = savestring (value);
+ else
+ {
+ retval = (char *)xmalloc (1);
+ retval[0] = '\0';
+ }
+ }
+ else
+ retval = (char *)NULL;
+
+ return retval;
+}
+
+/* Bind a variable NAME to VALUE in the HASH_TABLE TABLE, which may be the
+ temporary environment (but usually is not). */
+static SHELL_VAR *
+bind_variable_internal (name, value, table, hflags, aflags)
+ const char *name;
+ char *value;
+ HASH_TABLE *table;
+ int hflags, aflags;
+{
+ char *newval;
+ SHELL_VAR *entry;
+
+ entry = (hflags & HASH_NOSRCH) ? (SHELL_VAR *)NULL : hash_lookup (name, table);
+
+ if (entry == 0)
+ {
+ entry = make_new_variable (name, table);
+ var_setvalue (entry, make_variable_value (entry, value, 0)); /* XXX */
+ }
+ else if (entry->assign_func) /* array vars have assign functions now */
+ {
+ INVALIDATE_EXPORTSTR (entry);
+ newval = (aflags & ASS_APPEND) ? make_variable_value (entry, value, aflags) : value;
+ if (assoc_p (entry))
+ entry = (*(entry->assign_func)) (entry, newval, -1, savestring ("0"));
+ else if (array_p (entry))
+ entry = (*(entry->assign_func)) (entry, newval, 0, 0);
+ else
+ entry = (*(entry->assign_func)) (entry, newval, -1, 0);
+ if (newval != value)
+ free (newval);
+ return (entry);
+ }
+ else
+ {
+ if (readonly_p (entry) || noassign_p (entry))
+ {
+ if (readonly_p (entry))
+ err_readonly (name);
+ return (entry);
+ }
+
+ /* Variables which are bound are visible. */
+ VUNSETATTR (entry, att_invisible);
+
+ newval = make_variable_value (entry, value, aflags); /* XXX */
+
+ /* Invalidate any cached export string */
+ INVALIDATE_EXPORTSTR (entry);
+
+#if defined (ARRAY_VARS)
+ /* XXX -- this bears looking at again -- XXX */
+ /* If an existing array variable x is being assigned to with x=b or
+ `read x' or something of that nature, silently convert it to
+ x[0]=b or `read x[0]'. */
+ if (array_p (entry))
+ {
+ array_insert (array_cell (entry), 0, newval);
+ free (newval);
+ }
+ else if (assoc_p (entry))
+ {
+ assoc_insert (assoc_cell (entry), savestring ("0"), newval);
+ free (newval);
+ }
+ else
+#endif
+ {
+ FREE (value_cell (entry));
+ var_setvalue (entry, newval);
+ }
+ }
+
+ if (mark_modified_vars)
+ VSETATTR (entry, att_exported);
+
+ if (exported_p (entry))
+ array_needs_making = 1;
+
+ return (entry);
+}
+
+/* Bind a variable NAME to VALUE. This conses up the name
+ and value strings. If we have a temporary environment, we bind there
+ first, then we bind into shell_variables. */
+
+SHELL_VAR *
+bind_variable (name, value, flags)
+ const char *name;
+ char *value;
+ int flags;
+{
+ SHELL_VAR *v;
+ VAR_CONTEXT *vc;
+
+ if (shell_variables == 0)
+ create_variable_tables ();
+
+ /* If we have a temporary environment, look there first for the variable,
+ and, if found, modify the value there before modifying it in the
+ shell_variables table. This allows sourced scripts to modify values
+ given to them in a temporary environment while modifying the variable
+ value that the caller sees. */
+ if (temporary_env)
+ bind_tempenv_variable (name, value);
+
+ /* XXX -- handle local variables here. */
+ for (vc = shell_variables; vc; vc = vc->down)
+ {
+ if (vc_isfuncenv (vc) || vc_isbltnenv (vc))
+ {
+ v = hash_lookup (name, vc->table);
+ if (v)
+ return (bind_variable_internal (name, value, vc->table, 0, flags));
+ }
+ }
+ return (bind_variable_internal (name, value, global_variables->table, 0, flags));
+}
+
+/* Make VAR, a simple shell variable, have value VALUE. Once assigned a
+ value, variables are no longer invisible. This is a duplicate of part
+ of the internals of bind_variable. If the variable is exported, or
+ all modified variables should be exported, mark the variable for export
+ and note that the export environment needs to be recreated. */
+SHELL_VAR *
+bind_variable_value (var, value, aflags)
+ SHELL_VAR *var;
+ char *value;
+ int aflags;
+{
+ char *t;
+
+ VUNSETATTR (var, att_invisible);
+
+ if (var->assign_func)
+ {
+ /* If we're appending, we need the old value, so use
+ make_variable_value */
+ t = (aflags & ASS_APPEND) ? make_variable_value (var, value, aflags) : value;
+ (*(var->assign_func)) (var, t, -1, 0);
+ if (t != value && t)
+ free (t);
+ }
+ else
+ {
+ t = make_variable_value (var, value, aflags);
+ FREE (value_cell (var));
+ var_setvalue (var, t);
+ }
+
+ INVALIDATE_EXPORTSTR (var);
+
+ if (mark_modified_vars)
+ VSETATTR (var, att_exported);
+
+ if (exported_p (var))
+ array_needs_making = 1;
+
+ return (var);
+}
+
+/* Bind/create a shell variable with the name LHS to the RHS.
+ This creates or modifies a variable such that it is an integer.
+
+ This used to be in expr.c, but it is here so that all of the
+ variable binding stuff is localized. Since we don't want any
+ recursive evaluation from bind_variable() (possible without this code,
+ since bind_variable() calls the evaluator for variables with the integer
+ attribute set), we temporarily turn off the integer attribute for each
+ variable we set here, then turn it back on after binding as necessary. */
+
+SHELL_VAR *
+bind_int_variable (lhs, rhs)
+ char *lhs, *rhs;
+{
+ register SHELL_VAR *v;
+ int isint, isarr;
+
+ isint = isarr = 0;
+#if defined (ARRAY_VARS)
+ if (valid_array_reference (lhs))
+ {
+ isarr = 1;
+ v = array_variable_part (lhs, (char **)0, (int *)0);
+ }
+ else
+#endif
+ v = find_variable (lhs);
+
+ if (v)
+ {
+ isint = integer_p (v);
+ VUNSETATTR (v, att_integer);
+ }
+
+#if defined (ARRAY_VARS)
+ if (isarr)
+ v = assign_array_element (lhs, rhs, 0);
+ else
+#endif
+ v = bind_variable (lhs, rhs, 0);
+
+ if (isint)
+ VSETATTR (v, att_integer);
+
+ return (v);
+}
+
+SHELL_VAR *
+bind_var_to_int (var, val)
+ char *var;
+ intmax_t val;
+{
+ char ibuf[INT_STRLEN_BOUND (intmax_t) + 1], *p;
+
+ p = fmtulong (val, 10, ibuf, sizeof (ibuf), 0);
+ return (bind_int_variable (var, p));
+}
+
+/* Do a function binding to a variable. You pass the name and
+ the command to bind to. This conses the name and command. */
+SHELL_VAR *
+bind_function (name, value)
+ const char *name;
+ COMMAND *value;
+{
+ SHELL_VAR *entry;
+
+ entry = find_function (name);
+ if (entry == 0)
+ {
+ BUCKET_CONTENTS *elt;
+
+ elt = hash_insert (savestring (name), shell_functions, HASH_NOSRCH);
+ entry = new_shell_variable (name);
+ elt->data = (PTR_T)entry;
+ }
+ else
+ INVALIDATE_EXPORTSTR (entry);
+
+ if (var_isset (entry))
+ dispose_command (function_cell (entry));
+
+ if (value)
+ var_setfunc (entry, copy_command (value));
+ else
+ var_setfunc (entry, 0);
+
+ VSETATTR (entry, att_function);
+
+ if (mark_modified_vars)
+ VSETATTR (entry, att_exported);
+
+ VUNSETATTR (entry, att_invisible); /* Just to be sure */
+
+ if (exported_p (entry))
+ array_needs_making = 1;
+
+#if defined (PROGRAMMABLE_COMPLETION)
+ set_itemlist_dirty (&it_functions);
+#endif
+
+ return (entry);
+}
+
+#if defined (DEBUGGER)
+/* Bind a function definition, which includes source file and line number
+ information in addition to the command, into the FUNCTION_DEF hash table.*/
+void
+bind_function_def (name, value)
+ const char *name;
+ FUNCTION_DEF *value;
+{
+ FUNCTION_DEF *entry;
+ BUCKET_CONTENTS *elt;
+ COMMAND *cmd;
+
+ entry = find_function_def (name);
+ if (entry)
+ {
+ dispose_function_def_contents (entry);
+ entry = copy_function_def_contents (value, entry);
+ }
+ else
+ {
+ cmd = value->command;
+ value->command = 0;
+ entry = copy_function_def (value);
+ value->command = cmd;
+
+ elt = hash_insert (savestring (name), shell_function_defs, HASH_NOSRCH);
+ elt->data = (PTR_T *)entry;
+ }
+}
+#endif /* DEBUGGER */
+
+/* Add STRING, which is of the form foo=bar, to the temporary environment
+ HASH_TABLE (temporary_env). The functions in execute_cmd.c are
+ responsible for moving the main temporary env to one of the other
+ temporary environments. The expansion code in subst.c calls this. */
+int
+assign_in_env (word)
+ WORD_DESC *word;
+{
+ int offset;
+ char *name, *temp, *value;
+ SHELL_VAR *var;
+ const char *string;
+
+ string = word->word;
+
+ offset = assignment (string, 0);
+ name = savestring (string);
+ value = (char *)NULL;
+
+ if (name[offset] == '=')
+ {
+ name[offset] = 0;
+
+ /* ignore the `+' when assigning temporary environment */
+ if (name[offset - 1] == '+')
+ name[offset - 1] = '\0';
+
+ var = find_variable (name);
+ if (var && (readonly_p (var) || noassign_p (var)))
+ {
+ if (readonly_p (var))
+ err_readonly (name);
+ free (name);
+ return (0);
+ }
+
+ temp = name + offset + 1;
+ value = expand_assignment_string_to_string (temp, 0);
+ }
+
+ if (temporary_env == 0)
+ temporary_env = hash_create (TEMPENV_HASH_BUCKETS);
+
+ var = hash_lookup (name, temporary_env);
+ if (var == 0)
+ var = make_new_variable (name, temporary_env);
+ else
+ FREE (value_cell (var));
+
+ if (value == 0)
+ {
+ value = (char *)xmalloc (1); /* like do_assignment_internal */
+ value[0] = '\0';
+ }
+
+ var_setvalue (var, value);
+ var->attributes |= (att_exported|att_tempvar);
+ var->context = variable_context; /* XXX */
+
+ INVALIDATE_EXPORTSTR (var);
+ var->exportstr = mk_env_string (name, value);
+
+ array_needs_making = 1;
+
+ if (ifsname (name))
+ setifs (var);
+
+ if (echo_command_at_execute)
+ /* The Korn shell prints the `+ ' in front of assignment statements,
+ so we do too. */
+ xtrace_print_assignment (name, value, 0, 1);
+
+ free (name);
+ return 1;
+}
+
+/* **************************************************************** */
+/* */
+/* Copying variables */
+/* */
+/* **************************************************************** */
+
+#ifdef INCLUDE_UNUSED
+/* Copy VAR to a new data structure and return that structure. */
+SHELL_VAR *
+copy_variable (var)
+ SHELL_VAR *var;
+{
+ SHELL_VAR *copy = (SHELL_VAR *)NULL;
+
+ if (var)
+ {
+ copy = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
+
+ copy->attributes = var->attributes;
+ copy->name = savestring (var->name);
+
+ if (function_p (var))
+ var_setfunc (copy, copy_command (function_cell (var)));
+#if defined (ARRAY_VARS)
+ else if (array_p (var))
+ var_setarray (copy, array_copy (array_cell (var)));
+ else if (assoc_p (var))
+ var_setassoc (copy, assoc_copy (assoc_cell (var)));
+#endif
+ else if (value_cell (var))
+ var_setvalue (copy, savestring (value_cell (var)));
+ else
+ var_setvalue (copy, (char *)NULL);
+
+ copy->dynamic_value = var->dynamic_value;
+ copy->assign_func = var->assign_func;
+
+ copy->exportstr = COPY_EXPORTSTR (var);
+
+ copy->context = var->context;
+ }
+ return (copy);
+}
+#endif
+
+/* **************************************************************** */
+/* */
+/* Deleting and unsetting variables */
+/* */
+/* **************************************************************** */
+
+/* Dispose of the information attached to VAR. */
+static void
+dispose_variable_value (var)
+ SHELL_VAR *var;
+{
+ if (function_p (var))
+ dispose_command (function_cell (var));
+#if defined (ARRAY_VARS)
+ else if (array_p (var))
+ array_dispose (array_cell (var));
+ else if (assoc_p (var))
+ assoc_dispose (assoc_cell (var));
+#endif
+ else
+ FREE (value_cell (var));
+}
+
+void
+dispose_variable (var)
+ SHELL_VAR *var;
+{
+ if (var == 0)
+ return;
+
+ if (nofree_p (var) == 0)
+ dispose_variable_value (var);
+
+ FREE_EXPORTSTR (var);
+
+ free (var->name);
+
+ if (exported_p (var))
+ array_needs_making = 1;
+
+ free (var);
+}
+
+/* Unset the shell variable referenced by NAME. */
+int
+unbind_variable (name)
+ const char *name;
+{
+ return makunbound (name, shell_variables);
+}
+
+/* Unset the shell function named NAME. */
+int
+unbind_func (name)
+ const char *name;
+{
+ BUCKET_CONTENTS *elt;
+ SHELL_VAR *func;
+
+ elt = hash_remove (name, shell_functions, 0);
+
+ if (elt == 0)
+ return -1;
+
+#if defined (PROGRAMMABLE_COMPLETION)
+ set_itemlist_dirty (&it_functions);
+#endif
+
+ func = (SHELL_VAR *)elt->data;
+ if (func)
+ {
+ if (exported_p (func))
+ array_needs_making++;
+ dispose_variable (func);
+ }
+
+ free (elt->key);
+ free (elt);
+
+ return 0;
+}
+
+#if defined (DEBUGGER)
+int
+unbind_function_def (name)
+ const char *name;
+{
+ BUCKET_CONTENTS *elt;
+ FUNCTION_DEF *funcdef;
+
+ elt = hash_remove (name, shell_function_defs, 0);
+
+ if (elt == 0)
+ return -1;
+
+ funcdef = (FUNCTION_DEF *)elt->data;
+ if (funcdef)
+ dispose_function_def (funcdef);
+
+ free (elt->key);
+ free (elt);
+
+ return 0;
+}
+#endif /* DEBUGGER */
+
+/* Make the variable associated with NAME go away. HASH_LIST is the
+ hash table from which this variable should be deleted (either
+ shell_variables or shell_functions).
+ Returns non-zero if the variable couldn't be found. */
+int
+makunbound (name, vc)
+ const char *name;
+ VAR_CONTEXT *vc;
+{
+ BUCKET_CONTENTS *elt, *new_elt;
+ SHELL_VAR *old_var;
+ VAR_CONTEXT *v;
+ char *t;
+
+ for (elt = (BUCKET_CONTENTS *)NULL, v = vc; v; v = v->down)
+ if (elt = hash_remove (name, v->table, 0))
+ break;
+
+ if (elt == 0)
+ return (-1);
+
+ old_var = (SHELL_VAR *)elt->data;
+
+ if (old_var && exported_p (old_var))
+ array_needs_making++;
+
+ /* If we're unsetting a local variable and we're still executing inside
+ the function, just mark the variable as invisible. The function
+ eventually called by pop_var_context() will clean it up later. This
+ must be done so that if the variable is subsequently assigned a new
+ value inside the function, the `local' attribute is still present.
+ We also need to add it back into the correct hash table. */
+ if (old_var && local_p (old_var) && variable_context == old_var->context)
+ {
+ if (nofree_p (old_var))
+ var_setvalue (old_var, (char *)NULL);
+#if defined (ARRAY_VARS)
+ else if (array_p (old_var))
+ array_dispose (array_cell (old_var));
+ else if (assoc_p (old_var))
+ assoc_dispose (assoc_cell (old_var));
+#endif
+ else
+ FREE (value_cell (old_var));
+ /* Reset the attributes. Preserve the export attribute if the variable
+ came from a temporary environment. Make sure it stays local, and
+ make it invisible. */
+ old_var->attributes = (exported_p (old_var) && tempvar_p (old_var)) ? att_exported : 0;
+ VSETATTR (old_var, att_local);
+ VSETATTR (old_var, att_invisible);
+ var_setvalue (old_var, (char *)NULL);
+ INVALIDATE_EXPORTSTR (old_var);
+
+ new_elt = hash_insert (savestring (old_var->name), v->table, 0);
+ new_elt->data = (PTR_T)old_var;
+ stupidly_hack_special_variables (old_var->name);
+
+ free (elt->key);
+ free (elt);
+ return (0);
+ }
+
+ /* Have to save a copy of name here, because it might refer to
+ old_var->name. If so, stupidly_hack_special_variables will
+ reference freed memory. */
+ t = savestring (name);
+
+ free (elt->key);
+ free (elt);
+
+ dispose_variable (old_var);
+ stupidly_hack_special_variables (t);
+ free (t);
+
+ return (0);
+}
+
+/* Get rid of all of the variables in the current context. */
+void
+kill_all_local_variables ()
+{
+ VAR_CONTEXT *vc;
+
+ for (vc = shell_variables; vc; vc = vc->down)
+ if (vc_isfuncenv (vc) && vc->scope == variable_context)
+ break;
+ if (vc == 0)
+ return; /* XXX */
+
+ if (vc->table && vc_haslocals (vc))
+ {
+ delete_all_variables (vc->table);
+ hash_dispose (vc->table);
+ }
+ vc->table = (HASH_TABLE *)NULL;
+}
+
+static void
+free_variable_hash_data (data)
+ PTR_T data;
+{
+ SHELL_VAR *var;
+
+ var = (SHELL_VAR *)data;
+ dispose_variable (var);
+}
+
+/* Delete the entire contents of the hash table. */
+void
+delete_all_variables (hashed_vars)
+ HASH_TABLE *hashed_vars;
+{
+ hash_flush (hashed_vars, free_variable_hash_data);
+}
+
+/* **************************************************************** */
+/* */
+/* Setting variable attributes */
+/* */
+/* **************************************************************** */
+
+#define FIND_OR_MAKE_VARIABLE(name, entry) \
+ do \
+ { \
+ entry = find_variable (name); \
+ if (!entry) \
+ { \
+ entry = bind_variable (name, "", 0); \
+ if (!no_invisible_vars) entry->attributes |= att_invisible; \
+ } \
+ } \
+ while (0)
+
+/* Make the variable associated with NAME be readonly.
+ If NAME does not exist yet, create it. */
+void
+set_var_read_only (name)
+ char *name;
+{
+ SHELL_VAR *entry;
+
+ FIND_OR_MAKE_VARIABLE (name, entry);
+ VSETATTR (entry, att_readonly);
+}
+
+#ifdef INCLUDE_UNUSED
+/* Make the function associated with NAME be readonly.
+ If NAME does not exist, we just punt, like auto_export code below. */
+void
+set_func_read_only (name)
+ const char *name;
+{
+ SHELL_VAR *entry;
+
+ entry = find_function (name);
+ if (entry)
+ VSETATTR (entry, att_readonly);
+}
+
+/* Make the variable associated with NAME be auto-exported.
+ If NAME does not exist yet, create it. */
+void
+set_var_auto_export (name)
+ char *name;
+{
+ SHELL_VAR *entry;
+
+ FIND_OR_MAKE_VARIABLE (name, entry);
+ set_auto_export (entry);
+}
+
+/* Make the function associated with NAME be auto-exported. */
+void
+set_func_auto_export (name)
+ const char *name;
+{
+ SHELL_VAR *entry;
+
+ entry = find_function (name);
+ if (entry)
+ set_auto_export (entry);
+}
+#endif
+
+/* **************************************************************** */
+/* */
+/* Creating lists of variables */
+/* */
+/* **************************************************************** */
+
+static VARLIST *
+vlist_alloc (nentries)
+ int nentries;
+{
+ VARLIST *vlist;
+
+ vlist = (VARLIST *)xmalloc (sizeof (VARLIST));
+ vlist->list = (SHELL_VAR **)xmalloc ((nentries + 1) * sizeof (SHELL_VAR *));
+ vlist->list_size = nentries;
+ vlist->list_len = 0;
+ vlist->list[0] = (SHELL_VAR *)NULL;
+
+ return vlist;
+}
+
+static VARLIST *
+vlist_realloc (vlist, n)
+ VARLIST *vlist;
+ int n;
+{
+ if (vlist == 0)
+ return (vlist = vlist_alloc (n));
+ if (n > vlist->list_size)
+ {
+ vlist->list_size = n;
+ vlist->list = (SHELL_VAR **)xrealloc (vlist->list, (vlist->list_size + 1) * sizeof (SHELL_VAR *));
+ }
+ return vlist;
+}
+
+static void
+vlist_add (vlist, var, flags)
+ VARLIST *vlist;
+ SHELL_VAR *var;
+ int flags;
+{
+ register int i;
+
+ for (i = 0; i < vlist->list_len; i++)
+ if (STREQ (var->name, vlist->list[i]->name))
+ break;
+ if (i < vlist->list_len)
+ return;
+
+ if (i >= vlist->list_size)
+ vlist = vlist_realloc (vlist, vlist->list_size + 16);
+
+ vlist->list[vlist->list_len++] = var;
+ vlist->list[vlist->list_len] = (SHELL_VAR *)NULL;
+}
+
+/* Map FUNCTION over the variables in VAR_HASH_TABLE. Return an array of the
+ variables for which FUNCTION returns a non-zero value. A NULL value
+ for FUNCTION means to use all variables. */
+SHELL_VAR **
+map_over (function, vc)
+ sh_var_map_func_t *function;
+ VAR_CONTEXT *vc;
+{
+ VAR_CONTEXT *v;
+ VARLIST *vlist;
+ SHELL_VAR **ret;
+ int nentries;
+
+ for (nentries = 0, v = vc; v; v = v->down)
+ nentries += HASH_ENTRIES (v->table);
+
+ if (nentries == 0)
+ return (SHELL_VAR **)NULL;
+
+ vlist = vlist_alloc (nentries);
+
+ for (v = vc; v; v = v->down)
+ flatten (v->table, function, vlist, 0);
+
+ ret = vlist->list;
+ free (vlist);
+ return ret;
+}
+
+SHELL_VAR **
+map_over_funcs (function)
+ sh_var_map_func_t *function;
+{
+ VARLIST *vlist;
+ SHELL_VAR **ret;
+
+ if (shell_functions == 0 || HASH_ENTRIES (shell_functions) == 0)
+ return ((SHELL_VAR **)NULL);
+
+ vlist = vlist_alloc (HASH_ENTRIES (shell_functions));
+
+ flatten (shell_functions, function, vlist, 0);
+
+ ret = vlist->list;
+ free (vlist);
+ return ret;
+}
+
+/* Flatten VAR_HASH_TABLE, applying FUNC to each member and adding those
+ elements for which FUNC succeeds to VLIST->list. FLAGS is reserved
+ for future use. Only unique names are added to VLIST. If FUNC is
+ NULL, each variable in VAR_HASH_TABLE is added to VLIST. If VLIST is
+ NULL, FUNC is applied to each SHELL_VAR in VAR_HASH_TABLE. If VLIST
+ and FUNC are both NULL, nothing happens. */
+static void
+flatten (var_hash_table, func, vlist, flags)
+ HASH_TABLE *var_hash_table;
+ sh_var_map_func_t *func;
+ VARLIST *vlist;
+ int flags;
+{
+ register int i;
+ register BUCKET_CONTENTS *tlist;
+ int r;
+ SHELL_VAR *var;
+
+ if (var_hash_table == 0 || (HASH_ENTRIES (var_hash_table) == 0) || (vlist == 0 && func == 0))
+ return;
+
+ for (i = 0; i < var_hash_table->nbuckets; i++)
+ {
+ for (tlist = hash_items (i, var_hash_table); tlist; tlist = tlist->next)
+ {
+ var = (SHELL_VAR *)tlist->data;
+
+ r = func ? (*func) (var) : 1;
+ if (r && vlist)
+ vlist_add (vlist, var, flags);
+ }
+ }
+}
+
+void
+sort_variables (array)
+ SHELL_VAR **array;
+{
+ qsort (array, strvec_len ((char **)array), sizeof (SHELL_VAR *), (QSFUNC *)qsort_var_comp);
+}
+
+static int
+qsort_var_comp (var1, var2)
+ SHELL_VAR **var1, **var2;
+{
+ int result;
+
+ if ((result = (*var1)->name[0] - (*var2)->name[0]) == 0)
+ result = strcmp ((*var1)->name, (*var2)->name);
+
+ return (result);
+}
+
+/* Apply FUNC to each variable in SHELL_VARIABLES, adding each one for
+ which FUNC succeeds to an array of SHELL_VAR *s. Returns the array. */
+static SHELL_VAR **
+vapply (func)
+ sh_var_map_func_t *func;
+{
+ SHELL_VAR **list;
+
+ list = map_over (func, shell_variables);
+ if (list /* && posixly_correct */)
+ sort_variables (list);
+ return (list);
+}
+
+/* Apply FUNC to each variable in SHELL_FUNCTIONS, adding each one for
+ which FUNC succeeds to an array of SHELL_VAR *s. Returns the array. */
+static SHELL_VAR **
+fapply (func)
+ sh_var_map_func_t *func;
+{
+ SHELL_VAR **list;
+
+ list = map_over_funcs (func);
+ if (list /* && posixly_correct */)
+ sort_variables (list);
+ return (list);
+}
+
+/* Create a NULL terminated array of all the shell variables. */
+SHELL_VAR **
+all_shell_variables ()
+{
+ return (vapply ((sh_var_map_func_t *)NULL));
+}
+
+/* Create a NULL terminated array of all the shell functions. */
+SHELL_VAR **
+all_shell_functions ()
+{
+ return (fapply ((sh_var_map_func_t *)NULL));
+}
+
+static int
+visible_var (var)
+ SHELL_VAR *var;
+{
+ return (invisible_p (var) == 0);
+}
+
+SHELL_VAR **
+all_visible_functions ()
+{
+ return (fapply (visible_var));
+}
+
+SHELL_VAR **
+all_visible_variables ()
+{
+ return (vapply (visible_var));
+}
+
+/* Return non-zero if the variable VAR is visible and exported. Array
+ variables cannot be exported. */
+static int
+visible_and_exported (var)
+ SHELL_VAR *var;
+{
+ return (invisible_p (var) == 0 && exported_p (var));
+}
+
+/* Candidate variables for the export environment are either valid variables
+ with the export attribute or invalid variables inherited from the initial
+ environment and simply passed through. */
+static int
+export_environment_candidate (var)
+ SHELL_VAR *var;
+{
+ return (exported_p (var) && (invisible_p (var) == 0 || imported_p (var)));
+}
+
+/* Return non-zero if VAR is a local variable in the current context and
+ is exported. */
+static int
+local_and_exported (var)
+ SHELL_VAR *var;
+{
+ return (invisible_p (var) == 0 && local_p (var) && var->context == variable_context && exported_p (var));
+}
+
+SHELL_VAR **
+all_exported_variables ()
+{
+ return (vapply (visible_and_exported));
+}
+
+SHELL_VAR **
+local_exported_variables ()
+{
+ return (vapply (local_and_exported));
+}
+
+static int
+variable_in_context (var)
+ SHELL_VAR *var;
+{
+ return (invisible_p (var) == 0 && local_p (var) && var->context == variable_context);
+}
+
+SHELL_VAR **
+all_local_variables ()
+{
+ VARLIST *vlist;
+ SHELL_VAR **ret;
+ VAR_CONTEXT *vc;
+
+ vc = shell_variables;
+ for (vc = shell_variables; vc; vc = vc->down)
+ if (vc_isfuncenv (vc) && vc->scope == variable_context)
+ break;
+
+ if (vc == 0)
+ {
+ internal_error (_("all_local_variables: no function context at current scope"));
+ return (SHELL_VAR **)NULL;
+ }
+ if (vc->table == 0 || HASH_ENTRIES (vc->table) == 0 || vc_haslocals (vc) == 0)
+ return (SHELL_VAR **)NULL;
+
+ vlist = vlist_alloc (HASH_ENTRIES (vc->table));
+
+ flatten (vc->table, variable_in_context, vlist, 0);
+
+ ret = vlist->list;
+ free (vlist);
+ if (ret)
+ sort_variables (ret);
+ return ret;
+}
+
+#if defined (ARRAY_VARS)
+/* Return non-zero if the variable VAR is visible and an array. */
+static int
+visible_array_vars (var)
+ SHELL_VAR *var;
+{
+ return (invisible_p (var) == 0 && array_p (var));
+}
+
+SHELL_VAR **
+all_array_variables ()
+{
+ return (vapply (visible_array_vars));
+}
+#endif /* ARRAY_VARS */
+
+char **
+all_variables_matching_prefix (prefix)
+ const char *prefix;
+{
+ SHELL_VAR **varlist;
+ char **rlist;
+ int vind, rind, plen;
+
+ plen = STRLEN (prefix);
+ varlist = all_visible_variables ();
+ for (vind = 0; varlist && varlist[vind]; vind++)
+ ;
+ if (varlist == 0 || vind == 0)
+ return ((char **)NULL);
+ rlist = strvec_create (vind + 1);
+ for (vind = rind = 0; varlist[vind]; vind++)
+ {
+ if (plen == 0 || STREQN (prefix, varlist[vind]->name, plen))
+ rlist[rind++] = savestring (varlist[vind]->name);
+ }
+ rlist[rind] = (char *)0;
+ free (varlist);
+
+ return rlist;
+}
+
+/* **************************************************************** */
+/* */
+/* Managing temporary variable scopes */
+/* */
+/* **************************************************************** */
+
+/* Make variable NAME have VALUE in the temporary environment. */
+static SHELL_VAR *
+bind_tempenv_variable (name, value)
+ const char *name;
+ char *value;
+{
+ SHELL_VAR *var;
+
+ var = temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL;
+
+ if (var)
+ {
+ FREE (value_cell (var));
+ var_setvalue (var, savestring (value));
+ INVALIDATE_EXPORTSTR (var);
+ }
+
+ return (var);
+}
+
+/* Find a variable in the temporary environment that is named NAME.
+ Return the SHELL_VAR *, or NULL if not found. */
+SHELL_VAR *
+find_tempenv_variable (name)
+ const char *name;
+{
+ return (temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL);
+}
+
+/* Push the variable described by (SHELL_VAR *)DATA down to the next
+ variable context from the temporary environment. */
+static void
+push_temp_var (data)
+ PTR_T data;
+{
+ SHELL_VAR *var, *v;
+ HASH_TABLE *binding_table;
+
+ var = (SHELL_VAR *)data;
+
+ binding_table = shell_variables->table;
+ if (binding_table == 0)
+ {
+ if (shell_variables == global_variables)
+ /* shouldn't happen */
+ binding_table = shell_variables->table = global_variables->table = hash_create (0);
+ else
+ binding_table = shell_variables->table = hash_create (TEMPENV_HASH_BUCKETS);
+ }
+
+ v = bind_variable_internal (var->name, value_cell (var), binding_table, 0, 0);
+
+ /* XXX - should we set the context here? It shouldn't matter because of how
+ assign_in_env works, but might want to check. */
+ if (binding_table == global_variables->table) /* XXX */
+ var->attributes &= ~(att_tempvar|att_propagate);
+ else
+ {
+ var->attributes |= att_propagate;
+ if (binding_table == shell_variables->table)
+ shell_variables->flags |= VC_HASTMPVAR;
+ }
+ v->attributes |= var->attributes;
+
+ dispose_variable (var);
+}
+
+static void
+propagate_temp_var (data)
+ PTR_T data;
+{
+ SHELL_VAR *var;
+
+ var = (SHELL_VAR *)data;
+ if (tempvar_p (var) && (var->attributes & att_propagate))
+ push_temp_var (data);
+ else
+ 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. */
+static void
+dispose_temporary_env (pushf)
+ sh_free_func_t *pushf;
+{
+ hash_flush (temporary_env, pushf);
+ hash_dispose (temporary_env);
+ temporary_env = (HASH_TABLE *)NULL;
+
+ array_needs_making = 1;
+
+ sv_ifs ("IFS"); /* XXX here for now */
+}
+
+void
+dispose_used_env_vars ()
+{
+ if (temporary_env)
+ {
+ dispose_temporary_env (propagate_temp_var);
+ maybe_make_export_env ();
+ }
+}
+
+/* Take all of the shell variables in the temporary environment HASH_TABLE
+ and make shell variables from them at the current variable context. */
+void
+merge_temporary_env ()
+{
+ if (temporary_env)
+ dispose_temporary_env (push_temp_var);
+}
+
+/* **************************************************************** */
+/* */
+/* Creating and manipulating the environment */
+/* */
+/* **************************************************************** */
+
+static inline char *
+mk_env_string (name, value)
+ const char *name, *value;
+{
+ int name_len, value_len;
+ char *p;
+
+ name_len = strlen (name);
+ value_len = STRLEN (value);
+ p = (char *)xmalloc (2 + name_len + value_len);
+ strcpy (p, name);
+ p[name_len] = '=';
+ if (value && *value)
+ strcpy (p + name_len + 1, value);
+ else
+ p[name_len + 1] = '\0';
+ return (p);
+}
+
+#ifdef DEBUG
+/* Debugging */
+static int
+valid_exportstr (v)
+ SHELL_VAR *v;
+{
+ char *s;
+
+ s = v->exportstr;
+ if (s == 0)
+ {
+ internal_error (_("%s has null exportstr"), v->name);
+ return (0);
+ }
+ if (legal_variable_starter ((unsigned char)*s) == 0)
+ {
+ internal_error (_("invalid character %d in exportstr for %s"), *s, v->name);
+ return (0);
+ }
+ for (s = v->exportstr + 1; s && *s; s++)
+ {
+ if (*s == '=')
+ break;
+ if (legal_variable_char ((unsigned char)*s) == 0)
+ {
+ internal_error (_("invalid character %d in exportstr for %s"), *s, v->name);
+ return (0);
+ }
+ }
+ if (*s != '=')
+ {
+ internal_error (_("no `=' in exportstr for %s"), v->name);
+ return (0);
+ }
+ return (1);
+}
+#endif
+
+static char **
+make_env_array_from_var_list (vars)
+ SHELL_VAR **vars;
+{
+ register int i, list_index;
+ register SHELL_VAR *var;
+ char **list, *value;
+
+ list = strvec_create ((1 + strvec_len ((char **)vars)));
+
+#define USE_EXPORTSTR (value == var->exportstr)
+
+ for (i = 0, list_index = 0; var = vars[i]; i++)
+ {
+#if defined (__CYGWIN__)
+ /* We don't use the exportstr stuff on Cygwin at all. */
+ INVALIDATE_EXPORTSTR (var);
+#endif
+ if (var->exportstr)
+ value = var->exportstr;
+ else if (function_p (var))
+ value = named_function_string ((char *)NULL, function_cell (var), 0);
+#if defined (ARRAY_VARS)
+ else if (array_p (var))
+# if 0
+ value = array_to_assignment_string (array_cell (var));
+# else
+ continue; /* XXX array vars cannot yet be exported */
+# endif
+ else if (assoc_p (var))
+# if 0
+ value = assoc_to_assignment_string (assoc_cell (var));
+# else
+ continue; /* XXX associative array vars cannot yet be exported */
+# endif
+#endif
+ else
+ value = value_cell (var);
+
+ if (value)
+ {
+ /* Gee, I'd like to get away with not using savestring() if we're
+ using the cached exportstr... */
+ list[list_index] = USE_EXPORTSTR ? savestring (value)
+ : mk_env_string (var->name, value);
+
+ if (USE_EXPORTSTR == 0)
+ SAVE_EXPORTSTR (var, list[list_index]);
+
+ list_index++;
+#undef USE_EXPORTSTR
+
+#if 0 /* not yet */
+#if defined (ARRAY_VARS)
+ if (array_p (var) || assoc_p (var))
+ free (value);
+#endif
+#endif
+ }
+ }
+
+ list[list_index] = (char *)NULL;
+ return (list);
+}
+
+/* Make an array of assignment statements from the hash table
+ HASHED_VARS which contains SHELL_VARs. Only visible, exported
+ variables are eligible. */
+static char **
+make_var_export_array (vcxt)
+ VAR_CONTEXT *vcxt;
+{
+ char **list;
+ SHELL_VAR **vars;
+
+#if 0
+ vars = map_over (visible_and_exported, vcxt);
+#else
+ vars = map_over (export_environment_candidate, vcxt);
+#endif
+
+ if (vars == 0)
+ return (char **)NULL;
+
+ list = make_env_array_from_var_list (vars);
+
+ free (vars);
+ return (list);
+}
+
+static char **
+make_func_export_array ()
+{
+ char **list;
+ SHELL_VAR **vars;
+
+ vars = map_over_funcs (visible_and_exported);
+ if (vars == 0)
+ return (char **)NULL;
+
+ list = make_env_array_from_var_list (vars);
+
+ free (vars);
+ return (list);
+}
+
+/* Add ENVSTR to the end of the exported environment, EXPORT_ENV. */
+#define add_to_export_env(envstr,do_alloc) \
+do \
+ { \
+ if (export_env_index >= (export_env_size - 1)) \
+ { \
+ export_env_size += 16; \
+ export_env = strvec_resize (export_env, export_env_size); \
+ environ = export_env; \
+ } \
+ export_env[export_env_index++] = (do_alloc) ? savestring (envstr) : envstr; \
+ export_env[export_env_index] = (char *)NULL; \
+ } while (0)
+
+/* Add ASSIGN to EXPORT_ENV, or supercede a previous assignment in the
+ array with the same left-hand side. Return the new EXPORT_ENV. */
+char **
+add_or_supercede_exported_var (assign, do_alloc)
+ char *assign;
+ int do_alloc;
+{
+ register int i;
+ int equal_offset;
+
+ equal_offset = assignment (assign, 0);
+ if (equal_offset == 0)
+ return (export_env);
+
+ /* If this is a function, then only supersede the function definition.
+ We do this by including the `=() {' in the comparison, like
+ initialize_shell_variables does. */
+ if (assign[equal_offset + 1] == '(' &&
+ strncmp (assign + equal_offset + 2, ") {", 3) == 0) /* } */
+ equal_offset += 4;
+
+ for (i = 0; i < export_env_index; i++)
+ {
+ if (STREQN (assign, export_env[i], equal_offset + 1))
+ {
+ free (export_env[i]);
+ export_env[i] = do_alloc ? savestring (assign) : assign;
+ return (export_env);
+ }
+ }
+ add_to_export_env (assign, do_alloc);
+ return (export_env);
+}
+
+static void
+add_temp_array_to_env (temp_array, do_alloc, do_supercede)
+ char **temp_array;
+ int do_alloc, do_supercede;
+{
+ register int i;
+
+ if (temp_array == 0)
+ return;
+
+ for (i = 0; temp_array[i]; i++)
+ {
+ if (do_supercede)
+ export_env = add_or_supercede_exported_var (temp_array[i], do_alloc);
+ else
+ add_to_export_env (temp_array[i], do_alloc);
+ }
+
+ free (temp_array);
+}
+
+/* Make the environment array for the command about to be executed, if the
+ array needs making. Otherwise, do nothing. If a shell action could
+ change the array that commands receive for their environment, then the
+ code should `array_needs_making++'.
+
+ The order to add to the array is:
+ temporary_env
+ list of var contexts whose head is shell_variables
+ shell_functions
+
+ This is the shell variable lookup order. We add only new variable
+ names at each step, which allows local variables and variables in
+ the temporary environments to shadow variables in the global (or
+ any previous) scope.
+*/
+
+static int
+n_shell_variables ()
+{
+ VAR_CONTEXT *vc;
+ int n;
+
+ for (n = 0, vc = shell_variables; vc; vc = vc->down)
+ n += HASH_ENTRIES (vc->table);
+ return n;
+}
+
+void
+maybe_make_export_env ()
+{
+ register char **temp_array;
+ int new_size;
+ VAR_CONTEXT *tcxt;
+
+ if (array_needs_making)
+ {
+ if (export_env)
+ strvec_flush (export_env);
+
+ /* Make a guess based on how many shell variables and functions we
+ have. Since there will always be array variables, and array
+ variables are not (yet) exported, this will always be big enough
+ for the exported variables and functions. */
+ new_size = n_shell_variables () + HASH_ENTRIES (shell_functions) + 1 +
+ HASH_ENTRIES (temporary_env);
+ if (new_size > export_env_size)
+ {
+ export_env_size = new_size;
+ export_env = strvec_resize (export_env, export_env_size);
+ environ = export_env;
+ }
+ export_env[export_env_index = 0] = (char *)NULL;
+
+ /* Make a dummy variable context from the temporary_env, stick it on
+ the front of shell_variables, call make_var_export_array on the
+ whole thing to flatten it, and convert the list of SHELL_VAR *s
+ to the form needed by the environment. */
+ if (temporary_env)
+ {
+ tcxt = new_var_context ((char *)NULL, 0);
+ tcxt->table = temporary_env;
+ tcxt->down = shell_variables;
+ }
+ else
+ tcxt = shell_variables;
+
+ temp_array = make_var_export_array (tcxt);
+ if (temp_array)
+ add_temp_array_to_env (temp_array, 0, 0);
+
+ if (tcxt != shell_variables)
+ free (tcxt);
+
+#if defined (RESTRICTED_SHELL)
+ /* Restricted shells may not export shell functions. */
+ temp_array = restricted ? (char **)0 : make_func_export_array ();
+#else
+ temp_array = make_func_export_array ();
+#endif
+ if (temp_array)
+ add_temp_array_to_env (temp_array, 0, 0);
+
+ array_needs_making = 0;
+ }
+}
+
+/* This is an efficiency hack. PWD and OLDPWD are auto-exported, so
+ we will need to remake the exported environment every time we
+ change directories. `_' is always put into the environment for
+ every external command, so without special treatment it will always
+ cause the environment to be remade.
+
+ If there is no other reason to make the exported environment, we can
+ just update the variables in place and mark the exported environment
+ as no longer needing a remake. */
+void
+update_export_env_inplace (env_prefix, preflen, value)
+ char *env_prefix;
+ int preflen;
+ char *value;
+{
+ char *evar;
+
+ evar = (char *)xmalloc (STRLEN (value) + preflen + 1);
+ strcpy (evar, env_prefix);
+ if (value)
+ strcpy (evar + preflen, value);
+ export_env = add_or_supercede_exported_var (evar, 0);
+}
+
+/* We always put _ in the environment as the name of this command. */
+void
+put_command_name_into_env (command_name)
+ char *command_name;
+{
+ update_export_env_inplace ("_=", 2, command_name);
+}
+
+#if 0 /* UNUSED -- it caused too many problems */
+void
+put_gnu_argv_flags_into_env (pid, flags_string)
+ intmax_t pid;
+ char *flags_string;
+{
+ char *dummy, *pbuf;
+ int l, fl;
+
+ pbuf = itos (pid);
+ l = strlen (pbuf);
+
+ fl = strlen (flags_string);
+
+ dummy = (char *)xmalloc (l + fl + 30);
+ dummy[0] = '_';
+ strcpy (dummy + 1, pbuf);
+ strcpy (dummy + 1 + l, "_GNU_nonoption_argv_flags_");
+ dummy[l + 27] = '=';
+ strcpy (dummy + l + 28, flags_string);
+
+ free (pbuf);
+
+ export_env = add_or_supercede_exported_var (dummy, 0);
+}
+#endif
+
+/* **************************************************************** */
+/* */
+/* Managing variable contexts */
+/* */
+/* **************************************************************** */
+
+/* Allocate and return a new variable context with NAME and FLAGS.
+ NAME can be NULL. */
+
+VAR_CONTEXT *
+new_var_context (name, flags)
+ char *name;
+ int flags;
+{
+ VAR_CONTEXT *vc;
+
+ vc = (VAR_CONTEXT *)xmalloc (sizeof (VAR_CONTEXT));
+ vc->name = name ? savestring (name) : (char *)NULL;
+ vc->scope = variable_context;
+ vc->flags = flags;
+
+ vc->up = vc->down = (VAR_CONTEXT *)NULL;
+ vc->table = (HASH_TABLE *)NULL;
+
+ return vc;
+}
+
+/* Free a variable context and its data, including the hash table. Dispose
+ all of the variables. */
+void
+dispose_var_context (vc)
+ VAR_CONTEXT *vc;
+{
+ FREE (vc->name);
+
+ if (vc->table)
+ {
+ delete_all_variables (vc->table);
+ hash_dispose (vc->table);
+ }
+
+ free (vc);
+}
+
+/* Set VAR's scope level to the current variable context. */
+static int
+set_context (var)
+ SHELL_VAR *var;
+{
+ return (var->context = variable_context);
+}
+
+/* Make a new variable context with NAME and FLAGS and a HASH_TABLE of
+ temporary variables, and push it onto shell_variables. This is
+ for shell functions. */
+VAR_CONTEXT *
+push_var_context (name, flags, tempvars)
+ char *name;
+ int flags;
+ HASH_TABLE *tempvars;
+{
+ VAR_CONTEXT *vc;
+
+ vc = new_var_context (name, flags);
+ vc->table = tempvars;
+ if (tempvars)
+ {
+ /* Have to do this because the temp environment was created before
+ variable_context was incremented. */
+ flatten (tempvars, set_context, (VARLIST *)NULL, 0);
+ vc->flags |= VC_HASTMPVAR;
+ }
+ vc->down = shell_variables;
+ shell_variables->up = vc;
+
+ return (shell_variables = vc);
+}
+
+static void
+push_func_var (data)
+ PTR_T data;
+{
+ SHELL_VAR *var, *v;
+
+ var = (SHELL_VAR *)data;
+
+ if (tempvar_p (var) && (posixly_correct || (var->attributes & att_propagate)))
+ {
+ /* XXX - should we set v->context here? */
+ v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0, 0);
+ if (shell_variables == global_variables)
+ var->attributes &= ~(att_tempvar|att_propagate);
+ else
+ shell_variables->flags |= VC_HASTMPVAR;
+ v->attributes |= var->attributes;
+ }
+ else
+ stupidly_hack_special_variables (var->name); /* XXX */
+
+ dispose_variable (var);
+}
+
+/* Pop the top context off of VCXT and dispose of it, returning the rest of
+ the stack. */
+void
+pop_var_context ()
+{
+ VAR_CONTEXT *ret, *vcxt;
+
+ vcxt = shell_variables;
+ if (vc_isfuncenv (vcxt) == 0)
+ {
+ internal_error (_("pop_var_context: head of shell_variables not a function context"));
+ return;
+ }
+
+ if (ret = vcxt->down)
+ {
+ ret->up = (VAR_CONTEXT *)NULL;
+ shell_variables = ret;
+ if (vcxt->table)
+ hash_flush (vcxt->table, push_func_var);
+ dispose_var_context (vcxt);
+ }
+ else
+ internal_error (_("pop_var_context: no global_variables context"));
+}
+
+/* Delete the HASH_TABLEs for all variable contexts beginning at VCXT, and
+ all of the VAR_CONTEXTs except GLOBAL_VARIABLES. */
+void
+delete_all_contexts (vcxt)
+ VAR_CONTEXT *vcxt;
+{
+ VAR_CONTEXT *v, *t;
+
+ for (v = vcxt; v != global_variables; v = t)
+ {
+ t = v->down;
+ dispose_var_context (v);
+ }
+
+ delete_all_variables (global_variables->table);
+ shell_variables = global_variables;
+}
+
+/* **************************************************************** */
+/* */
+/* Pushing and Popping temporary variable scopes */
+/* */
+/* **************************************************************** */
+
+VAR_CONTEXT *
+push_scope (flags, tmpvars)
+ int flags;
+ HASH_TABLE *tmpvars;
+{
+ return (push_var_context ((char *)NULL, flags, tmpvars));
+}
+
+static void
+push_exported_var (data)
+ PTR_T data;
+{
+ SHELL_VAR *var, *v;
+
+ var = (SHELL_VAR *)data;
+
+ /* If a temp var had its export attribute set, or it's marked to be
+ propagated, bind it in the previous scope before disposing it. */
+ /* XXX - This isn't exactly right, because all tempenv variables have the
+ export attribute set. */
+#if 0
+ if (exported_p (var) || (var->attributes & att_propagate))
+#else
+ if (tempvar_p (var) && exported_p (var) && (var->attributes & att_propagate))
+#endif
+ {
+ var->attributes &= ~att_tempvar; /* XXX */
+ v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0, 0);
+ if (shell_variables == global_variables)
+ var->attributes &= ~att_propagate;
+ v->attributes |= var->attributes;
+ }
+ else
+ stupidly_hack_special_variables (var->name); /* XXX */
+
+ dispose_variable (var);
+}
+
+void
+pop_scope (is_special)
+ int is_special;
+{
+ VAR_CONTEXT *vcxt, *ret;
+
+ vcxt = shell_variables;
+ if (vc_istempscope (vcxt) == 0)
+ {
+ internal_error (_("pop_scope: head of shell_variables not a temporary environment scope"));
+ return;
+ }
+
+ ret = vcxt->down;
+ if (ret)
+ ret->up = (VAR_CONTEXT *)NULL;
+
+ shell_variables = ret;
+
+ /* Now we can take care of merging variables in VCXT into set of scopes
+ whose head is RET (shell_variables). */
+ FREE (vcxt->name);
+ if (vcxt->table)
+ {
+ if (is_special)
+ hash_flush (vcxt->table, push_func_var);
+ else
+ hash_flush (vcxt->table, push_exported_var);
+ hash_dispose (vcxt->table);
+ }
+ free (vcxt);
+
+ sv_ifs ("IFS"); /* XXX here for now */
+}
+
+/* **************************************************************** */
+/* */
+/* Pushing and Popping function contexts */
+/* */
+/* **************************************************************** */
+
+static WORD_LIST **dollar_arg_stack = (WORD_LIST **)NULL;
+static int dollar_arg_stack_slots;
+static int dollar_arg_stack_index;
+
+/* XXX - we might want to consider pushing and popping the `getopts' state
+ when we modify the positional parameters. */
+void
+push_context (name, is_subshell, tempvars)
+ char *name; /* function name */
+ int is_subshell;
+ HASH_TABLE *tempvars;
+{
+ if (is_subshell == 0)
+ push_dollar_vars ();
+ variable_context++;
+ push_var_context (name, VC_FUNCENV, tempvars);
+}
+
+/* Only called when subshell == 0, so we don't need to check, and can
+ unconditionally pop the dollar vars off the stack. */
+void
+pop_context ()
+{
+ pop_dollar_vars ();
+ variable_context--;
+ pop_var_context ();
+
+ sv_ifs ("IFS"); /* XXX here for now */
+}
+
+/* Save the existing positional parameters on a stack. */
+void
+push_dollar_vars ()
+{
+ if (dollar_arg_stack_index + 2 > dollar_arg_stack_slots)
+ {
+ dollar_arg_stack = (WORD_LIST **)
+ xrealloc (dollar_arg_stack, (dollar_arg_stack_slots += 10)
+ * sizeof (WORD_LIST **));
+ }
+ dollar_arg_stack[dollar_arg_stack_index++] = list_rest_of_args ();
+ dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL;
+}
+
+/* Restore the positional parameters from our stack. */
+void
+pop_dollar_vars ()
+{
+ if (!dollar_arg_stack || dollar_arg_stack_index == 0)
+ return;
+
+ remember_args (dollar_arg_stack[--dollar_arg_stack_index], 1);
+ dispose_words (dollar_arg_stack[dollar_arg_stack_index]);
+ dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL;
+ set_dollar_vars_unchanged ();
+}
+
+void
+dispose_saved_dollar_vars ()
+{
+ if (!dollar_arg_stack || dollar_arg_stack_index == 0)
+ return;
+
+ dispose_words (dollar_arg_stack[dollar_arg_stack_index]);
+ dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL;
+}
+
+/* Manipulate the special BASH_ARGV and BASH_ARGC variables. */
+
+void
+push_args (list)
+ WORD_LIST *list;
+{
+#if defined (ARRAY_VARS) && defined (DEBUGGER)
+ SHELL_VAR *bash_argv_v, *bash_argc_v;
+ ARRAY *bash_argv_a, *bash_argc_a;
+ WORD_LIST *l;
+ arrayind_t i;
+ char *t;
+
+ GET_ARRAY_FROM_VAR ("BASH_ARGV", bash_argv_v, bash_argv_a);
+ GET_ARRAY_FROM_VAR ("BASH_ARGC", bash_argc_v, bash_argc_a);
+
+ for (l = list, i = 0; l; l = l->next, i++)
+ array_push (bash_argv_a, l->word->word);
+
+ t = itos (i);
+ array_push (bash_argc_a, t);
+ free (t);
+#endif /* ARRAY_VARS && DEBUGGER */
+}
+
+/* Remove arguments from BASH_ARGV array. Pop top element off BASH_ARGC
+ array and use that value as the count of elements to remove from
+ BASH_ARGV. */
+void
+pop_args ()
+{
+#if defined (ARRAY_VARS) && defined (DEBUGGER)
+ SHELL_VAR *bash_argv_v, *bash_argc_v;
+ ARRAY *bash_argv_a, *bash_argc_a;
+ ARRAY_ELEMENT *ce;
+ intmax_t i;
+
+ GET_ARRAY_FROM_VAR ("BASH_ARGV", bash_argv_v, bash_argv_a);
+ GET_ARRAY_FROM_VAR ("BASH_ARGC", bash_argc_v, bash_argc_a);
+
+ ce = array_shift (bash_argc_a, 1, 0);
+ if (ce == 0 || legal_number (element_value (ce), &i) == 0)
+ i = 0;
+
+ for ( ; i > 0; i--)
+ array_pop (bash_argv_a);
+ array_dispose_element (ce);
+#endif /* ARRAY_VARS && DEBUGGER */
+}
+
+/*************************************************
+ * *
+ * Functions to manage special variables *
+ * *
+ *************************************************/
+
+/* Extern declarations for variables this code has to manage. */
+extern int eof_encountered, eof_encountered_limit, ignoreeof;
+
+#if defined (READLINE)
+extern int hostname_list_initialized;
+#endif
+
+/* An alist of name.function for each special variable. Most of the
+ functions don't do much, and in fact, this would be faster with a
+ switch statement, but by the end of this file, I am sick of switch
+ statements. */
+
+#define SET_INT_VAR(name, intvar) intvar = find_variable (name) != 0
+
+/* This table will be sorted with qsort() the first time it's accessed. */
+struct name_and_function {
+ char *name;
+ sh_sv_func_t *function;
+};
+
+static struct name_and_function special_vars[] = {
+ { "BASH_XTRACEFD", sv_xtracefd },
+
+#if defined (READLINE)
+# if defined (STRICT_POSIX)
+ { "COLUMNS", sv_winsize },
+# endif
+ { "COMP_WORDBREAKS", sv_comp_wordbreaks },
+#endif
+
+ { "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) && defined (PROMPT_STRING_DECODE)
+ { "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 ();
+ }
+}
+
+/* What to do when GLOBIGNORE changes. */
+void
+sv_globignore (name)
+ char *name;
+{
+ if (privileged_mode == 0)
+ setup_glob_ignore (name);
+}
+
+#if defined (READLINE)
+void
+sv_comp_wordbreaks (name)
+ char *name;
+{
+ SHELL_VAR *sv;
+
+ sv = find_variable (name);
+ if (sv == 0)
+ reset_completer_word_break_chars ();
+}
+
+/* What to do just after one of the TERMxxx variables has changed.
+ If we are an interactive shell, then try to reset the terminal
+ information in readline. */
+void
+sv_terminal (name)
+ char *name;
+{
+ if (interactive_shell && no_line_editing == 0)
+ rl_reset_terminal (get_string_value ("TERM"));
+}
+
+void
+sv_hostfile (name)
+ char *name;
+{
+ SHELL_VAR *v;
+
+ v = find_variable (name);
+ if (v == 0)
+ clear_hostname_list ();
+ else
+ hostname_list_initialized = 0;
+}
+
+#if defined (STRICT_POSIX)
+/* In strict posix mode, we allow assignments to LINES and COLUMNS (and values
+ found in the initial environment) to override the terminal size reported by
+ the kernel. */
+void
+sv_winsize (name)
+ char *name;
+{
+ SHELL_VAR *v;
+ intmax_t xd;
+ int d;
+
+ if (posixly_correct == 0 || interactive_shell == 0 || no_line_editing)
+ return;
+
+ v = find_variable (name);
+ if (v == 0 || var_isnull (v))
+ rl_reset_screen_size ();
+ else
+ {
+ if (legal_number (value_cell (v), &xd) == 0)
+ return;
+ winsize_assignment = winsize_assigned = 1;
+ d = xd; /* truncate */
+ if (name[0] == 'L') /* LINES */
+ rl_set_screen_size (d, -1);
+ else /* COLUMNS */
+ rl_set_screen_size (-1, d);
+ winsize_assignment = 0;
+ }
+}
+#endif /* STRICT_POSIX */
+#endif /* READLINE */
+
+/* Update the value of HOME in the export environment so tilde expansion will
+ work on cygwin. */
+#if defined (__CYGWIN__)
+sv_home (name)
+ char *name;
+{
+ array_needs_making = 1;
+ maybe_make_export_env ();
+}
+#endif
+
+#if defined (HISTORY)
+/* What to do after the HISTSIZE or HISTFILESIZE variables change.
+ If there is a value for this HISTSIZE (and it is numeric), then stifle
+ the history. Otherwise, if there is NO value for this variable,
+ unstifle the history. If name is HISTFILESIZE, and its value is
+ numeric, truncate the history file to hold no more than that many
+ lines. */
+void
+sv_histsize (name)
+ char *name;
+{
+ char *temp;
+ intmax_t num;
+ int hmax;
+
+ temp = get_string_value (name);
+
+ if (temp && *temp)
+ {
+ if (legal_number (temp, &num))
+ {
+ hmax = num;
+ if (name[4] == 'S')
+ {
+ stifle_history (hmax);
+ hmax = where_history ();
+ if (history_lines_this_session > hmax)
+ history_lines_this_session = hmax;
+ }
+ else
+ {
+ history_truncate_file (get_string_value ("HISTFILE"), hmax);
+ if (hmax <= history_lines_in_file)
+ history_lines_in_file = hmax;
+ }
+ }
+ }
+ else if (name[4] == 'S')
+ unstifle_history ();
+}
+
+/* What to do after the HISTIGNORE variable changes. */
+void
+sv_histignore (name)
+ char *name;
+{
+ setup_history_ignore (name);
+}
+
+/* What to do after the HISTCONTROL variable changes. */
+void
+sv_history_control (name)
+ char *name;
+{
+ char *temp;
+ char *val;
+ int tptr;
+
+ history_control = 0;
+ temp = get_string_value (name);
+
+ if (temp == 0 || *temp == 0)
+ return;
+
+ tptr = 0;
+ while (val = extract_colon_unit (temp, &tptr))
+ {
+ if (STREQ (val, "ignorespace"))
+ history_control |= HC_IGNSPACE;
+ else if (STREQ (val, "ignoredups"))
+ history_control |= HC_IGNDUPS;
+ else if (STREQ (val, "ignoreboth"))
+ history_control |= HC_IGNBOTH;
+ else if (STREQ (val, "erasedups"))
+ history_control |= HC_ERASEDUPS;
+
+ free (val);
+ }
+}
+
+#if defined (BANG_HISTORY)
+/* Setting/unsetting of the history expansion character. */
+void
+sv_histchars (name)
+ char *name;
+{
+ char *temp;
+
+ temp = get_string_value (name);
+ if (temp)
+ {
+ history_expansion_char = *temp;
+ if (temp[0] && temp[1])
+ {
+ history_subst_char = temp[1];
+ if (temp[2])
+ history_comment_char = temp[2];
+ }
+ }
+ else
+ {
+ history_expansion_char = '!';
+ history_subst_char = '^';
+ history_comment_char = '#';
+ }
+}
+#endif /* BANG_HISTORY */
+
+void
+sv_histtimefmt (name)
+ char *name;
+{
+ SHELL_VAR *v;
+
+ v = find_variable (name);
+ history_write_timestamps = (v != 0);
+}
+#endif /* HISTORY */
+
+#if defined (HAVE_TZSET) && 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, nproc)
+ int *ps;
+ int nproc;
+{
+ SHELL_VAR *v;
+ ARRAY *a;
+ ARRAY_ELEMENT *ae;
+ register int i;
+ char *t, tbuf[INT_STRLEN_BOUND(int) + 1];
+
+ v = find_variable ("PIPESTATUS");
+ if (v == 0)
+ v = make_new_array_variable ("PIPESTATUS");
+ if (array_p (v) == 0)
+ return; /* Do nothing if not an array variable. */
+ a = array_cell (v);
+
+ if (a == 0 || array_num_elements (a) == 0)
+ {
+ for (i = 0; i < nproc; i++) /* was ps[i] != -1, not i < nproc */
+ {
+ t = inttostr (ps[i], tbuf, sizeof (tbuf));
+ array_insert (a, i, t);
+ }
+ return;
+ }
+
+ /* Fast case */
+ if (array_num_elements (a) == nproc && nproc == 1)
+ {
+ ae = element_forw (a->head);
+ free (element_value (ae));
+ ae->value = itos (ps[0]);
+ }
+ else if (array_num_elements (a) <= nproc)
+ {
+ /* modify in array_num_elements members in place, then add */
+ ae = a->head;
+ for (i = 0; i < array_num_elements (a); i++)
+ {
+ ae = element_forw (ae);
+ free (element_value (ae));
+ ae->value = itos (ps[i]);
+ }
+ /* add any more */
+ for ( ; i < nproc; i++)
+ {
+ t = inttostr (ps[i], tbuf, sizeof (tbuf));
+ array_insert (a, i, t);
+ }
+ }
+ else
+ {
+ /* deleting elements. it's faster to rebuild the array. */
+ array_flush (a);
+ for (i = 0; ps[i] != -1; i++)
+ {
+ t = inttostr (ps[i], tbuf, sizeof (tbuf));
+ array_insert (a, i, t);
+ }
+ }
+}
+#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));
+ }
+}