]> git.ipfire.org Git - thirdparty/bash.git/commitdiff
commit bash-20100121 snapshot
authorChet Ramey <chet.ramey@case.edu>
Tue, 13 Dec 2011 02:53:04 +0000 (21:53 -0500)
committerChet Ramey <chet.ramey@case.edu>
Tue, 13 Dec 2011 02:53:04 +0000 (21:53 -0500)
37 files changed:
CWRU/CWRU.chlog
CWRU/CWRU.chlog~
arrayfunc.c
arrayfunc.c~ [new file with mode: 0644]
arrayfunc.h
arrayfunc.h~ [new file with mode: 0644]
autom4te.cache/output.0
autom4te.cache/traces.0
bashline.c
bashline.c~ [new file with mode: 0644]
builtins/setattr.def
builtins/setattr.def~ [new file with mode: 0644]
builtins/ulimit.def
builtins/ulimit.def~ [new file with mode: 0644]
config.h.in
config.h.in~ [new file with mode: 0644]
configure
configure.in
configure.in~ [new file with mode: 0644]
doc/bash.1
doc/bashref.texi
expr.c
expr.c~ [new file with mode: 0644]
jobs.c
jobs.c~ [new file with mode: 0644]
lib/sh/oslib.c
lib/sh/oslib.c~ [new file with mode: 0644]
print_cmd.c
print_cmd.c~ [new file with mode: 0644]
sig.c
sig.c~ [new file with mode: 0644]
subst.c
subst.c~ [new file with mode: 0644]
tests/RUN-ONE-TEST
tests/RUN-ONE-TEST~ [new file with mode: 0755]
variables.c
variables.c~ [new file with mode: 0644]

index 581bab0f28971f750ed50be0154a28fd50d015ca..f015b4dd549e63fece9aa31acd8354bebaa6e21e 100644 (file)
@@ -9372,3 +9372,88 @@ 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>
+       - change array_value_internal to return NULL for variable which has
+         not been set
index bf53bb629cb455f99e82d4b68565770c99b71332..6230712d4d469fe4a1af231449bae238abcbeec8 100644 (file)
@@ -9325,6 +9325,10 @@ doc/bash.1
 
 [bash-4.1 frozen]
 
+                                  12/31
+                                  -----
+[bash-4.1 released]
+
                                 1/5/2010
                                 --------
 doc/bashref.texi
@@ -9344,3 +9348,110 @@ lib/readline/complete.c
 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>
index 218faf3dd5fce1037cb3dbd6efd5a1c829225077..ad5b2bdd7bccd2c543c10af46ee2dfdc50ef0c01 100644 (file)
@@ -44,7 +44,7 @@ static SHELL_VAR *bind_array_var_internal __P((SHELL_VAR *, arrayind_t, char *,
 
 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");
@@ -658,6 +658,7 @@ unbind_array_element (var, sub)
          return -1;
        }
       assoc_remove (assoc_cell (var), akey);
+      free (akey);
     }
   else
     {
@@ -848,9 +849,10 @@ array_variable_part (s, subp, lenp)
    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;
@@ -881,7 +883,7 @@ array_value_internal (s, quoted, allow_all, rtype)
          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);
@@ -929,6 +931,8 @@ index_error:
                }
              return ((char *)NULL);
            }
+         if (indp)
+           *indp = ind;
        }
       else if (assoc_p (var))
        {
@@ -939,12 +943,15 @@ index_error:
            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);
     }
@@ -955,11 +962,12 @@ index_error:
 /* 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.
@@ -967,11 +975,12 @@ array_value (s, quoted, rtype)
    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 *
@@ -990,6 +999,9 @@ array_keys (s, quoted)
   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))
diff --git a/arrayfunc.c~ b/arrayfunc.c~
new file mode 100644 (file)
index 0000000..c5c926e
--- /dev/null
@@ -0,0 +1,1026 @@
+/* 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 */
index c12fc9932f5e9b3af99910babd74ce173a899b17..9add11897890af54ca5f1121a0427b9e47701cb3 100644 (file)
@@ -51,8 +51,8 @@ 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 *));
+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));
 
diff --git a/arrayfunc.h~ b/arrayfunc.h~
new file mode 100644 (file)
index 0000000..85ebb48
--- /dev/null
@@ -0,0 +1,64 @@
+/* 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_ */
index a14841a6a1c65df0c0cfd6ad25409e315428035e..8038cfbeff2687597a2f087c1262d2c2cde4d506 100644 (file)
@@ -1,7 +1,7 @@
 @%:@! /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>.
 @%:@ 
@@ -597,8 +597,8 @@ SHELL=${CONFIG_SHELL-/bin/sh}
 # 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"
@@ -1411,7 +1411,7 @@ if test "$ac_init_help" = "long"; then
   # 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]...
 
@@ -1476,7 +1476,7 @@ fi
 
 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
 
@@ -1652,7 +1652,7 @@ fi
 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,
@@ -1666,7 +1666,7 @@ cat >config.log <<_ACEOF
 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 $@
@@ -2080,7 +2080,7 @@ ac_config_headers="$ac_config_headers config.h"
 
 
 BASHVERS=4.1
-RELSTATUS=release
+RELSTATUS=maint
 
 case "$RELSTATUS" in
 alp*|bet*|dev*|rc*|maint*)     DEBUG='-DDEBUG' MALLOC_DEBUG='-DMALLOC_DEBUG' ;;
@@ -11502,12 +11502,13 @@ done
 
 
 
+
 
 
 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
@@ -31170,7 +31171,7 @@ exec 6>&1
 # 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
@@ -31233,7 +31234,7 @@ Report bugs to <bug-autoconf@gnu.org>."
 _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'`\\"
 
index 497b57e2a459d263a84c83ecd6601cb131b4371d..4663f479e6b0b334287969f1a98842987d124a18 100644 (file)
@@ -1,4 +1,4 @@
-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'])
@@ -1190,6 +1190,8 @@ m4trace:configure.in:659: -1- AH_OUTPUT([HAVE_REGEX_H], [/* Define to 1 if you h
 #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. */
index 7658771ebb18a0e4fbab743cb244c777c46ee873..345c5755d62972d1f9a6228dc5745f85bad21746 100644 (file)
@@ -3421,7 +3421,10 @@ static int
 putx(c)
      int c;
 {
-  putc (c, rl_outstream);
+  int x;
+
+  x = putc (c, rl_outstream);
+  return (x);
 }
   
 static int
diff --git a/bashline.c~ b/bashline.c~
new file mode 100644 (file)
index 0000000..4d0a1cb
--- /dev/null
@@ -0,0 +1,3663 @@
+/* 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 */
index 8b4cdf765fc0704ed1f88d404cb495780bec081d..f549d05f93540347eb6285dabc7a035d81417edf 100644 (file)
@@ -433,11 +433,11 @@ show_var_attributes (var, pattr, nodefs)
     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);
     }
diff --git a/builtins/setattr.def~ b/builtins/setattr.def~
new file mode 100644 (file)
index 0000000..29a835e
--- /dev/null
@@ -0,0 +1,514 @@
+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 */
+}
index 7c1e2568b917ace009d5b5d67afa746e8e5592f1..97fcb9ce0403795ed4b495d64f0027382ae54449 100644 (file)
@@ -118,6 +118,10 @@ extern int errno;
 #  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)
diff --git a/builtins/ulimit.def~ b/builtins/ulimit.def~
new file mode 100644 (file)
index 0000000..1667306
--- /dev/null
@@ -0,0 +1,776 @@
+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 */
index 07352dd4892f8b19a7b71bc2a038b52c4b83d219..12167c3ac83fee01da76e6e79323bd1c701c0d1e 100644 (file)
 /* 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
 
diff --git a/config.h.in~ b/config.h.in~
new file mode 100644 (file)
index 0000000..07352dd
--- /dev/null
@@ -0,0 +1,1110 @@
+/* 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_ */
index 875bbbf1a83c86d70a66e329d257ea83f3a419e2..aa4b96a3d07e3964dee348013edb63157c26c3b4 100755 (executable)
--- a/configure
+++ b/configure
@@ -1,7 +1,7 @@
 #! /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>.
 #
@@ -597,8 +597,8 @@ SHELL=${CONFIG_SHELL-/bin/sh}
 # 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"
@@ -1411,7 +1411,7 @@ if test "$ac_init_help" = "long"; then
   # 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]...
 
@@ -1476,7 +1476,7 @@ fi
 
 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
 
@@ -1652,7 +1652,7 @@ fi
 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,
@@ -1666,7 +1666,7 @@ cat >config.log <<_ACEOF
 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 $@
@@ -2080,7 +2080,7 @@ ac_config_headers="$ac_config_headers config.h"
 
 
 BASHVERS=4.1
-RELSTATUS=release
+RELSTATUS=maint
 
 case "$RELSTATUS" in
 alp*|bet*|dev*|rc*|maint*)     DEBUG='-DDEBUG' MALLOC_DEBUG='-DMALLOC_DEBUG' ;;
@@ -11502,12 +11502,13 @@ done
 
 
 
+
 
 
 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
@@ -31170,7 +31171,7 @@ exec 6>&1
 # 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
@@ -31233,7 +31234,7 @@ Report bugs to <bug-autoconf@gnu.org>."
 _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'`\\"
 
index 843bd700c3576b4866ad2d0bdb56b6828f686d56..21bf7bb1986df8d5bab0982bc58caa4bac805939 100644 (file)
@@ -21,10 +21,10 @@ dnl Process this file with autoconf to produce a configure script.
 #   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])
 
@@ -659,7 +659,7 @@ 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)
+                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)
diff --git a/configure.in~ b/configure.in~
new file mode 100644 (file)
index 0000000..76ef244
--- /dev/null
@@ -0,0 +1,1156 @@
+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
+])
index 70085bae66a30284ddc4af480b51eaacb398463b..bec857637413704401924959c440abbbabc5881a 100644 (file)
@@ -9429,7 +9429,8 @@ The maximum amount of cpu time in seconds
 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
index da7378c688256ff44e1f4c57061b3a340fa9dd27..b9e6f73942b922ffc98d9eacccb131f5939dee74 100644 (file)
@@ -3939,7 +3939,8 @@ The maximum amount of cpu time in seconds.
 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.
diff --git a/expr.c b/expr.c
index 446e6784224c6a769c3f1466235beab86b298b47..ef6d04b73db8d703bebe621371eb685c4545ecc0 100644 (file)
--- a/expr.c
+++ b/expr.c
    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 */
@@ -148,10 +170,17 @@ 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));
+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 *));
 
@@ -159,6 +188,7 @@ 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 *));
 
@@ -179,23 +209,6 @@ static intmax_t    exppower __P((void));
 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. */
@@ -217,6 +230,7 @@ extern const char * const bash_badsub_errmsg;
     (X)->tokval = tokval; \
     (X)->tokstr = tokstr; \
     (X)->noeval = noeval; \
+    (X)->lval = curlval; \
   } while (0)
 
 #define RESTORETOK(X) \
@@ -228,6 +242,7 @@ extern const char * const bash_badsub_errmsg;
     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
@@ -298,6 +313,32 @@ expr_bind_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 = 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
@@ -364,12 +405,14 @@ subexpr (expr)
     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 ();
 
@@ -406,6 +449,7 @@ expassign ()
 {
   register intmax_t value;
   char *lhs, *rhs;
+  arrayind_t lind;
 
   value = expcond ();
   if (curtok == EQ || curtok == OP_ASSIGN)
@@ -425,6 +469,8 @@ expassign ()
        }
 
       lhs = savestring (tokstr);
+      /* save ind in case rhs is string var and evaluation overwrites it */
+      lind = curlval.ind;
       readtok ();
       value = expassign ();
 
@@ -476,7 +522,12 @@ 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);
@@ -828,7 +879,12 @@ exp0 ()
       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;
 
@@ -873,12 +929,18 @@ exp0 ()
              /* 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 */
            }
@@ -899,14 +961,44 @@ exp0 ()
   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)
@@ -941,18 +1033,27 @@ expr_streval (tok, e)
        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);
 }
 
@@ -1024,6 +1125,7 @@ readtok ()
   register char *cp, *xp;
   register unsigned char c, c1;
   register int e;
+  struct lvalue lval;
 
   /* Skip leading whitespace. */
   cp = tp;
@@ -1075,6 +1177,7 @@ readtok ()
       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;
@@ -1090,7 +1193,10 @@ readtok ()
       /* 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;
 
diff --git a/expr.c~ b/expr.c~
new file mode 100644 (file)
index 0000000..3ed6803
--- /dev/null
+++ b/expr.c~
@@ -0,0 +1,1446 @@
+/* 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 */
diff --git a/jobs.c b/jobs.c
index df13ad9e90f723dfd133510b3d5caa9006e2d692..1cfbb14bce8643adf11ba921b48b292c0739082e 100644 (file)
--- a/jobs.c
+++ b/jobs.c
 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 */
diff --git a/jobs.c~ b/jobs.c~
new file mode 100644 (file)
index 0000000..df13ad9
--- /dev/null
+++ b/jobs.c~
@@ -0,0 +1,4240 @@
+/* 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 */
index d47f9dc80bbc4b9c80239baa11120d5c7cfcde8b..d8eba6e6f8b9508d2277bdbded64bebf5a220cd9 100644 (file)
 #include <filecntl.h>
 #include <bashansi.h>
 
+#if !defined (HAVE_KILLPG)
+#  include <signal.h>
+#endif
+
 #include <stdio.h>
 #include <errno.h>
 #include <chartypes.h>
@@ -209,7 +213,8 @@ gethostname (name, namelen)
 #  else /* !HAVE_UNAME */
 int
 gethostname (name, namelen)
-     int name, namelen;
+     char *name;
+     int namelen;
 {
   strncpy (name, "unknown", namelen);
   name[namelen] = '\0';
diff --git a/lib/sh/oslib.c~ b/lib/sh/oslib.c~
new file mode 100644 (file)
index 0000000..e554c5c
--- /dev/null
@@ -0,0 +1,300 @@
+/* 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);
+}
index 9a41dcbd481ad2050e21d27b0d9b7ea8a23b6ec5..bf405c68fffc47d7a5669f89237e1b80d4835278 100644 (file)
@@ -1120,6 +1120,10 @@ print_redirection (redirect)
        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;
@@ -1128,6 +1132,7 @@ print_redirection (redirect)
          free (x);
        }
       else
+#endif
        cprintf ("<<< %s", redirect->redirectee.filename->word);
       break;
 
diff --git a/print_cmd.c~ b/print_cmd.c~
new file mode 100644 (file)
index 0000000..841739b
--- /dev/null
@@ -0,0 +1,1534 @@
+/* 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 */
diff --git a/sig.c b/sig.c
index a217b8947cd9300411aec74fcbbe4928f0b0090e..dd9bd3dddecf0676ec1c4e1be23869692363ec3e 100644 (file)
--- a/sig.c
+++ b/sig.c
@@ -60,6 +60,8 @@ extern int executing_list;
 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;
 
diff --git a/sig.c~ b/sig.c~
new file mode 100644 (file)
index 0000000..a217b89
--- /dev/null
+++ b/sig.c~
@@ -0,0 +1,664 @@
+/* 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 */
diff --git a/subst.c b/subst.c
index 81a3256c24585c377ef51a2aa4f037f203172bd2..679151612df1703fecb7adb7dfdddc725b8b2f6c 100644 (file)
--- a/subst.c
+++ b/subst.c
@@ -5243,6 +5243,7 @@ array_length_reference (s)
   char *akey;
   char *t, c;
   ARRAY *array;
+  HASH_TABLE *h;
   SHELL_VAR *var;
 
   var = array_variable_part (s, &t, &len);
@@ -5266,15 +5267,16 @@ array_length_reference (s)
      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))
@@ -5432,7 +5434,7 @@ parameter_brace_expand_word (name, var_is_special, quoted, pflags)
 #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)
@@ -5967,7 +5969,7 @@ get_var_and_type (varname, value, quoted, varp, valp)
          else
            {
              vtype = VT_ARRAYMEMBER;
-             *valp = array_value (varname, 1, (int *)NULL);
+             *valp = array_value (varname, 1, (int *)NULL, (arrayind_t *)NULL);
            }
          *varp = v;
        }
@@ -5984,7 +5986,7 @@ get_var_and_type (varname, value, quoted, varp, valp)
        {
          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)))
diff --git a/subst.c~ b/subst.c~
new file mode 100644 (file)
index 0000000..58e61d9
--- /dev/null
+++ b/subst.c~
@@ -0,0 +1,9072 @@
+/* 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, &quoted_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);
+}
index 3efcf32d68e9722024b6ca9d67f9e81b2aa5ac04..72ec06a2c1fd8dde92acea5e8ac773e35f1d061b 100755 (executable)
@@ -1,4 +1,4 @@
-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
 
diff --git a/tests/RUN-ONE-TEST~ b/tests/RUN-ONE-TEST~
new file mode 100755 (executable)
index 0000000..3efcf32
--- /dev/null
@@ -0,0 +1,9 @@
+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 "$@"
index bb399b1020b5b50691946578afe3340fecfcf66d..d1da8f67b87948cce36cfc419df7f3e5694112da 100644 (file)
@@ -1309,7 +1309,6 @@ get_random (var)
 
   rv = get_random_number ();
   last_random_value = rv;
-fprintf(stderr, "get_random: rv = %d\n", rv);
   p = itos (rv);
 
   FREE (value_cell (var));
diff --git a/variables.c~ b/variables.c~
new file mode 100644 (file)
index 0000000..bb399b1
--- /dev/null
@@ -0,0 +1,4686 @@
+/* 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));
+    }
+}