]> git.ipfire.org Git - thirdparty/bash.git/commitdiff
commit bash-20111104 snapshot
authorChet Ramey <chet.ramey@case.edu>
Mon, 9 Jan 2012 13:31:41 +0000 (08:31 -0500)
committerChet Ramey <chet.ramey@case.edu>
Mon, 9 Jan 2012 13:31:41 +0000 (08:31 -0500)
63 files changed:
CWRU/CWRU.chlog
CWRU/CWRU.chlog~
Makefile.in
alias.c
alias.c~ [new file with mode: 0644]
array.c
array.c~ [new file with mode: 0644]
arrayfunc.c
arrayfunc.c~ [new file with mode: 0644]
assoc.c
assoc.c~ [new file with mode: 0644]
bashline.c
bashline.c~
builtins/cd.def
builtins/cd.def~
builtins/complete.def
builtins/complete.def~ [new file with mode: 0644]
builtins/hash.def
builtins/hash.def~ [new file with mode: 0644]
builtins/help.def
builtins/help.def~ [new file with mode: 0644]
builtins/mkbuiltins.c
builtins/mkbuiltins.c~ [new file with mode: 0644]
builtins/read.def
builtins/read.def~ [new file with mode: 0644]
builtins/type.def
builtins/type.def~ [new file with mode: 0644]
eval.c
eval.c~ [new file with mode: 0644]
execute_cmd.c
execute_cmd.c~
expr.c
expr.c~
findcmd.c
findcmd.c~ [new file with mode: 0644]
general.c
general.c~ [new file with mode: 0644]
jobs.c
jobs.c~
lib/glob/glob.c
lib/glob/glob.c~ [new file with mode: 0644]
lib/glob/xmbsrtowcs.c
lib/glob/xmbsrtowcs.c~ [new file with mode: 0644]
lib/sh/fmtulong.c
lib/sh/fmtulong.c~ [new file with mode: 0644]
lib/sh/zmapfd.c
lib/sh/zmapfd.c~ [new file with mode: 0644]
make_cmd.c
make_cmd.c~
pcomplete.c
pcomplete.c~
print_cmd.c
print_cmd.c~
redir.c
redir.c~ [new file with mode: 0644]
stringlib.c
stringlib.c~ [new file with mode: 0644]
subst.c
subst.c~ [new file with mode: 0644]
trap.c
trap.c~ [new file with mode: 0644]
variables.c
variables.c~

index 1ee709d6b9fee467aab5228be16df628f4c59a61..f5d6ceeda6d2bc79e9a36376b1635d032c705b5d 100644 (file)
@@ -12407,3 +12407,145 @@ make_cmd.c
          when breaking the string between the double parens into three
          separate components instead of a simple character loop.  Fixes
          bug reported by Dan Douglas <ormaaj@gmail.com>
+
+                                  11/2
+                                  ----
+Makefile.in
+       - make libbuiltins.a depend on builtext.h to serialize its creation
+         and avoid conflict between multiple invocations of mkbuiltins.
+         Fix from Mike Frysinger <vapier@gentoo.org>
+
+                                  11/5
+                                  ----
+findcmd.c
+       - user_command_matches: if stat(".", ...) returns -1, set st_dev
+         and st_ino fields in dotinfo to 0 to avoid same_file matches
+       - find_user_command_in_path: check stat(2) return the same way
+
+lib/glob/glob.c
+       - glob_vector: don't call strlen(pat) without checking pat == 0
+       - glob_dir_to_array: make sure to free `result' and all allocated
+         members before returning error due to malloc failure
+       - glob_vector: make sure to free `nextname' and `npat' on errors
+         (mostly when setting lose = 1)
+       - glob_vector: if flags & GX_MATCHDIRS but not GX_ALLDIRS, make
+         sure we free `subdir'
+       - glob_filename: when expanding ** (GX_ALLDIRS), make sure we
+         free temp_results (return value from glob_vector)
+
+lib/glob/xmbsrtowcs.c
+       - xdupmbstowcs: fix call to realloc to use sizeof (char *) instead
+         of sizeof (char **) when assigning idxtmp
+
+execute_cmd.c
+       - print_index_and_element: return 0 right away if L == 0
+       - is_dirname: fix memory leak by freeing `temp'
+       - time_command: don't try to deref NULL `command' when assigning
+         to `posix_time'
+       - shell_execve: null-terminate `sample' after READ_SAMPLE_BUF so it's
+         terminated for functions that expect that
+
+builtins/read.def
+       - read_builtin: don't call bind_read_variable with a potentially-null
+         string
+
+pcomplete.c
+       - gen_command_matches: don't call dispose_word_desc with a NULL arg
+       - gen_compspec_completions: fix memory leak by freeing `ret' before
+         calling gen_action_completions (tcs, ...).  happens when
+         performing directory completion as default and no completions
+         have been generated
+       - gen_progcomp_completions: make sure to set foundp to 0 whenever
+         returning NULL
+       - it_init_aliases: fix memory leak by freeing alias_list before
+         returning
+
+bashline.c
+       - command_word_completion_function: don't call restore_tilde with a
+         NULL directory_part argument
+       - bash_directory_expansion: bugfix: don't throw away results of
+         rl_directory_rewrite_hook if it's set and returns non-zero
+       - bind_keyseq_to_unix_command: free `kseq' before returning error
+
+arrayfunc.c
+       - assign_array_element_internal: make sure `akey' is freed if non-null
+         before returning error
+       - assign_compound_array_list: free `akey' before returning error
+       - array_value_internal: free `akey' before returning error
+       - unbind_array_element: free `akey' before returning error
+
+subst.c
+       - array_length_reference: free `akey' before returning error in case
+         of expand_assignment_string_to_string error
+       - array_length_reference: free `akey' after call to assoc_reference
+       - skip_to_delim: if skipping process and command substitution, free
+         return value from extract_process_subst
+       - parameter_brace_substring: free `val' (vtype == VT_VARIABLE) before
+         returning if verify_substring_values fails
+       - parameter_brace_expand: remove two duplicate lines that allocate
+         ret in parameter_brace_substring case
+       - parameter_brace_expand: convert `free (name); name = xmalloc (...)'
+         to use `xrealloc (name, ...)'
+       - parameter_brace_expand: free `name' before returning when handling
+         ${!PREFIX*} expansion
+       - split_at_delims: fix memory leak by freeing `d2' before returning
+
+redir.c
+       - redirection_error: free `filename' if the redirection operator is
+         REDIR_VARASSIGN by assigning allocname
+
+eval.c
+       - send_pwd_to_eterm: fix memory leak by freeing value returned by
+         get_working_directory()
+
+builtins/cd.def
+       - change_to_directory: fix memory leak by freeing return value from
+         resetpwd()
+       - cd_builtin: fix memory leak by freeing value returned by dirspell()
+       - cd_builtin: fix memory leak by freeing `directory' if appropriate
+         before overwriting with return value from resetpwd()
+
+builtins/type.def
+       - describe_command: free `full_path' before overwriting it with return
+         value from sh_makepath
+
+builtins/complete.def
+       - compgen_builtin: fix memory leak by calling strlist_dispose (sl)
+         before overwriting sl with return value from completions_to_stringlist
+
+builtins/hash.def
+       - list_hashed_filename_targets: fix memory leak by freeing `target'
+
+make_cmd.c
+       - make_arith_for_command: free `init', `test', and `step' before
+         returning error on parse error
+
+jobs.c
+       - initialize_job_control: don't call move_to_high_fd if shell_tty == -1
+
+general.c
+       - check_dev_tty: don't call close with an fd < 0
+       - legal_number: deal with NULL `string' argument, return invalid
+
+lib/sh/fmtulong.c
+       - fmtulong: if the `base' argument is invalid, make sure we index
+         buf by `len-1' at maximum
+
+print_cmd.c
+       - print_deferred_heredocs: don't try to dereference a NULL `cstring'
+       - cprintf: make sure to call va_end (args)
+
+variables.c
+       - push_dollar_vars: fix call to xrealloc to use sizeof (WORD_LIST *)
+         instead of sizeof (WORD_LIST **)
+
+lib/sh/zmapfd.c
+       - zmapfd: if read returns error, free result and return -1 immediately
+         instead of trying to reallocate it
+
+                                  11/6
+                                  ----
+execute_cmd.c
+       - cpl_reap: rewrote to avoid using pointer after freeing it; now builds
+         new coproc list on the fly while traversing the old one and sets the
+         right values for coproc_list when done
index c284216affb120d32bf2aeb82ea242ac08c1ab09..6e2d1d05a72444a5a7fef8da34d17df234b65c15 100644 (file)
@@ -12399,3 +12399,146 @@ lib/readline/histexpand.c
          we assume that the double quote is followed by a character in
          history_no_expand_chars.  tcsh and csh seem to do this.  This
          answers a persistent complaint about history expansion
+
+                                  10/29
+                                  -----
+make_cmd.c
+       - make_arith_for_command: use skip_to_delim to find the next `;'
+         when breaking the string between the double parens into three
+         separate components instead of a simple character loop.  Fixes
+         bug reported by Dan Douglas <ormaaj@gmail.com>
+
+                                  11/2
+                                  ----
+Makefile.in
+       - make libbuiltins.a depend on builtext.h to serialize its creation
+         and avoid conflict between multiple invocations of mkbuiltins.
+         Fix from Mike Frysinger <vapier@gentoo.org>
+
+                                  11/5
+                                  ----
+findcmd.c
+       - user_command_matches: if stat(".", ...) returns -1, set st_dev
+         and st_ino fields in dotinfo to 0 to avoid same_file matches
+       - find_user_command_in_path: check stat(2) return the same way
+
+lib/glob/glob.c
+       - glob_vector: don't call strlen(pat) without checking pat == 0
+       - glob_dir_to_array: make sure to free `result' and all allocated
+         members before returning error due to malloc failure
+       - glob_vector: make sure to free `nextname' and `npat' on errors
+         (mostly when setting lose = 1)
+       - glob_vector: if flags & GX_MATCHDIRS but not GX_ALLDIRS, make
+         sure we free `subdir'
+       - glob_filename: when expanding ** (GX_ALLDIRS), make sure we
+         free temp_results (return value from glob_vector)
+
+lib/glob/xmbsrtowcs.c
+       - xdupmbstowcs: fix call to realloc to use sizeof (char *) instead
+         of sizeof (char **) when assigning idxtmp
+
+execute_cmd.c
+       - print_index_and_element: return 0 right away if L == 0
+       - is_dirname: fix memory leak by freeing `temp'
+       - time_command: don't try to deref NULL `command' when assigning
+         to `posix_time'
+       - shell_execve: null-terminate `sample' after READ_SAMPLE_BUF so it's
+         terminated for functions that expect that
+
+builtins/read.def
+       - read_builtin: don't call bind_read_variable with a potentially-null
+         string
+
+pcomplete.c
+       - gen_command_matches: don't call dispose_word_desc with a NULL arg
+       - gen_compspec_completions: fix memory leak by freeing `ret' before
+         calling gen_action_completions (tcs, ...).  happens when
+         performing directory completion as default and no completions
+         have been generated
+       - gen_progcomp_completions: make sure to set foundp to 0 whenever
+         returning NULL
+       - it_init_aliases: fix memory leak by freeing alias_list before
+         returning
+
+bashline.c
+       - command_word_completion_function: don't call restore_tilde with a
+         NULL directory_part argument
+       - bash_directory_expansion: bugfix: don't throw away results of
+         rl_directory_rewrite_hook if it's set and returns non-zero
+       - bind_keyseq_to_unix_command: free `kseq' before returning error
+
+arrayfunc.c
+       - assign_array_element_internal: make sure `akey' is freed if non-null
+         before returning error
+       - assign_compound_array_list: free `akey' before returning error
+       - array_value_internal: free `akey' before returning error
+       - unbind_array_element: free `akey' before returning error
+
+subst.c
+       - array_length_reference: free `akey' before returning error in case
+         of expand_assignment_string_to_string error
+       - array_length_reference: free `akey' after call to assoc_reference
+       - skip_to_delim: if skipping process and command substitution, free
+         return value from extract_process_subst
+       - parameter_brace_substring: free `val' (vtype == VT_VARIABLE) before
+         returning if verify_substring_values fails
+       - parameter_brace_expand: remove two duplicate lines that allocate
+         ret in parameter_brace_substring case
+       - parameter_brace_expand: convert `free (name); name = xmalloc (...)'
+         to use `xrealloc (name, ...)'
+       - parameter_brace_expand: free `name' before returning when handling
+         ${!PREFIX*} expansion
+       - split_at_delims: fix memory leak by freeing `d2' before returning
+
+redir.c
+       - redirection_error: free `filename' if the redirection operator is
+         REDIR_VARASSIGN by assigning allocname
+
+eval.c
+       - send_pwd_to_eterm: fix memory leak by freeing value returned by
+         get_working_directory()
+
+builtins/cd.def
+       - change_to_directory: fix memory leak by freeing return value from
+         resetpwd()
+       - cd_builtin: fix memory leak by freeing value returned by dirspell()
+       - cd_builtin: fix memory leak by freeing `directory' if appropriate
+         before overwriting with return value from resetpwd()
+
+builtins/type.def
+       - describe_command: free `full_path' before overwriting it with return
+         value from sh_makepath
+
+builtins/complete.def
+       - compgen_builtin: fix memory leak by calling strlist_dispose (sl)
+         before overwriting sl with return value from completions_to_stringlist
+
+builtins/hash.def
+       - list_hashed_filename_targets: fix memory leak by freeing `target'
+
+make_cmd.c
+       - make_arith_for_command: free `init', `test', and `step' before
+         returning error on parse error
+
+jobs.c
+       - initialize_job_control: don't call move_to_high_fd if shell_tty == -1
+
+general.c
+       - check_dev_tty: don't call close with an fd < 0
+       - legal_number: deal with NULL `string' argument, return invalid
+
+lib/sh/fmtulong.c
+       - fmtulong: if the `base' argument is invalid, make sure we index
+         buf by `len-1' at maximum
+
+print_cmd.c
+       - print_deferred_heredocs: don't try to dereference a NULL `cstring'
+       - cprintf: make sure to call va_end (args)
+
+variables.c
+       - push_dollar_vars: fix call to xrealloc to use sizeof (WORD_LIST *)
+         instead of sizeof (WORD_LIST **)
+
+lib/sh/zmapfd.c
+       - zmapfd: if read returns error, free result and return -1 immediately
+         instead of trying to reallocate it
index b5a2193c293eb4280a37fa69a55baac2f92597cd..2274fc0544b076aebd90a79634c9bbeb22993a5b 100644 (file)
@@ -672,7 +672,7 @@ syntax.c:   mksyntax${EXEEXT} $(srcdir)/syntax.h
        $(RM) $@
        ./mksyntax$(EXEEXT) -o $@
 
-$(BUILTINS_LIBRARY): $(BUILTIN_DEFS) $(BUILTIN_C_SRC) config.h ${BASHINCDIR}/memalloc.h version.h
+$(BUILTINS_LIBRARY): $(BUILTIN_DEFS) $(BUILTIN_C_SRC) config.h ${BASHINCDIR}/memalloc.h $(DEFDIR)/builtext.h version.h
        @(cd $(DEFDIR) && $(MAKE) $(MFLAGS) DEBUG=${DEBUG} libbuiltins.a ) || exit 1
 
 # these require special rules to circumvent make builtin rules
diff --git a/alias.c b/alias.c
index 6c953760f1036e2fb4820b4bcfb53bce37407a34..7839229659e9c3159c2a5c2e3b823e7757d25ba1 100644 (file)
--- a/alias.c
+++ b/alias.c
@@ -110,7 +110,7 @@ add_alias (name, value)
   alias_t *temp;
   int n;
 
-  if (!aliases)
+  if (aliases == 0)
     {
       initialize_aliases ();
       temp = (alias_t *)NULL;
diff --git a/alias.c~ b/alias.c~
new file mode 100644 (file)
index 0000000..6c95376
--- /dev/null
+++ b/alias.c~
@@ -0,0 +1,580 @@
+/* alias.c -- Not a full alias, but just the kind that we use in the
+   shell.  Csh style alias is somewhere else (`over there, in a box'). */
+
+/* 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 (ALIAS)
+
+#if defined (HAVE_UNISTD_H)
+#  ifdef _MINIX
+#    include <sys/types.h>
+#  endif
+#  include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include "chartypes.h"
+#include "bashansi.h"
+#include "command.h"
+#include "general.h"
+#include "externs.h"
+#include "alias.h"
+
+#if defined (PROGRAMMABLE_COMPLETION)
+#  include "pcomplete.h"
+#endif
+
+#define ALIAS_HASH_BUCKETS     16      /* must be power of two */
+
+typedef int sh_alias_map_func_t __P((alias_t *));
+
+static void free_alias_data __P((PTR_T));
+static alias_t **map_over_aliases __P((sh_alias_map_func_t *));
+static void sort_aliases __P((alias_t **));
+static int qsort_alias_compare __P((alias_t **, alias_t **));
+
+#if defined (READLINE)
+static int skipquotes __P((char *, int));
+static int skipws __P((char *, int));
+static int rd_token __P((char *, int));
+#endif
+
+/* Non-zero means expand all words on the line.  Otherwise, expand
+   after first expansion if the expansion ends in a space. */
+int alias_expand_all = 0;
+
+/* The list of aliases that we have. */
+HASH_TABLE *aliases = (HASH_TABLE *)NULL;
+
+void
+initialize_aliases ()
+{
+  if (aliases == 0)
+    aliases = hash_create (ALIAS_HASH_BUCKETS);
+}
+
+/* Scan the list of aliases looking for one with NAME.  Return NULL
+   if the alias doesn't exist, else a pointer to the alias_t. */
+alias_t *
+find_alias (name)
+     char *name;
+{
+  BUCKET_CONTENTS *al;
+
+  if (aliases == 0)
+    return ((alias_t *)NULL);
+
+  al = hash_search (name, aliases, 0);
+  return (al ? (alias_t *)al->data : (alias_t *)NULL);
+}
+
+/* Return the value of the alias for NAME, or NULL if there is none. */
+char *
+get_alias_value (name)
+     char *name;
+{
+  alias_t *alias;
+
+  if (aliases == 0)
+    return ((char *)NULL);
+
+  alias = find_alias (name);
+  return (alias ? alias->value : (char *)NULL);
+}
+
+/* Make a new alias from NAME and VALUE.  If NAME can be found,
+   then replace its value. */
+void
+add_alias (name, value)
+     char *name, *value;
+{
+  BUCKET_CONTENTS *elt;
+  alias_t *temp;
+  int n;
+
+  if (!aliases)
+    {
+      initialize_aliases ();
+      temp = (alias_t *)NULL;
+    }
+  else
+    temp = find_alias (name);
+
+  if (temp)
+    {
+      free (temp->value);
+      temp->value = savestring (value);
+      temp->flags &= ~AL_EXPANDNEXT;
+      n = value[strlen (value) - 1];
+      if (n == ' ' || n == '\t')
+       temp->flags |= AL_EXPANDNEXT;
+    }
+  else
+    {
+      temp = (alias_t *)xmalloc (sizeof (alias_t));
+      temp->name = savestring (name);
+      temp->value = savestring (value);
+      temp->flags = 0;
+
+      n = value[strlen (value) - 1];
+      if (n == ' ' || n == '\t')
+       temp->flags |= AL_EXPANDNEXT;
+
+      elt = hash_insert (savestring (name), aliases, HASH_NOSRCH);
+      elt->data = temp;
+#if defined (PROGRAMMABLE_COMPLETION)
+      set_itemlist_dirty (&it_aliases);
+#endif
+    }
+}
+
+/* Delete a single alias structure. */
+static void
+free_alias_data (data)
+     PTR_T data;
+{
+  register alias_t *a;
+
+  a = (alias_t *)data;
+  free (a->value);
+  free (a->name);
+  free (data);
+}
+
+/* Remove the alias with name NAME from the alias table.  Returns
+   the number of aliases left in the table, or -1 if the alias didn't
+   exist. */
+int
+remove_alias (name)
+     char *name;
+{
+  BUCKET_CONTENTS *elt;
+
+  if (aliases == 0)
+    return (-1);
+
+  elt = hash_remove (name, aliases, 0);
+  if (elt)
+    {
+      free_alias_data (elt->data);
+      free (elt->key);         /* alias name */
+      free (elt);              /* XXX */
+#if defined (PROGRAMMABLE_COMPLETION)
+      set_itemlist_dirty (&it_aliases);
+#endif
+      return (aliases->nentries);
+    }
+  return (-1);
+}
+
+/* Delete all aliases. */
+void
+delete_all_aliases ()
+{
+  if (aliases == 0)
+    return;
+
+  hash_flush (aliases, free_alias_data);
+  hash_dispose (aliases);
+  aliases = (HASH_TABLE *)NULL;
+#if defined (PROGRAMMABLE_COMPLETION)
+  set_itemlist_dirty (&it_aliases);
+#endif
+}
+
+/* Return an array of aliases that satisfy the conditions tested by FUNCTION.
+   If FUNCTION is NULL, return all aliases. */
+static alias_t **
+map_over_aliases (function)
+     sh_alias_map_func_t *function;
+{
+  register int i;
+  register BUCKET_CONTENTS *tlist;
+  alias_t *alias, **list;
+  int list_index;
+
+  i = HASH_ENTRIES (aliases);
+  if (i == 0)
+    return ((alias_t **)NULL);
+
+  list = (alias_t **)xmalloc ((i + 1) * sizeof (alias_t *));
+  for (i = list_index = 0; i < aliases->nbuckets; i++)
+    {
+      for (tlist = hash_items (i, aliases); tlist; tlist = tlist->next)
+       {
+         alias = (alias_t *)tlist->data;
+
+         if (!function || (*function) (alias))
+           {
+             list[list_index++] = alias;
+             list[list_index] = (alias_t *)NULL;
+           }
+       }
+    }
+  return (list);
+}
+
+static void
+sort_aliases (array)
+     alias_t **array;
+{
+  qsort (array, strvec_len ((char **)array), sizeof (alias_t *), (QSFUNC *)qsort_alias_compare);
+}
+
+static int
+qsort_alias_compare (as1, as2)
+     alias_t **as1, **as2;
+{
+  int result;
+
+  if ((result = (*as1)->name[0] - (*as2)->name[0]) == 0)
+    result = strcmp ((*as1)->name, (*as2)->name);
+
+  return (result);
+}
+
+/* Return a sorted list of all defined aliases */
+alias_t **
+all_aliases ()
+{
+  alias_t **list;
+
+  if (aliases == 0 || HASH_ENTRIES (aliases) == 0)
+    return ((alias_t **)NULL);
+
+  list = map_over_aliases ((sh_alias_map_func_t *)NULL);
+  if (list)
+    sort_aliases (list);
+  return (list);
+}
+
+char *
+alias_expand_word (s)
+     char *s;
+{
+  alias_t *r;
+
+  r = find_alias (s);
+  return (r ? savestring (r->value) : (char *)NULL);
+}
+
+/* Readline support functions -- expand all aliases in a line. */
+
+#if defined (READLINE)
+
+/* Return non-zero if CHARACTER is a member of the class of characters
+   that are self-delimiting in the shell (this really means that these
+   characters delimit tokens). */
+#define self_delimiting(character) (member ((character), " \t\n\r;|&()"))
+
+/* Return non-zero if CHARACTER is a member of the class of characters
+   that delimit commands in the shell. */
+#define command_separator(character) (member ((character), "\r\n;|&("))
+
+/* If this is 1, we are checking the next token read for alias expansion
+   because it is the first word in a command. */
+static int command_word;
+
+/* This is for skipping quoted strings in alias expansions. */
+#define quote_char(c)  (((c) == '\'') || ((c) == '"'))
+
+/* Consume a quoted string from STRING, starting at string[START] (so
+   string[START] is the opening quote character), and return the index
+   of the closing quote character matching the opening quote character.
+   This handles single matching pairs of unquoted quotes; it could afford
+   to be a little smarter... This skips words between balanced pairs of
+   quotes, words where the first character is quoted with a `\', and other
+   backslash-escaped characters. */
+
+static int
+skipquotes (string, start)
+     char *string;
+     int start;
+{
+  register int i;
+  int delimiter = string[start];
+
+  /* i starts at START + 1 because string[START] is the opening quote
+     character. */
+  for (i = start + 1 ; string[i] ; i++)
+    {
+      if (string[i] == '\\')
+       {
+         i++;          /* skip backslash-quoted quote characters, too */
+         if (string[i] == 0)
+           break;
+         continue;
+       }
+
+      if (string[i] == delimiter)
+       return i;
+    }
+  return (i);
+}
+
+/* Skip the white space and any quoted characters in STRING, starting at
+   START.  Return the new index into STRING, after zero or more characters
+   have been skipped. */
+static int
+skipws (string, start)
+     char *string;
+     int start;
+{
+  register int i;
+  int pass_next, backslash_quoted_word;
+  unsigned char peekc;
+
+  /* skip quoted strings, in ' or ", and words in which a character is quoted
+     with a `\'. */
+  i = backslash_quoted_word = pass_next = 0;
+
+  /* Skip leading whitespace (or separator characters), and quoted words.
+     But save it in the output.  */
+
+  for (i = start; string[i]; i++)
+    {
+      if (pass_next)
+       {
+         pass_next = 0;
+         continue;
+       }
+
+      if (whitespace (string[i]))
+       {
+         backslash_quoted_word = 0; /* we are no longer in a backslash-quoted word */
+         continue;
+       }
+
+      if (string[i] == '\\')
+       {
+         peekc = string[i+1];
+         if (peekc == 0)
+           break;
+         if (ISLETTER (peekc))
+           backslash_quoted_word++;    /* this is a backslash-quoted word */
+         else
+           pass_next++;
+         continue;
+       }
+
+      /* This only handles single pairs of non-escaped quotes.  This
+        overloads backslash_quoted_word to also mean that a word like
+        ""f is being scanned, so that the quotes will inhibit any expansion
+        of the word. */
+      if (quote_char(string[i]))
+       {
+         i = skipquotes (string, i);
+         /* This could be a line that contains a single quote character,
+            in which case skipquotes () terminates with string[i] == '\0'
+            (the end of the string).  Check for that here. */
+         if (string[i] == '\0')
+           break;
+
+         peekc = string[i + 1];
+         if (ISLETTER (peekc))
+           backslash_quoted_word++;
+         continue;
+       }
+
+      /* If we're in the middle of some kind of quoted word, let it
+        pass through. */
+      if (backslash_quoted_word)
+       continue;
+
+      /* If this character is a shell command separator, then set a hint for
+        alias_expand that the next token is the first word in a command. */
+
+      if (command_separator (string[i]))
+       {
+         command_word++;
+         continue;
+       }
+      break;
+    }
+  return (i);
+}
+
+/* Characters that may appear in a token.  Basically, anything except white
+   space and a token separator. */
+#define token_char(c)  (!((whitespace (string[i]) || self_delimiting (string[i]))))
+
+/* Read from START in STRING until the next separator character, and return
+   the index of that separator.  Skip backslash-quoted characters.  Call
+   skipquotes () for quoted strings in the middle or at the end of tokens,
+   so all characters show up (e.g. foo'' and foo""bar) */
+static int
+rd_token (string, start)
+     char *string;
+     int start;
+{
+  register int i;
+
+  /* From here to next separator character is a token. */
+  for (i = start; string[i] && token_char (string[i]); i++)
+    {
+      if (string[i] == '\\')
+       {
+         i++;  /* skip backslash-escaped character */
+         if (string[i] == 0)
+           break;
+         continue;
+       }
+
+      /* If this character is a quote character, we want to call skipquotes
+        to get the whole quoted portion as part of this word.  That word
+        will not generally match an alias, even if te unquoted word would
+        have.  The presence of the quotes in the token serves then to
+        inhibit expansion. */
+      if (quote_char (string[i]))
+       {
+         i = skipquotes (string, i);
+         /* This could be a line that contains a single quote character,
+            in which case skipquotes () terminates with string[i] == '\0'
+            (the end of the string).  Check for that here. */
+         if (string[i] == '\0')
+           break;
+
+         /* Now string[i] is the matching quote character, and the
+            quoted portion of the token has been scanned. */
+         continue;
+       }
+    }
+  return (i);
+}
+
+/* Return a new line, with any aliases substituted. */
+char *
+alias_expand (string)
+     char *string;
+{
+  register int i, j, start;
+  char *line, *token;
+  int line_len, tl, real_start, expand_next, expand_this_token;
+  alias_t *alias;
+
+  line_len = strlen (string) + 1;
+  line = (char *)xmalloc (line_len);
+  token = (char *)xmalloc (line_len);
+
+  line[0] = i = 0;
+  expand_next = 0;
+  command_word = 1; /* initialized to expand the first word on the line */
+
+  /* Each time through the loop we find the next word in line.  If it
+     has an alias, substitute the alias value.  If the value ends in ` ',
+     then try again with the next word.  Else, if there is no value, or if
+     the value does not end in space, we are done. */
+
+  for (;;)
+    {
+
+      token[0] = 0;
+      start = i;
+
+      /* Skip white space and quoted characters */
+      i = skipws (string, start);
+
+      if (start == i && string[i] == '\0')
+       {
+         free (token);
+         return (line);
+       }
+
+      /* copy the just-skipped characters into the output string,
+        expanding it if there is not enough room. */
+      j = strlen (line);
+      tl = i - start;  /* number of characters just skipped */
+      RESIZE_MALLOCED_BUFFER (line, j, (tl + 1), line_len, (tl + 50));
+      strncpy (line + j, string + start, tl);
+      line[j + tl] = '\0';
+
+      real_start = i;
+
+      command_word = command_word || (command_separator (string[i]));
+      expand_this_token = (command_word || expand_next);
+      expand_next = 0;
+
+      /* Read the next token, and copy it into TOKEN. */
+      start = i;
+      i = rd_token (string, start);
+
+      tl = i - start;  /* token length */
+
+      /* If tl == 0, but we're not at the end of the string, then we have a
+        single-character token, probably a delimiter */
+      if (tl == 0 && string[i] != '\0')
+       {
+         tl = 1;
+         i++;          /* move past it */
+       }
+
+      strncpy (token, string + start, tl);
+      token [tl] = '\0';
+
+      /* If there is a backslash-escaped character quoted in TOKEN,
+        then we don't do alias expansion.  This should check for all
+        other quoting characters, too. */
+      if (mbschr (token, '\\'))
+       expand_this_token = 0;
+
+      /* If we should be expanding here, if we are expanding all words, or if
+        we are in a location in the string where an expansion is supposed to
+        take place, see if this word has a substitution.  If it does, then do
+        the expansion.  Note that we defer the alias value lookup until we
+        are sure we are expanding this token. */
+
+      if ((token[0]) &&
+         (expand_this_token || alias_expand_all) &&
+         (alias = find_alias (token)))
+       {
+         char *v;
+         int vlen, llen;
+
+         v = alias->value;
+         vlen = strlen (v);
+         llen = strlen (line);
+
+         /* +3 because we possibly add one more character below. */
+         RESIZE_MALLOCED_BUFFER (line, llen, (vlen + 3), line_len, (vlen + 50));
+
+         strcpy (line + llen, v);
+
+         if ((expand_this_token && vlen && whitespace (v[vlen - 1])) ||
+             alias_expand_all)
+           expand_next = 1;
+       }
+      else
+       {
+         int llen, tlen;
+
+         llen = strlen (line);
+         tlen = i - real_start; /* tlen == strlen(token) */
+
+         RESIZE_MALLOCED_BUFFER (line, llen, (tlen + 1), line_len, (llen + tlen + 50));
+
+         strncpy (line + llen, string + real_start, tlen);
+         line[llen + tlen] = '\0';
+       }
+      command_word = 0;
+    }
+}
+#endif /* READLINE */
+#endif /* ALIAS */
diff --git a/array.c b/array.c
index 27fe17083ff0db306f0efb8bbf56d941ac05fb72..a6cca015a3f5b68ac1827819fe886ad694f8df8f 100644 (file)
--- a/array.c
+++ b/array.c
@@ -816,7 +816,7 @@ int quoted;
                                                rsize, rsize);
                        strcpy(result + rlen, t);
                        rlen += reg;
-                       if (quoted && t)
+                       if (quoted)
                                free(t);
                        /*
                         * Add a separator only after non-null elements.
diff --git a/array.c~ b/array.c~
new file mode 100644 (file)
index 0000000..27fe170
--- /dev/null
+++ b/array.c~
@@ -0,0 +1,1130 @@
+/*
+ * array.c - functions to create, destroy, access, and manipulate arrays
+ *          of strings.
+ *
+ * Arrays are sparse doubly-linked lists.  An element's index is stored
+ * with it.
+ *
+ * Chet Ramey
+ * chet@ins.cwru.edu
+ */
+
+/* Copyright (C) 1997-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)
+#  ifdef _MINIX
+#    include <sys/types.h>
+#  endif
+#  include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include "bashansi.h"
+
+#include "shell.h"
+#include "array.h"
+#include "builtins/common.h"
+
+#define ADD_BEFORE(ae, new) \
+       do { \
+               ae->prev->next = new; \
+               new->prev = ae->prev; \
+               ae->prev = new; \
+               new->next = ae; \
+       } while(0)
+
+static char *array_to_string_internal __P((ARRAY_ELEMENT *, ARRAY_ELEMENT *, char *, int));
+
+static ARRAY *lastarray = 0;
+static ARRAY_ELEMENT *lastref = 0;
+
+#define IS_LASTREF(a)  ((a) == lastarray)
+
+#define INVALIDATE_LASTREF(a) \
+do { \
+       if ((a) == lastarray) { \
+               lastarray = 0; \
+               lastref = 0; \
+       } \
+} while (0)
+
+#define SET_LASTREF(a, e) \
+do { \
+       lastarray = (a); \
+       lastref = (e); \
+} while (0)
+
+#define UNSET_LASTREF() \
+do { \
+       lastarray = 0; \
+       lastref = 0; \
+} while (0)
+
+ARRAY *
+array_create()
+{
+       ARRAY   *r;
+       ARRAY_ELEMENT   *head;
+
+       r =(ARRAY *)xmalloc(sizeof(ARRAY));
+       r->type = array_indexed;
+       r->max_index = -1;
+       r->num_elements = 0;
+       head = array_create_element(-1, (char *)NULL);  /* dummy head */
+       head->prev = head->next = head;
+       r->head = head;
+       return(r);
+}
+
+void
+array_flush (a)
+ARRAY  *a;
+{
+       register ARRAY_ELEMENT *r, *r1;
+
+       if (a == 0)
+               return;
+       for (r = element_forw(a->head); r != a->head; ) {
+               r1 = element_forw(r);
+               array_dispose_element(r);
+               r = r1;
+       }
+       a->head->next = a->head->prev = a->head;
+       a->max_index = -1;
+       a->num_elements = 0;
+       INVALIDATE_LASTREF(a);
+}
+
+void
+array_dispose(a)
+ARRAY  *a;
+{
+       if (a == 0)
+               return;
+       array_flush (a);
+       array_dispose_element(a->head);
+       free(a);
+}
+
+ARRAY *
+array_copy(a)
+ARRAY  *a;
+{
+       ARRAY   *a1;
+       ARRAY_ELEMENT   *ae, *new;
+
+       if (a == 0)
+               return((ARRAY *) NULL);
+       a1 = array_create();
+       a1->type = a->type;
+       a1->max_index = a->max_index;
+       a1->num_elements = a->num_elements;
+       for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) {
+               new = array_create_element(element_index(ae), element_value(ae));
+               ADD_BEFORE(a1->head, new);
+       }
+       return(a1);
+}
+
+/*
+ * Make and return a new array composed of the elements in array A from
+ * S to E, inclusive.
+ */
+ARRAY *
+array_slice(array, s, e)
+ARRAY          *array;
+ARRAY_ELEMENT  *s, *e;
+{
+       ARRAY   *a;
+       ARRAY_ELEMENT *p, *n;
+       int     i;
+       arrayind_t mi;
+
+       a = array_create ();
+       a->type = array->type;
+
+       for (mi = 0, p = s, i = 0; p != e; p = element_forw(p), i++) {
+               n = array_create_element (element_index(p), element_value(p));
+               ADD_BEFORE(a->head, n);
+               mi = element_index(n);
+       }
+       a->num_elements = i;
+       a->max_index = mi;
+       return a;
+}
+
+/*
+ * Walk the array, calling FUNC once for each element, with the array
+ * element as the argument.
+ */
+void
+array_walk(a, func, udata)
+ARRAY  *a;
+sh_ae_map_func_t *func;
+void   *udata;
+{
+       register ARRAY_ELEMENT *ae;
+
+       if (a == 0 || array_empty(a))
+               return;
+       for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae))
+               if ((*func)(ae, udata) < 0)
+                       return;
+}
+
+/*
+ * Shift the array A N elements to the left.  Delete the first N elements
+ * and subtract N from the indices of the remaining elements.  If FLAGS
+ * does not include AS_DISPOSE, this returns a singly-linked null-terminated
+ * list of elements so the caller can dispose of the chain.  If FLAGS
+ * includes AS_DISPOSE, this function disposes of the shifted-out elements
+ * and returns NULL.
+ */
+ARRAY_ELEMENT *
+array_shift(a, n, flags)
+ARRAY  *a;
+int    n, flags;
+{
+       register ARRAY_ELEMENT *ae, *ret;
+       register int i;
+
+       if (a == 0 || array_empty(a) || n <= 0)
+               return ((ARRAY_ELEMENT *)NULL);
+
+       INVALIDATE_LASTREF(a);
+       for (i = 0, ret = ae = element_forw(a->head); ae != a->head && i < n; ae = element_forw(ae), i++)
+               ;
+       if (ae == a->head) {
+               /* Easy case; shifting out all of the elements */
+               if (flags & AS_DISPOSE) {
+                       array_flush (a);
+                       return ((ARRAY_ELEMENT *)NULL);
+               }
+               for (ae = ret; element_forw(ae) != a->head; ae = element_forw(ae))
+                       ;
+               element_forw(ae) = (ARRAY_ELEMENT *)NULL;
+               a->head->next = a->head->prev = a->head;
+               a->max_index = -1;
+               a->num_elements = 0;
+               return ret;
+       }
+       /*
+        * ae now points to the list of elements we want to retain.
+        * ret points to the list we want to either destroy or return.
+        */
+       ae->prev->next = (ARRAY_ELEMENT *)NULL;         /* null-terminate RET */
+
+       a->head->next = ae;             /* slice RET out of the array */
+       ae->prev = a->head;
+
+       for ( ; ae != a->head; ae = element_forw(ae))
+               element_index(ae) -= n; /* renumber retained indices */
+
+       a->num_elements -= n;           /* modify bookkeeping information */
+       a->max_index = element_index(a->head->prev);
+
+       if (flags & AS_DISPOSE) {
+               for (ae = ret; ae; ) {
+                       ret = element_forw(ae);
+                       array_dispose_element(ae);
+                       ae = ret;
+               }
+               return ((ARRAY_ELEMENT *)NULL);
+       }
+
+       return ret;
+}
+
+/*
+ * Shift array A right N indices.  If S is non-null, it becomes the value of
+ * the new element 0.  Returns the number of elements in the array after the
+ * shift.
+ */
+int
+array_rshift (a, n, s)
+ARRAY  *a;
+int    n;
+char   *s;
+{
+       register ARRAY_ELEMENT  *ae, *new;
+
+       if (a == 0 || (array_empty(a) && s == 0))
+               return 0;
+       else if (n <= 0)
+               return (a->num_elements);
+
+       ae = element_forw(a->head);
+       if (s) {
+               new = array_create_element(0, s);
+               ADD_BEFORE(ae, new);
+               a->num_elements++;
+               if (array_num_elements(a) == 1) {       /* array was empty */
+                       a->max_index = 0;
+                       return 1;
+               }
+       }
+
+       /*
+        * Renumber all elements in the array except the one we just added.
+        */
+       for ( ; ae != a->head; ae = element_forw(ae))
+               element_index(ae) += n;
+
+       a->max_index = element_index(a->head->prev);
+
+       INVALIDATE_LASTREF(a);
+       return (a->num_elements);
+}
+
+ARRAY_ELEMENT *
+array_unshift_element(a)
+ARRAY  *a;
+{
+       return (array_shift (a, 1, 0));
+}
+
+int
+array_shift_element(a, v)
+ARRAY  *a;
+char   *v;
+{
+       return (array_rshift (a, 1, v));
+}
+
+ARRAY *
+array_quote(array)
+ARRAY  *array;
+{
+       ARRAY_ELEMENT   *a;
+       char    *t;
+
+       if (array == 0 || array_head(array) == 0 || array_empty(array))
+               return (ARRAY *)NULL;
+       for (a = element_forw(array->head); a != array->head; a = element_forw(a)) {
+               t = quote_string (a->value);
+               FREE(a->value);
+               a->value = t;
+       }
+       return array;
+}
+
+ARRAY *
+array_quote_escapes(array)
+ARRAY  *array;
+{
+       ARRAY_ELEMENT   *a;
+       char    *t;
+
+       if (array == 0 || array_head(array) == 0 || array_empty(array))
+               return (ARRAY *)NULL;
+       for (a = element_forw(array->head); a != array->head; a = element_forw(a)) {
+               t = quote_escapes (a->value);
+               FREE(a->value);
+               a->value = t;
+       }
+       return array;
+}
+
+ARRAY *
+array_dequote(array)
+ARRAY  *array;
+{
+       ARRAY_ELEMENT   *a;
+       char    *t;
+
+       if (array == 0 || array_head(array) == 0 || array_empty(array))
+               return (ARRAY *)NULL;
+       for (a = element_forw(array->head); a != array->head; a = element_forw(a)) {
+               t = dequote_string (a->value);
+               FREE(a->value);
+               a->value = t;
+       }
+       return array;
+}
+
+ARRAY *
+array_dequote_escapes(array)
+ARRAY  *array;
+{
+       ARRAY_ELEMENT   *a;
+       char    *t;
+
+       if (array == 0 || array_head(array) == 0 || array_empty(array))
+               return (ARRAY *)NULL;
+       for (a = element_forw(array->head); a != array->head; a = element_forw(a)) {
+               t = dequote_escapes (a->value);
+               FREE(a->value);
+               a->value = t;
+       }
+       return array;
+}
+
+ARRAY *
+array_remove_quoted_nulls(array)
+ARRAY  *array;
+{
+       ARRAY_ELEMENT   *a;
+       char    *t;
+
+       if (array == 0 || array_head(array) == 0 || array_empty(array))
+               return (ARRAY *)NULL;
+       for (a = element_forw(array->head); a != array->head; a = element_forw(a))
+               a->value = remove_quoted_nulls (a->value);
+       return array;
+}
+
+/*
+ * Return a string whose elements are the members of array A beginning at
+ * index START and spanning NELEM members.  Null elements are counted.
+ * Since arrays are sparse, unset array elements are not counted.
+ */
+char *
+array_subrange (a, start, nelem, starsub, quoted)
+ARRAY  *a;
+arrayind_t     start, nelem;
+int    starsub, quoted;
+{
+       ARRAY           *a2;
+       ARRAY_ELEMENT   *h, *p;
+       arrayind_t      i;
+       char            *ifs, *sifs, *t;
+       int             slen;
+
+       p = a ? array_head (a) : 0;
+       if (p == 0 || array_empty (a) || start > array_max_index(a))
+               return ((char *)NULL);
+
+       /*
+        * Find element with index START.  If START corresponds to an unset
+        * element (arrays can be sparse), use the first element whose index
+        * is >= START.  If START is < 0, we count START indices back from
+        * the end of A (not elements, even with sparse arrays -- START is an
+        * index).
+        */
+       for (p = element_forw(p); p != array_head(a) && start > element_index(p); p = element_forw(p))
+               ;
+
+       if (p == a->head)
+               return ((char *)NULL);
+
+       /* Starting at P, take NELEM elements, inclusive. */
+       for (i = 0, h = p; p != a->head && i < nelem; i++, p = element_forw(p))
+               ;
+
+       a2 = array_slice(a, h, p);
+
+       if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))
+               array_quote(a2);
+       else
+               array_quote_escapes(a2);
+
+       if (starsub && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) {
+               /* ${array[*]} */
+               array_remove_quoted_nulls (a2);
+               sifs = ifs_firstchar ((int *)NULL);
+               t = array_to_string (a2, sifs, 0);
+               free (sifs);
+       } else if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) {
+               /* ${array[@]} */
+               sifs = ifs_firstchar (&slen);
+               ifs = getifs ();
+               if (ifs == 0 || *ifs == 0) {
+                       if (slen < 2)
+                               sifs = xrealloc(sifs, 2);
+                       sifs[0] = ' ';
+                       sifs[1] = '\0';
+               }
+               t = array_to_string (a2, sifs, 0);
+               free (sifs);
+       } else
+               t = array_to_string (a2, " ", 0);
+       array_dispose(a2);
+
+       return t;
+}
+
+char *
+array_patsub (a, pat, rep, mflags)
+ARRAY  *a;
+char   *pat, *rep;
+int    mflags;
+{
+       ARRAY           *a2;
+       ARRAY_ELEMENT   *e;
+       char    *t, *sifs, *ifs;
+       int     slen;
+
+       if (a == 0 || array_head(a) == 0 || array_empty(a))
+               return ((char *)NULL);
+
+       a2 = array_copy(a);
+       for (e = element_forw(a2->head); e != a2->head; e = element_forw(e)) {
+               t = pat_subst(element_value(e), pat, rep, mflags);
+               FREE(element_value(e));
+               e->value = t;
+       }
+
+       if (mflags & MATCH_QUOTED)
+               array_quote(a2);
+       else
+               array_quote_escapes(a2);
+
+       if (mflags & MATCH_STARSUB) {
+               array_remove_quoted_nulls (a2);
+               sifs = ifs_firstchar((int *)NULL);
+               t = array_to_string (a2, sifs, 0);
+               free(sifs);
+       } else if (mflags & MATCH_QUOTED) {
+               /* ${array[@]} */
+               sifs = ifs_firstchar (&slen);
+               ifs = getifs ();
+               if (ifs == 0 || *ifs == 0) {
+                       if (slen < 2)
+                               sifs = xrealloc (sifs, 2);
+                       sifs[0] = ' ';
+                       sifs[1] = '\0';
+               }
+               t = array_to_string (a2, sifs, 0);
+               free(sifs);
+       } else
+               t = array_to_string (a2, " ", 0);
+       array_dispose (a2);
+
+       return t;
+}
+
+char *
+array_modcase (a, pat, modop, mflags)
+ARRAY  *a;
+char   *pat;
+int    modop;
+int    mflags;
+{
+       ARRAY           *a2;
+       ARRAY_ELEMENT   *e;
+       char    *t, *sifs, *ifs;
+       int     slen;
+
+       if (a == 0 || array_head(a) == 0 || array_empty(a))
+               return ((char *)NULL);
+
+       a2 = array_copy(a);
+       for (e = element_forw(a2->head); e != a2->head; e = element_forw(e)) {
+               t = sh_modcase(element_value(e), pat, modop);
+               FREE(element_value(e));
+               e->value = t;
+       }
+
+       if (mflags & MATCH_QUOTED)
+               array_quote(a2);
+       else
+               array_quote_escapes(a2);
+
+       if (mflags & MATCH_STARSUB) {
+               array_remove_quoted_nulls (a2);
+               sifs = ifs_firstchar((int *)NULL);
+               t = array_to_string (a2, sifs, 0);
+               free(sifs);
+       } else if (mflags & MATCH_QUOTED) {
+               /* ${array[@]} */
+               sifs = ifs_firstchar (&slen);
+               ifs = getifs ();
+               if (ifs == 0 || *ifs == 0) {
+                       if (slen < 2)
+                               sifs = xrealloc (sifs, 2);
+                       sifs[0] = ' ';
+                       sifs[1] = '\0';
+               }
+               t = array_to_string (a2, sifs, 0);
+               free(sifs);
+       } else
+               t = array_to_string (a2, " ", 0);
+       array_dispose (a2);
+
+       return t;
+}
+/*
+ * Allocate and return a new array element with index INDEX and value
+ * VALUE.
+ */
+ARRAY_ELEMENT *
+array_create_element(indx, value)
+arrayind_t     indx;
+char   *value;
+{
+       ARRAY_ELEMENT *r;
+
+       r = (ARRAY_ELEMENT *)xmalloc(sizeof(ARRAY_ELEMENT));
+       r->ind = indx;
+       r->value = value ? savestring(value) : (char *)NULL;
+       r->next = r->prev = (ARRAY_ELEMENT *) NULL;
+       return(r);
+}
+
+#ifdef INCLUDE_UNUSED
+ARRAY_ELEMENT *
+array_copy_element(ae)
+ARRAY_ELEMENT  *ae;
+{
+       return(ae ? array_create_element(element_index(ae), element_value(ae))
+                 : (ARRAY_ELEMENT *) NULL);
+}
+#endif
+
+void
+array_dispose_element(ae)
+ARRAY_ELEMENT  *ae;
+{
+       if (ae) {
+               FREE(ae->value);
+               free(ae);
+       }
+}
+
+/*
+ * Add a new element with index I and value V to array A (a[i] = v).
+ */
+int
+array_insert(a, i, v)
+ARRAY  *a;
+arrayind_t     i;
+char   *v;
+{
+       register ARRAY_ELEMENT *new, *ae;
+
+       if (a == 0)
+               return(-1);
+       new = array_create_element(i, v);
+       if (i > array_max_index(a)) {
+               /*
+                * Hook onto the end.  This also works for an empty array.
+                * Fast path for the common case of allocating arrays
+                * sequentially.
+                */
+               ADD_BEFORE(a->head, new);
+               a->max_index = i;
+               a->num_elements++;
+               SET_LASTREF(a, new);
+               return(0);
+       }
+       /*
+        * Otherwise we search for the spot to insert it.
+        */
+       for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) {
+               if (element_index(ae) == i) {
+                       /*
+                        * Replacing an existing element.
+                        */
+                       array_dispose_element(new);
+                       free(element_value(ae));
+                       ae->value = v ? savestring(v) : (char *)NULL;
+                       SET_LASTREF(a, ae);
+                       return(0);
+               } else if (element_index(ae) > i) {
+                       ADD_BEFORE(ae, new);
+                       a->num_elements++;
+                       SET_LASTREF(a, new);
+                       return(0);
+               }
+       }
+       INVALIDATE_LASTREF(a);
+       return (-1);            /* problem */
+}
+
+/*
+ * Delete the element with index I from array A and return it so the
+ * caller can dispose of it.
+ */
+ARRAY_ELEMENT *
+array_remove(a, i)
+ARRAY  *a;
+arrayind_t     i;
+{
+       register ARRAY_ELEMENT *ae;
+
+       if (a == 0 || array_empty(a))
+               return((ARRAY_ELEMENT *) NULL);
+       for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae))
+               if (element_index(ae) == i) {
+                       ae->next->prev = ae->prev;
+                       ae->prev->next = ae->next;
+                       a->num_elements--;
+                       if (i == array_max_index(a))
+                               a->max_index = element_index(ae->prev);
+                       INVALIDATE_LASTREF(a);
+                       return(ae);
+               }
+       return((ARRAY_ELEMENT *) NULL);
+}
+
+/*
+ * Return the value of a[i].
+ */
+char *
+array_reference(a, i)
+ARRAY  *a;
+arrayind_t     i;
+{
+       register ARRAY_ELEMENT *ae;
+
+       if (a == 0 || array_empty(a))
+               return((char *) NULL);
+       if (i > array_max_index(a))
+               return((char *)NULL);
+       /* Keep roving pointer into array to optimize sequential access */
+       if (lastref && IS_LASTREF(a))
+               ae = (i >= element_index(lastref)) ? lastref : element_forw(a->head);
+       else
+               ae = element_forw(a->head);
+       for ( ; ae != a->head; ae = element_forw(ae))
+               if (element_index(ae) == i) {
+                       SET_LASTREF(a, ae);
+                       return(element_value(ae));
+               }
+       UNSET_LASTREF();
+       return((char *) NULL);
+}
+
+/* Convenience routines for the shell to translate to and from the form used
+   by the rest of the code. */
+
+WORD_LIST *
+array_to_word_list(a)
+ARRAY  *a;
+{
+       WORD_LIST       *list;
+       ARRAY_ELEMENT   *ae;
+
+       if (a == 0 || array_empty(a))
+               return((WORD_LIST *)NULL);
+       list = (WORD_LIST *)NULL;
+       for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae))
+               list = make_word_list (make_bare_word(element_value(ae)), list);
+       return (REVERSE_LIST(list, WORD_LIST *));
+}
+
+ARRAY *
+array_from_word_list (list)
+WORD_LIST      *list;
+{
+       ARRAY   *a;
+
+       if (list == 0)
+               return((ARRAY *)NULL);
+       a = array_create();
+       return (array_assign_list (a, list));
+}
+
+WORD_LIST *
+array_keys_to_word_list(a)
+ARRAY  *a;
+{
+       WORD_LIST       *list;
+       ARRAY_ELEMENT   *ae;
+       char            *t;
+
+       if (a == 0 || array_empty(a))
+               return((WORD_LIST *)NULL);
+       list = (WORD_LIST *)NULL;
+       for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) {
+               t = itos(element_index(ae));
+               list = make_word_list (make_bare_word(t), list);
+               free(t);
+       }
+       return (REVERSE_LIST(list, WORD_LIST *));
+}
+
+ARRAY *
+array_assign_list (array, list)
+ARRAY  *array;
+WORD_LIST      *list;
+{
+       register WORD_LIST *l;
+       register arrayind_t i;
+
+       for (l = list, i = 0; l; l = l->next, i++)
+               array_insert(array, i, l->word->word);
+       return array;
+}
+
+char **
+array_to_argv (a)
+ARRAY  *a;
+{
+       char            **ret, *t;
+       int             i;
+       ARRAY_ELEMENT   *ae;
+
+       if (a == 0 || array_empty(a))
+               return ((char **)NULL);
+       ret = strvec_create (array_num_elements (a) + 1);
+       i = 0;
+       for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) {
+               t = element_value (ae);
+               ret[i++] = t ? savestring (t) : (char *)NULL;
+       }
+       ret[i] = (char *)NULL;
+       return (ret);
+}
+       
+/*
+ * Return a string that is the concatenation of the elements in A from START
+ * to END, separated by SEP.
+ */
+static char *
+array_to_string_internal (start, end, sep, quoted)
+ARRAY_ELEMENT  *start, *end;
+char   *sep;
+int    quoted;
+{
+       char    *result, *t;
+       ARRAY_ELEMENT *ae;
+       int     slen, rsize, rlen, reg;
+
+       if (start == end)       /* XXX - should not happen */
+               return ((char *)NULL);
+
+       slen = strlen(sep);
+       result = NULL;
+       for (rsize = rlen = 0, ae = start; ae != end; ae = element_forw(ae)) {
+               if (rsize == 0)
+                       result = (char *)xmalloc (rsize = 64);
+               if (element_value(ae)) {
+                       t = quoted ? quote_string(element_value(ae)) : element_value(ae);
+                       reg = strlen(t);
+                       RESIZE_MALLOCED_BUFFER (result, rlen, (reg + slen + 2),
+                                               rsize, rsize);
+                       strcpy(result + rlen, t);
+                       rlen += reg;
+                       if (quoted && t)
+                               free(t);
+                       /*
+                        * Add a separator only after non-null elements.
+                        */
+                       if (element_forw(ae) != end) {
+                               strcpy(result + rlen, sep);
+                               rlen += slen;
+                       }
+               }
+       }
+       if (result)
+         result[rlen] = '\0';  /* XXX */
+       return(result);
+}
+
+char *
+array_to_assign (a, quoted)
+ARRAY  *a;
+int    quoted;
+{
+       char    *result, *valstr, *is;
+       char    indstr[INT_STRLEN_BOUND(intmax_t) + 1];
+       ARRAY_ELEMENT *ae;
+       int     rsize, rlen, elen;
+
+       if (a == 0 || array_empty (a))
+               return((char *)NULL);
+
+       result = (char *)xmalloc (rsize = 128);
+       result[0] = '(';
+       rlen = 1;
+
+       for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) {
+               is = inttostr (element_index(ae), indstr, sizeof(indstr));
+               valstr = element_value (ae) ? sh_double_quote (element_value(ae))
+                                           : (char *)NULL;
+               elen = STRLEN (is) + 8 + STRLEN (valstr);
+               RESIZE_MALLOCED_BUFFER (result, rlen, (elen + 1), rsize, rsize);
+
+               result[rlen++] = '[';
+               strcpy (result + rlen, is);
+               rlen += STRLEN (is);
+               result[rlen++] = ']';
+               result[rlen++] = '=';
+               if (valstr) {
+                       strcpy (result + rlen, valstr);
+                       rlen += STRLEN (valstr);
+               }
+
+               if (element_forw(ae) != a->head)
+                 result[rlen++] = ' ';
+
+               FREE (valstr);
+       }
+       RESIZE_MALLOCED_BUFFER (result, rlen, 1, rsize, 8);
+       result[rlen++] = ')';
+       result[rlen] = '\0';
+       if (quoted) {
+               /* This is not as efficient as it could be... */
+               valstr = sh_single_quote (result);
+               free (result);
+               result = valstr;
+       }
+       return(result);
+}
+
+char *
+array_to_string (a, sep, quoted)
+ARRAY  *a;
+char   *sep;
+int    quoted;
+{
+       if (a == 0)
+               return((char *)NULL);
+       if (array_empty(a))
+               return(savestring(""));
+       return (array_to_string_internal (element_forw(a->head), a->head, sep, quoted));
+}
+
+#if defined (INCLUDE_UNUSED) || defined (TEST_ARRAY)
+/*
+ * Return an array consisting of elements in S, separated by SEP
+ */
+ARRAY *
+array_from_string(s, sep)
+char   *s, *sep;
+{
+       ARRAY   *a;
+       WORD_LIST *w;
+
+       if (s == 0)
+               return((ARRAY *)NULL);
+       w = list_string (s, sep, 0);
+       if (w == 0)
+               return((ARRAY *)NULL);
+       a = array_from_word_list (w);
+       return (a);
+}
+#endif
+
+#if defined (TEST_ARRAY)
+/*
+ * To make a running version, compile -DTEST_ARRAY and link with:
+ *     xmalloc.o syntax.o lib/malloc/libmalloc.a lib/sh/libsh.a
+ */
+int interrupt_immediately = 0;
+
+int
+signal_is_trapped(s)
+int    s;
+{
+       return 0;
+}
+
+void
+fatal_error(const char *s, ...)
+{
+       fprintf(stderr, "array_test: fatal memory error\n");
+       abort();
+}
+
+void
+programming_error(const char *s, ...)
+{
+       fprintf(stderr, "array_test: fatal programming error\n");
+       abort();
+}
+
+WORD_DESC *
+make_bare_word (s)
+const char     *s;
+{
+       WORD_DESC *w;
+
+       w = (WORD_DESC *)xmalloc(sizeof(WORD_DESC));
+       w->word = s ? savestring(s) : savestring ("");
+       w->flags = 0;
+       return w;
+}
+
+WORD_LIST *
+make_word_list(x, l)
+WORD_DESC      *x;
+WORD_LIST      *l;
+{
+       WORD_LIST *w;
+
+       w = (WORD_LIST *)xmalloc(sizeof(WORD_LIST));
+       w->word = x;
+       w->next = l;
+       return w;
+}
+
+WORD_LIST *
+list_string(s, t, i)
+char   *s, *t;
+int    i;
+{
+       char    *r, *a;
+       WORD_LIST       *wl;
+
+       if (s == 0)
+               return (WORD_LIST *)NULL;
+       r = savestring(s);
+       wl = (WORD_LIST *)NULL;
+       a = strtok(r, t);
+       while (a) {
+               wl = make_word_list (make_bare_word(a), wl);
+               a = strtok((char *)NULL, t);
+       }
+       return (REVERSE_LIST (wl, WORD_LIST *));
+}
+
+GENERIC_LIST *
+list_reverse (list)
+GENERIC_LIST   *list;
+{
+       register GENERIC_LIST *next, *prev;
+
+       for (prev = 0; list; ) {
+               next = list->next;
+               list->next = prev;
+               prev = list;
+               list = next;
+       }
+       return prev;
+}
+
+char *
+pat_subst(s, t, u, i)
+char   *s, *t, *u;
+int    i;
+{
+       return ((char *)NULL);
+}
+
+char *
+quote_string(s)
+char   *s;
+{
+       return savestring(s);
+}
+
+print_element(ae)
+ARRAY_ELEMENT  *ae;
+{
+       char    lbuf[INT_STRLEN_BOUND (intmax_t) + 1];
+
+       printf("array[%s] = %s\n",
+               inttostr (element_index(ae), lbuf, sizeof (lbuf)),
+               element_value(ae));
+}
+
+print_array(a)
+ARRAY  *a;
+{
+       printf("\n");
+       array_walk(a, print_element, (void *)NULL);
+}
+
+main()
+{
+       ARRAY   *a, *new_a, *copy_of_a;
+       ARRAY_ELEMENT   *ae, *aew;
+       char    *s;
+
+       a = array_create();
+       array_insert(a, 1, "one");
+       array_insert(a, 7, "seven");
+       array_insert(a, 4, "four");
+       array_insert(a, 1029, "one thousand twenty-nine");
+       array_insert(a, 12, "twelve");
+       array_insert(a, 42, "forty-two");
+       print_array(a);
+       s = array_to_string (a, " ", 0);
+       printf("s = %s\n", s);
+       copy_of_a = array_from_string(s, " ");
+       printf("copy_of_a:");
+       print_array(copy_of_a);
+       array_dispose(copy_of_a);
+       printf("\n");
+       free(s);
+       ae = array_remove(a, 4);
+       array_dispose_element(ae);
+       ae = array_remove(a, 1029);
+       array_dispose_element(ae);
+       array_insert(a, 16, "sixteen");
+       print_array(a);
+       s = array_to_string (a, " ", 0);
+       printf("s = %s\n", s);
+       copy_of_a = array_from_string(s, " ");
+       printf("copy_of_a:");
+       print_array(copy_of_a);
+       array_dispose(copy_of_a);
+       printf("\n");
+       free(s);
+       array_insert(a, 2, "two");
+       array_insert(a, 1029, "new one thousand twenty-nine");
+       array_insert(a, 0, "zero");
+       array_insert(a, 134, "");
+       print_array(a);
+       s = array_to_string (a, ":", 0);
+       printf("s = %s\n", s);
+       copy_of_a = array_from_string(s, ":");
+       printf("copy_of_a:");
+       print_array(copy_of_a);
+       array_dispose(copy_of_a);
+       printf("\n");
+       free(s);
+       new_a = array_copy(a);
+       print_array(new_a);
+       s = array_to_string (new_a, ":", 0);
+       printf("s = %s\n", s);
+       copy_of_a = array_from_string(s, ":");
+       free(s);
+       printf("copy_of_a:");
+       print_array(copy_of_a);
+       array_shift(copy_of_a, 2, AS_DISPOSE);
+       printf("copy_of_a shifted by two:");
+       print_array(copy_of_a);
+       ae = array_shift(copy_of_a, 2, 0);
+       printf("copy_of_a shifted by two:");
+       print_array(copy_of_a);
+       for ( ; ae; ) {
+               aew = element_forw(ae);
+               array_dispose_element(ae);
+               ae = aew;
+       }
+       array_rshift(copy_of_a, 1, (char *)0);
+       printf("copy_of_a rshift by 1:");
+       print_array(copy_of_a);
+       array_rshift(copy_of_a, 2, "new element zero");
+       printf("copy_of_a rshift again by 2 with new element zero:");
+       print_array(copy_of_a);
+       s = array_to_assign(copy_of_a, 0);
+       printf("copy_of_a=%s\n", s);
+       free(s);
+       ae = array_shift(copy_of_a, array_num_elements(copy_of_a), 0);
+       for ( ; ae; ) {
+               aew = element_forw(ae);
+               array_dispose_element(ae);
+               ae = aew;
+       }
+       array_dispose(copy_of_a);
+       printf("\n");
+       array_dispose(a);
+       array_dispose(new_a);
+}
+
+#endif /* TEST_ARRAY */
+#endif /* ARRAY_VARS */
index 82058b748e673779ddc2639a64d47c6bd3667171..d46e3b02199e323cc3130bec3414701e8837c839 100644 (file)
@@ -282,6 +282,7 @@ assign_array_element_internal (entry, name, vname, sub, sublen, value, flags)
       if (akey == 0 || *akey == 0)
        {
          err_badarraysub (name);
+         FREE (akey);
          return ((SHELL_VAR *)NULL);
        }
       entry = bind_assoc_variable (entry, vname, akey, value, flags);
@@ -534,6 +535,7 @@ assign_compound_array_list (var, nlist, flags)
              if (akey == 0 || *akey == 0)
                {
                  err_badarraysub (w);
+                 FREE (akey);
                  continue;
                }
            }
@@ -700,6 +702,7 @@ unbind_array_element (var, sub)
       if (akey == 0 || *akey == 0)
        {
          builtin_error ("[%s]: %s", sub, _(bash_badsub_errmsg));
+         FREE (akey);
          return -1;
        }
       assoc_remove (assoc_cell (var), akey);
@@ -935,6 +938,7 @@ array_value_internal (s, quoted, flags, rtype, indp)
     return ((char *)NULL);     /* error message already printed */
 
   /* [ */
+  akey = 0;
   if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']')
     {
       if (rtype)
@@ -964,11 +968,11 @@ array_value_internal (s, quoted, flags, rtype, indp)
       if (t[0] == '*' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
        {
          temp = string_list_dollar_star (l);
-         retval = quote_string (temp);
+         retval = quote_string (temp);         /* XXX - leak here */
          free (temp);
        }
       else     /* ${name[@]} or unquoted ${name[*]} */
-       retval = string_list_dollar_at (l, quoted);
+       retval = string_list_dollar_at (l, quoted);     /* XXX - leak here */
 
       dispose_words (l);
     }
@@ -1001,11 +1005,17 @@ array_value_internal (s, quoted, flags, rtype, indp)
          akey = expand_assignment_string_to_string (t, 0);     /* [ */
          t[len - 1] = ']';
          if (akey == 0 || *akey == 0)
-           INDEX_ERROR();
+           {
+             FREE (akey);
+             INDEX_ERROR();
+           }
        }
      
       if (var == 0 || value_cell (var) == 0)   /* XXX - check invisible_p(var) ? */
-       return ((char *)NULL);
+       {
+          FREE (akey);
+         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))
diff --git a/arrayfunc.c~ b/arrayfunc.c~
new file mode 100644 (file)
index 0000000..0d64a2f
--- /dev/null
@@ -0,0 +1,1097 @@
+/* arrayfunc.c -- High-level array functions used by other parts of the shell. */
+
+/* Copyright (C) 2001-2011 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 SHELL_VAR *assign_array_element_internal __P((SHELL_VAR *, char *, char *, char *, int, 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;
+  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);
+  entry = assign_array_element_internal (entry, name, vname, sub, sublen, value, flags);
+
+  free (vname);
+  return entry;
+}
+
+static SHELL_VAR *
+assign_array_element_internal (entry, name, vname, sub, sublen, value, flags)
+     SHELL_VAR *entry;
+     char *name;               /* only used for error messages */
+     char *vname;
+     char *sub;
+     int sublen;
+     char *value;
+     int flags;
+{
+  char *akey;
+  arrayind_t ind;
+
+  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)
+       {
+         err_badarraysub (name);
+         FREE (akey);
+         return ((SHELL_VAR *)NULL);
+       }
+      entry = bind_assoc_variable (entry, vname, akey, value, flags);
+    }
+  else
+    {
+      ind = array_expand_index (entry, sub, sublen);
+      if (ind < 0)
+       {
+         err_badarraysub (name);
+         return ((SHELL_VAR *)NULL);
+       }
+      entry = bind_array_variable (vname, ind, value, flags);
+    }
+
+  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;
+  WORD_LIST *hd, *tl, *t, *n;
+  char *val;
+  int ni;
+
+  /* This condition is true when invoked from the declare builtin with a
+     command like
+       declare -a d='([1]="" [2]="bdef" [5]="hello world" "test")' */
+  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 (var && assoc_p (var))
+    {
+      if (val != value)
+       free (val);
+      return list;
+    }
+
+  /* If we're using [subscript]=value, we need to quote each [ and ] to
+     prevent unwanted filename expansion.  This doesn't need to be done
+     for associative array expansion, since that uses a different expansion
+     function (see assign_compound_array_list below). */
+  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, free_val;
+  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] == '[')
+       {
+         /* Don't have to handle embedded quotes specially any more, since
+            associative array subscripts have not been expanded yet (see
+            above). */
+         len = skipsubscript (w, 0, 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 (var, w + 1, len);
+             if (ind < 0)
+               {
+                 err_badarraysub (w);
+                 continue;
+               }
+
+             last_ind = ind;
+           }
+         else if (assoc_p (var))
+           {
+             /* This is not performed above, see expand_compound_array_assignment */
+             w[len] = '\0';    /*[*/
+             akey = expand_assignment_string_to_string (w+1, 0);
+             w[len] = ']';
+             /* And we need to expand the value also, see below */
+             if (akey == 0 || *akey == 0)
+               {
+                 err_badarraysub (w);
+                 FREE (akey);
+                 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;
+       }
+
+      free_val = 0;
+      /* See above; we need to expand the value here */
+      if (assoc_p (var))
+       {
+         val = expand_assignment_string_to_string (val, 0);
+         free_val = 1;
+       }
+
+      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++;
+
+      if (free_val)
+       free (val);
+    }
+}
+
+/* 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));
+         FREE (akey);
+         return -1;
+       }
+      assoc_remove (assoc_cell (var), akey);
+      free (akey);
+    }
+  else
+    {
+      ind = array_expand_index (var, 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 (var, s, len)
+     SHELL_VAR *var;
+     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;
+}
+
+#define INDEX_ERROR() \
+  do \
+    { \
+      if (var) \
+       err_badarraysub (var->name); \
+      else \
+       { \
+         t[-1] = '\0'; \
+         err_badarraysub (s); \
+         t[-1] = '[';  /* ] */\
+       } \
+      return ((char *)NULL); \
+    } \
+  while (0)
+
+/* 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, flags, rtype, indp)
+     char *s;
+     int quoted, flags, *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 */
+
+  /* [ */
+  akey = 0;
+  if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']')
+    {
+      if (rtype)
+       *rtype = (t[0] == '*') ? 1 : 2;
+      if ((flags & AV_ALLOWALL) == 0)
+       {
+         err_badarraysub (s);
+         return ((char *)NULL);
+       }
+      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);
+      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)
+       {
+         if ((flags & AV_USEIND) == 0 || indp == 0)
+           {
+             ind = array_expand_index (var, t, len);
+             if (ind < 0)
+               {
+                 /* negative subscripts to indexed arrays count back from end */
+                 if (var && array_p (var))
+                   ind = array_max_index (array_cell (var)) + 1 + ind;
+                 if (ind < 0)
+                   INDEX_ERROR();
+               }
+             if (indp)
+               *indp = ind;
+           }
+         else if (indp)
+           ind = *indp;
+       }
+      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)
+           {
+             FREE (akey);
+             INDEX_ERROR();
+           }
+       }
+     
+      if (var == 0 || value_cell (var) == 0)   /* XXX - check invisible_p(var) ? */
+       {
+          FREE (akey);
+         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, flags, rtype, indp)
+     char *s;
+     int quoted, flags, *rtype;
+     arrayind_t *indp;
+{
+  return (array_value_internal (s, quoted, flags|AV_ALLOWALL, rtype, indp));
+}
+
+/* Return the value of the array indexing expression S as a single string.
+   If (FLAGS & AV_ALLOWALL) 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, flags, rtype, indp)
+     char *s;
+     int flags, *rtype;
+     arrayind_t *indp;
+{
+  return (array_value_internal (s, 0, flags, 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 */
diff --git a/assoc.c b/assoc.c
index 5d3cd11bc3997cdc3d936859e5334d0fc6164e5a..f9ed8816e305fe71b829dfef874d997319b41d1b 100644 (file)
--- a/assoc.c
+++ b/assoc.c
@@ -533,7 +533,7 @@ assoc_to_string (h, sep, quoted)
     return (savestring (""));
 
   result = NULL;
-  list = NULL;
+  l = list = NULL;
   /* This might be better implemented directly, but it's simple to implement
      by converting to a word list first, possibly quoting the data, then
      using list_string */
@@ -551,6 +551,8 @@ assoc_to_string (h, sep, quoted)
   l = REVERSE_LIST(list, WORD_LIST *);
 
   result = l ? string_list_internal (l, sep) : savestring ("");
+  dispose_words (l);  
+
   return result;
 }
 
diff --git a/assoc.c~ b/assoc.c~
new file mode 100644 (file)
index 0000000..5d3cd11
--- /dev/null
+++ b/assoc.c~
@@ -0,0 +1,557 @@
+/*
+ * assoc.c - functions to manipulate associative arrays
+ *
+ * Associative arrays are standard shell hash tables.
+ *
+ * Chet Ramey
+ * chet@ins.cwru.edu
+ */
+
+/* Copyright (C) 2008,2009,2011 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)
+#  ifdef _MINIX
+#    include <sys/types.h>
+#  endif
+#  include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include "bashansi.h"
+
+#include "shell.h"
+#include "array.h"
+#include "assoc.h"
+#include "builtins/common.h"
+
+static WORD_LIST *assoc_to_word_list_internal __P((HASH_TABLE *, int));
+
+/* assoc_create == hash_create */
+
+void
+assoc_dispose (hash)
+     HASH_TABLE *hash;
+{
+  if (hash)
+    {
+      hash_flush (hash, 0);
+      hash_dispose (hash);
+    }
+}
+
+void
+assoc_flush (hash)
+     HASH_TABLE *hash;
+{
+  hash_flush (hash, 0);
+}
+
+int
+assoc_insert (hash, key, value)
+     HASH_TABLE *hash;
+     char *key;
+     char *value;
+{
+  BUCKET_CONTENTS *b;
+
+  b = hash_search (key, hash, HASH_CREATE);
+  if (b == 0)
+    return -1;
+  /* If we are overwriting an existing element's value, we're not going to
+     use the key.  Nothing in the array assignment code path frees the key
+     string, so we can free it here to avoid a memory leak. */
+  if (b->key != key)
+    free (key);
+  FREE (b->data);
+  b->data = value ? savestring (value) : (char *)0;
+  return (0);
+}
+
+/* Like assoc_insert, but returns b->data instead of freeing it */
+PTR_T
+assoc_replace (hash, key, value)
+     HASH_TABLE *hash;
+     char *key;
+     char *value;
+{
+  BUCKET_CONTENTS *b;
+  PTR_T t;
+
+  b = hash_search (key, hash, HASH_CREATE);
+  if (b == 0)
+    return (PTR_T)0;
+  /* If we are overwriting an existing element's value, we're not going to
+     use the key.  Nothing in the array assignment code path frees the key
+     string, so we can free it here to avoid a memory leak. */
+  if (b->key != key)
+    free (key);
+  t = b->data;
+  b->data = value ? savestring (value) : (char *)0;
+  return t;
+}
+
+void
+assoc_remove (hash, string)
+     HASH_TABLE *hash;
+     char *string;
+{
+  BUCKET_CONTENTS *b;
+
+  b = hash_remove (string, hash, 0);
+  if (b)
+    {
+      free ((char *)b->data);
+      free (b->key);
+      free (b);
+    }
+}
+
+char *
+assoc_reference (hash, string)
+     HASH_TABLE *hash;
+     char *string;
+{
+  BUCKET_CONTENTS *b;
+
+  if (hash == 0)
+    return (char *)0;
+
+  b = hash_search (string, hash, 0);
+  return (b ? (char *)b->data : 0);
+}
+
+/* Quote the data associated with each element of the hash table ASSOC,
+   using quote_string */
+HASH_TABLE *
+assoc_quote (h)
+     HASH_TABLE *h;
+{
+  int i;
+  BUCKET_CONTENTS *tlist;
+  char *t;
+
+  if (h == 0 || assoc_empty (h))
+    return ((HASH_TABLE *)NULL);
+  
+  for (i = 0; i < h->nbuckets; i++)
+    for (tlist = hash_items (i, h); tlist; tlist = tlist->next)
+      {
+       t = quote_string ((char *)tlist->data);
+       FREE (tlist->data);
+       tlist->data = t;
+      }
+
+  return h;
+}
+
+/* Quote escape characters in the data associated with each element
+   of the hash table ASSOC, using quote_escapes */
+HASH_TABLE *
+assoc_quote_escapes (h)
+     HASH_TABLE *h;
+{
+  int i;
+  BUCKET_CONTENTS *tlist;
+  char *t;
+
+  if (h == 0 || assoc_empty (h))
+    return ((HASH_TABLE *)NULL);
+  
+  for (i = 0; i < h->nbuckets; i++)
+    for (tlist = hash_items (i, h); tlist; tlist = tlist->next)
+      {
+       t = quote_escapes ((char *)tlist->data);
+       FREE (tlist->data);
+       tlist->data = t;
+      }
+
+  return h;
+}
+
+HASH_TABLE *
+assoc_dequote (h)
+     HASH_TABLE *h;
+{
+  int i;
+  BUCKET_CONTENTS *tlist;
+  char *t;
+
+  if (h == 0 || assoc_empty (h))
+    return ((HASH_TABLE *)NULL);
+  
+  for (i = 0; i < h->nbuckets; i++)
+    for (tlist = hash_items (i, h); tlist; tlist = tlist->next)
+      {
+       t = dequote_string ((char *)tlist->data);
+       FREE (tlist->data);
+       tlist->data = t;
+      }
+
+  return h;
+}
+
+HASH_TABLE *
+assoc_dequote_escapes (h)
+     HASH_TABLE *h;
+{
+  int i;
+  BUCKET_CONTENTS *tlist;
+  char *t;
+
+  if (h == 0 || assoc_empty (h))
+    return ((HASH_TABLE *)NULL);
+  
+  for (i = 0; i < h->nbuckets; i++)
+    for (tlist = hash_items (i, h); tlist; tlist = tlist->next)
+      {
+       t = dequote_escapes ((char *)tlist->data);
+       FREE (tlist->data);
+       tlist->data = t;
+      }
+
+  return h;
+}
+
+HASH_TABLE *
+assoc_remove_quoted_nulls (h)
+     HASH_TABLE *h;
+{
+  int i;
+  BUCKET_CONTENTS *tlist;
+  char *t;
+
+  if (h == 0 || assoc_empty (h))
+    return ((HASH_TABLE *)NULL);
+  
+  for (i = 0; i < h->nbuckets; i++)
+    for (tlist = hash_items (i, h); tlist; tlist = tlist->next)
+      {
+       t = remove_quoted_nulls ((char *)tlist->data);
+       tlist->data = t;
+      }
+
+  return h;
+}
+
+/*
+ * Return a string whose elements are the members of array H beginning at
+ * the STARTth element and spanning NELEM members.  Null elements are counted.
+ */
+char *
+assoc_subrange (hash, start, nelem, starsub, quoted)
+HASH_TABLE *hash;
+arrayind_t start, nelem;
+int starsub, quoted;
+{
+  WORD_LIST *l, *save, *h, *t;
+  int i, j;
+  char *ret;
+
+  if (assoc_empty (hash))
+    return ((char *)NULL);
+
+  save = l = assoc_to_word_list (hash);
+  if (save == 0)
+    return ((char *)NULL);
+
+  for (i = 1; l && i < start; i++)
+    l = l->next;
+  if (l == 0)
+    return ((char *)NULL);
+  for (j = 0,h = t = l; l && j < nelem; j++)
+    {
+      t = l;
+      l = l->next;
+    }
+
+  t->next = (WORD_LIST *)NULL;
+
+  ret = string_list_pos_params (starsub ? '*' : '@', h, quoted);
+
+  if (t != l)
+    t->next = l;
+
+  dispose_words (save);
+  return (ret);
+
+}
+
+char *
+assoc_patsub (h, pat, rep, mflags)
+     HASH_TABLE *h;
+     char *pat, *rep;
+     int mflags;
+{
+  BUCKET_CONTENTS *tlist;
+  int i, slen;
+  HASH_TABLE *h2;
+  char *t, *sifs, *ifs;
+
+  if (h == 0 || assoc_empty (h))
+    return ((char *)NULL);
+
+  h2 = assoc_copy (h);
+  for (i = 0; i < h2->nbuckets; i++)
+    for (tlist = hash_items (i, h2); tlist; tlist = tlist->next)
+      {
+       t = pat_subst ((char *)tlist->data, pat, rep, mflags);
+       FREE (tlist->data);
+       tlist->data = t;
+      }
+
+  if (mflags & MATCH_QUOTED)
+    assoc_quote (h2);
+  else
+    assoc_quote_escapes (h2);
+
+  if (mflags & MATCH_STARSUB)
+    {
+      assoc_remove_quoted_nulls (h2);
+      sifs = ifs_firstchar ((int *)NULL);
+      t = assoc_to_string (h2, sifs, 0);
+      free (sifs);
+    }
+  else if (mflags & MATCH_QUOTED)
+    {
+      /* ${array[@]} */
+      sifs = ifs_firstchar (&slen);
+      ifs = getifs ();
+      if (ifs == 0 || *ifs == 0)
+       {
+         if (slen < 2)
+           sifs = xrealloc (sifs, 2);
+         sifs[0] = ' ';
+         sifs[1] = '\0';
+       }
+      t = assoc_to_string (h2, sifs, 0);
+      free(sifs);
+    }
+  else
+    t = assoc_to_string (h2, " ", 0);
+
+  assoc_dispose (h2);
+
+  return t;
+}
+
+char *
+assoc_modcase (h, pat, modop, mflags)
+     HASH_TABLE *h;
+     char *pat;
+     int modop;
+     int mflags;
+{
+  BUCKET_CONTENTS *tlist;
+  int i, slen;
+  HASH_TABLE *h2;
+  char *t, *sifs, *ifs;
+
+  if (h == 0 || assoc_empty (h))
+    return ((char *)NULL);
+
+  h2 = assoc_copy (h);
+  for (i = 0; i < h2->nbuckets; i++)
+    for (tlist = hash_items (i, h2); tlist; tlist = tlist->next)
+      {
+       t = sh_modcase ((char *)tlist->data, pat, modop);
+       FREE (tlist->data);
+       tlist->data = t;
+      }
+
+  if (mflags & MATCH_QUOTED)
+    assoc_quote (h2);
+  else
+    assoc_quote_escapes (h2);
+
+  if (mflags & MATCH_STARSUB)
+    {
+      assoc_remove_quoted_nulls (h2);
+      sifs = ifs_firstchar ((int *)NULL);
+      t = assoc_to_string (h2, sifs, 0);
+      free (sifs);
+    }
+  else if (mflags & MATCH_QUOTED)
+    {
+      /* ${array[@]} */
+      sifs = ifs_firstchar (&slen);
+      ifs = getifs ();
+      if (ifs == 0 || *ifs == 0)
+       {
+         if (slen < 2)
+           sifs = xrealloc (sifs, 2);
+         sifs[0] = ' ';
+         sifs[1] = '\0';
+       }
+      t = assoc_to_string (h2, sifs, 0);
+      free(sifs);
+    }
+  else
+    t = assoc_to_string (h2, " ", 0);
+
+  assoc_dispose (h2);
+
+  return t;
+}
+
+char *
+assoc_to_assign (hash, quoted)
+     HASH_TABLE *hash;
+     int quoted;
+{
+  char *ret;
+  char *istr, *vstr;
+  int i, rsize, rlen, elen;
+  BUCKET_CONTENTS *tlist;
+
+  if (hash == 0 || assoc_empty (hash))
+    return (char *)0;
+
+  ret = xmalloc (rsize = 128);
+  ret[0] = '(';
+  rlen = 1;
+
+  for (i = 0; i < hash->nbuckets; i++)
+    for (tlist = hash_items (i, hash); tlist; tlist = tlist->next)
+      {
+#if 1
+       if (sh_contains_shell_metas (tlist->key))
+         istr = sh_double_quote (tlist->key);
+       else
+         istr = tlist->key;    
+#else
+       istr = tlist->key;
+#endif
+       vstr = tlist->data ? sh_double_quote ((char *)tlist->data) : (char *)0;
+
+       elen = STRLEN (istr) + 8 + STRLEN (vstr);
+       RESIZE_MALLOCED_BUFFER (ret, rlen, (elen+1), rsize, rsize);
+
+       ret[rlen++] = '[';
+       strcpy (ret+rlen, istr);
+       rlen += STRLEN (istr);
+       ret[rlen++] = ']';
+       ret[rlen++] = '=';
+       if (vstr)
+         {
+           strcpy (ret + rlen, vstr);
+           rlen += STRLEN (vstr);
+         }
+       ret[rlen++] = ' ';
+
+
+       if (istr != tlist->key)
+         FREE (istr);
+
+       FREE (vstr);
+    }
+
+  RESIZE_MALLOCED_BUFFER (ret, rlen, 1, rsize, 8);
+  ret[rlen++] = ')';
+  ret[rlen] = '\0';
+
+  if (quoted)
+    {
+      vstr = sh_single_quote (ret);
+      free (ret);
+      ret = vstr;
+    }
+
+  return ret;
+}
+
+static WORD_LIST *
+assoc_to_word_list_internal (h, t)
+     HASH_TABLE *h;
+     int t;
+{
+  WORD_LIST *list;
+  int i;
+  BUCKET_CONTENTS *tlist;
+  char *w;
+
+  if (h == 0 || assoc_empty (h))
+    return((WORD_LIST *)NULL);
+  list = (WORD_LIST *)NULL;
+  
+  for (i = 0; i < h->nbuckets; i++)
+    for (tlist = hash_items (i, h); tlist; tlist = tlist->next)
+      {
+       w = (t == 0) ? (char *)tlist->data : (char *)tlist->key;
+       list = make_word_list (make_bare_word(w), list);
+      }
+  return (REVERSE_LIST(list, WORD_LIST *));
+}
+
+WORD_LIST *
+assoc_to_word_list (h)
+     HASH_TABLE *h;
+{
+  return (assoc_to_word_list_internal (h, 0));
+}
+
+WORD_LIST *
+assoc_keys_to_word_list (h)
+     HASH_TABLE *h;
+{
+  return (assoc_to_word_list_internal (h, 1));
+}
+
+char *
+assoc_to_string (h, sep, quoted)
+     HASH_TABLE *h;
+     char *sep;
+     int quoted;
+{
+  BUCKET_CONTENTS *tlist;
+  int i;
+  char *result, *t, *w;
+  WORD_LIST *list, *l;
+
+  if (h == 0)
+    return ((char *)NULL);
+  if (assoc_empty (h))
+    return (savestring (""));
+
+  result = NULL;
+  list = NULL;
+  /* This might be better implemented directly, but it's simple to implement
+     by converting to a word list first, possibly quoting the data, then
+     using list_string */
+  for (i = 0; i < h->nbuckets; i++)
+    for (tlist = hash_items (i, h); tlist; tlist = tlist->next)
+      {
+       w = (char *)tlist->data;
+       if (w == 0)
+         continue;
+       t = quoted ? quote_string (w) : savestring (w);
+       list = make_word_list (make_bare_word(t), list);
+       FREE (t);
+      }
+
+  l = REVERSE_LIST(list, WORD_LIST *);
+
+  result = l ? string_list_internal (l, sep) : savestring ("");
+  return result;
+}
+
+#endif /* ARRAY_VARS */
index 70dce90b1a71d05816feb2f244713289745d5a16..982c82283df7ff154c2296835b4ee2dd6a988892 100644 (file)
@@ -1807,7 +1807,7 @@ globword:
         {
          if (executable_or_directory (val))
            {
-             if (*hint_text == '~')
+             if (*hint_text == '~' && directory_part)
                {
                  temp = restore_tilde (val, directory_part);
                  free (val);
@@ -2773,8 +2773,11 @@ bash_directory_expansion (dirname)
 
   d = savestring (*dirname);
 
-  if (rl_directory_rewrite_hook)
-    (*rl_directory_rewrite_hook) (&d);
+  if ((rl_directory_rewrite_hook) &&  (*rl_directory_rewrite_hook) (&d))
+    {
+      free (*dirname);
+      *dirname = d;
+    }
   else if (rl_directory_completion_hook && (*rl_directory_completion_hook) (&d))
     {
       free (*dirname);
@@ -3904,12 +3907,16 @@ bind_keyseq_to_unix_command (line)
   if (line[i] != ':')
     {
       builtin_error (_("%s: missing colon separator"), line);
+      FREE (kseq);
       return -1;
     }
 
   i = isolate_sequence (line, i + 1, 0, &kstart);
   if (i < 0)
-    return -1;
+    {
+      FREE (kseq);
+      return -1;
+    }
 
   /* Create the value string containing the command to execute. */
   value = substring (line, kstart, i);
@@ -3920,7 +3927,8 @@ bind_keyseq_to_unix_command (line)
   /* 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);
-  
+
+  free (kseq);  
   return 0;
 }
 
index bec1b655dcc8ad6bbd45ab6833f39938992b11d8..58c5ba5a545011974c512e233e876cde1f0d7eba 100644 (file)
@@ -526,9 +526,8 @@ initialize_readline ()
   set_directory_hook ();
 
   rl_filename_rewrite_hook = bash_filename_rewrite_hook;
-#if 0
+
   rl_filename_stat_hook = bash_filename_stat_hook;
-#endif
 
   /* Tell the filename completer we want a chance to ignore some names. */
   rl_ignore_some_completions_function = filename_completion_ignore;
@@ -1340,6 +1339,7 @@ attempt_shell_completion (text, start, end)
 
   rl_filename_quote_characters = default_filename_quote_characters;
   set_filename_bstab (rl_filename_quote_characters);
+  set_directory_hook ();
 
   /* 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
@@ -1671,6 +1671,12 @@ command_word_completion_function (hint_text, state)
            }
          else
            {
+            if (dircomplete_expand && path_dot_or_dotdot (filename_hint))
+               {
+                 dircomplete_expand = 0;
+                 set_directory_hook ();
+                 dircomplete_expand = 1;
+               }
              mapping_over = 4;
              goto inner;
            }
@@ -1801,7 +1807,7 @@ globword:
         {
          if (executable_or_directory (val))
            {
-             if (*hint_text == '~')
+             if (*hint_text == '~' && directory_part)
                {
                  temp = restore_tilde (val, directory_part);
                  free (val);
@@ -1871,6 +1877,9 @@ globword:
 
  inner:
   val = rl_filename_completion_function (filename_hint, istate);
+  if (mapping_over == 4 && dircomplete_expand)
+    set_directory_hook ();
+
   istate = 1;
 
   if (val == 0)
@@ -2029,7 +2038,7 @@ command_subst_completion_function (text, state)
        rl_completion_suppress_append = 1;
     }
 
-  if (!matches || !matches[cmd_index])
+  if (matches == 0 || matches[cmd_index] == 0)
     {
       rl_filename_quoting_desired = 0; /* disable quoting */
       return ((char *)NULL);
index 27b9eef233ed10e61bcb194a941a8212708b2482..fb0caaac2de6994d018844355d4b24efd136f22b 100644 (file)
@@ -1,7 +1,7 @@
 This file is cd.def, from which is created cd.c.  It implements the
 builtins "cd" and "pwd" in Bash.
 
-Copyright (C) 1987-2010 Free Software Foundation, Inc.
+Copyright (C) 1987-2011 Free Software Foundation, Inc.
 
 This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -338,6 +338,7 @@ cd_builtin (list)
       if (temp && change_to_directory (temp, no_symlinks))
        {
          printf ("%s\n", temp);
+         free (temp);
          return (bindpwd (no_symlinks));
        }
       else
@@ -406,7 +407,11 @@ pwd_builtin (list)
      the file system has changed state underneath bash). */
   if ((tcwd && directory == 0) ||
       (posixly_correct && same_file (".", tcwd, (struct stat *)0, (struct stat *)0) == 0))
-    directory = resetpwd ("pwd");
+    {
+      if (directory && directory != tcwd)
+        free (directory);
+      directory = resetpwd ("pwd");
+    }
 
 #undef tcwd
 
@@ -495,6 +500,8 @@ change_to_directory (newdir, nolinks)
          t = resetpwd ("cd");
          if (t == 0)
            set_working_directory (tdir);
+         else
+           free (t);
        }
       else
        set_working_directory (tdir);
index b1aae26d01ebd0995d7d362af6c7f7a374da7dc9..7756931aaa92e2657246113dce8c18328d148f58 100644 (file)
@@ -1,7 +1,7 @@
 This file is cd.def, from which is created cd.c.  It implements the
 builtins "cd" and "pwd" in Bash.
 
-Copyright (C) 1987-2010 Free Software Foundation, Inc.
+Copyright (C) 1987-2011 Free Software Foundation, Inc.
 
 This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -290,7 +290,8 @@ cd_builtin (list)
            free (temp);
        }
 
-#if 0  /* changed for bash-4.2 Posix cd description steps 5-6 */
+#if 0
+      /* changed for bash-4.2 Posix cd description steps 5-6 */
       /* POSIX.2 says that if `.' does not appear in $CDPATH, we don't
         try the current directory, so we just punt now with an error
         message if POSIXLY_CORRECT is non-zero.  The check for cdpath[0]
@@ -494,6 +495,8 @@ change_to_directory (newdir, nolinks)
          t = resetpwd ("cd");
          if (t == 0)
            set_working_directory (tdir);
+         else
+           free (t);
        }
       else
        set_working_directory (tdir);
index 8d4ec3cd54cebeae4236b6d90bde2d6f48e3ae29..f5b323feadaf2a443fbc420a560cff1d0bad522b 100644 (file)
@@ -1,7 +1,7 @@
 This file is complete.def, from which is created complete.c.
 It implements the builtins "complete", "compgen", and "compopt" in Bash.
 
-Copyright (C) 1999-2010 Free Software Foundation, Inc.
+Copyright (C) 1999-2011 Free Software Foundation, Inc.
 
 This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -728,6 +728,7 @@ compgen_builtin (list)
   if ((sl == 0 || sl->list_len == 0) && (copts & COPT_DEFAULT))
     {
       matches = rl_completion_matches (word, rl_filename_completion_function);
+      strlist_dispose (sl);
       sl = completions_to_stringlist (matches);
       strvec_dispose (matches);
     }
diff --git a/builtins/complete.def~ b/builtins/complete.def~
new file mode 100644 (file)
index 0000000..8d4ec3c
--- /dev/null
@@ -0,0 +1,869 @@
+This file is complete.def, from which is created complete.c.
+It implements the builtins "complete", "compgen", and "compopt" in Bash.
+
+Copyright (C) 1999-2010 Free Software Foundation, Inc.
+
+This file is part of GNU Bash, the Bourne Again SHell.
+
+Bash is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Bash is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+
+$PRODUCES complete.c
+
+$BUILTIN complete
+$DEPENDS_ON PROGRAMMABLE_COMPLETION
+$FUNCTION complete_builtin
+$SHORT_DOC complete [-abcdefgjksuv] [-pr] [-DE] [-o option] [-A action] [-G globpat] [-W wordlist]  [-F function] [-C command] [-X filterpat] [-P prefix] [-S suffix] [name ...]
+Specify how arguments are to be completed by Readline.
+
+For each NAME, specify how arguments are to be completed.  If no options
+are supplied, existing completion specifications are printed in a way that
+allows them to be reused as input.
+
+Options:
+  -p   print existing completion specifications in a reusable format
+  -r   remove a completion specification for each NAME, or, if no
+       NAMEs are supplied, all completion specifications
+  -D   apply the completions and actions as the default for commands
+       without any specific completion defined
+  -E   apply the completions and actions to "empty" commands --
+       completion attempted on a blank line
+
+When completion is attempted, the actions are applied in the order the
+uppercase-letter options are listed above.  The -D option takes
+precedence over -E.
+
+Exit Status:
+Returns success unless an invalid option is supplied or an error occurs.
+$END
+
+#include <config.h>
+
+#include <stdio.h>
+
+#include "../bashtypes.h"
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#include "../bashansi.h"
+#include "../bashintl.h"
+
+#include "../shell.h"
+#include "../builtins.h"
+#include "../pcomplete.h"
+#include "../bashline.h"
+
+#include "common.h"
+#include "bashgetopt.h"
+
+#include <readline/readline.h>
+
+#define STRDUP(x)       ((x) ? savestring (x) : (char *)NULL)
+
+/* Structure containing all the non-action (binary) options; filled in by
+   build_actions(). */
+struct _optflags {
+  int pflag;
+  int rflag;
+  int Dflag;
+  int Eflag;
+};
+
+static int find_compact __P((char *));
+static int find_compopt __P((char *));
+
+static int build_actions __P((WORD_LIST *, struct _optflags *, unsigned long *, unsigned long *));
+
+static int remove_cmd_completions __P((WORD_LIST *));
+
+static int print_one_completion __P((char *, COMPSPEC *));
+static int print_compitem __P((BUCKET_CONTENTS *));
+static void print_compopts __P((const char *, COMPSPEC *, int));
+static void print_all_completions __P((void));
+static int print_cmd_completions __P((WORD_LIST *));
+
+static char *Garg, *Warg, *Parg, *Sarg, *Xarg, *Farg, *Carg;
+
+static const struct _compacts {
+  const char * const actname;
+  int actflag;
+  int actopt;
+} compacts[] = {
+  { "alias",     CA_ALIAS,     'a' },
+  { "arrayvar",  CA_ARRAYVAR,   0 },
+  { "binding",   CA_BINDING,    0 },
+  { "builtin",   CA_BUILTIN,   'b' },
+  { "command",   CA_COMMAND,   'c' },
+  { "directory", CA_DIRECTORY, 'd' },
+  { "disabled",  CA_DISABLED,   0 },
+  { "enabled",   CA_ENABLED,    0 },
+  { "export",    CA_EXPORT,    'e' },
+  { "file",      CA_FILE,      'f' },
+  { "function",  CA_FUNCTION,   0 },
+  { "helptopic", CA_HELPTOPIC,  0 },
+  { "hostname",  CA_HOSTNAME,   0 },
+  { "group",     CA_GROUP,     'g' },
+  { "job",       CA_JOB,       'j' },
+  { "keyword",   CA_KEYWORD,   'k' },
+  { "running",   CA_RUNNING,    0 },
+  { "service",   CA_SERVICE,   's' },
+  { "setopt",    CA_SETOPT,     0 },
+  { "shopt",     CA_SHOPT,      0 },
+  { "signal",    CA_SIGNAL,     0 },
+  { "stopped",   CA_STOPPED,    0 },
+  { "user",      CA_USER,      'u' },
+  { "variable",  CA_VARIABLE,  'v' },
+  { (char *)NULL, 0, 0 },
+};
+
+/* This should be a STRING_INT_ALIST */
+static const struct _compopt {
+  const char * const optname;
+  int optflag;
+} compopts[] = {
+  { "bashdefault", COPT_BASHDEFAULT },
+  { "default", COPT_DEFAULT },
+  { "dirnames", COPT_DIRNAMES },
+  { "filenames",COPT_FILENAMES},
+  { "nospace", COPT_NOSPACE },
+  { "plusdirs", COPT_PLUSDIRS },
+  { (char *)NULL, 0 },
+};
+
+static int
+find_compact (name)
+     char *name;
+{
+  register int i;
+
+  for (i = 0; compacts[i].actname; i++)
+    if (STREQ (name, compacts[i].actname))
+      return i;
+  return -1;
+}
+
+static int
+find_compopt (name)
+     char *name;
+{
+  register int i;
+
+  for (i = 0; compopts[i].optname; i++)
+    if (STREQ (name, compopts[i].optname))
+      return i;
+  return -1;
+}
+
+/* Build the actions and compspec options from the options specified in LIST.
+   ACTP is a pointer to an unsigned long in which to place the bitmap of
+   actions.  OPTP is a pointer to an unsigned long in which to place the
+   btmap of compspec options (arguments to `-o').  PP, if non-null, gets 1
+   if -p is supplied; RP, if non-null, gets 1 if -r is supplied.
+   If either is null, the corresponding option generates an error.
+   This also sets variables corresponding to options that take arguments as
+   a side effect; the caller should ensure that those variables are set to
+   NULL before calling build_actions.  Return value:
+       EX_USAGE = bad option
+       EXECUTION_SUCCESS = some options supplied
+       EXECUTION_FAILURE = no options supplied
+*/
+
+static int
+build_actions (list, flagp, actp, optp)
+     WORD_LIST *list;
+     struct _optflags *flagp;
+     unsigned long *actp, *optp;
+{
+  int opt, ind, opt_given;
+  unsigned long acts, copts;
+
+  acts = copts = (unsigned long)0L;
+  opt_given = 0;
+
+  reset_internal_getopt ();
+  while ((opt = internal_getopt (list, "abcdefgjko:prsuvA:G:W:P:S:X:F:C:DE")) != -1)
+    {
+      opt_given = 1;
+      switch (opt)
+       {
+       case 'r':
+         if (flagp)
+           {
+             flagp->rflag = 1;
+             break;
+           }
+         else
+           {
+             sh_invalidopt ("-r");
+             builtin_usage ();
+             return (EX_USAGE);
+           }
+
+       case 'p':
+         if (flagp)
+           {
+             flagp->pflag = 1;
+             break;
+           }
+         else
+           {
+             sh_invalidopt ("-p");
+             builtin_usage ();
+             return (EX_USAGE);
+           }
+
+       case 'a':
+         acts |= CA_ALIAS;
+         break;
+       case 'b':
+         acts |= CA_BUILTIN;
+         break;
+       case 'c':
+         acts |= CA_COMMAND;
+         break;
+       case 'd':
+         acts |= CA_DIRECTORY;
+         break;
+       case 'e':
+         acts |= CA_EXPORT;
+         break;
+       case 'f':
+         acts |= CA_FILE;
+         break;
+       case 'g':
+         acts |= CA_GROUP;
+         break;
+       case 'j':
+         acts |= CA_JOB;
+         break;
+       case 'k':
+         acts |= CA_KEYWORD;
+         break;
+       case 's':
+         acts |= CA_SERVICE;
+         break;
+       case 'u':
+         acts |= CA_USER;
+         break;
+       case 'v':
+         acts |= CA_VARIABLE;
+         break;
+       case 'o':
+         ind = find_compopt (list_optarg);
+         if (ind < 0)
+           {
+             sh_invalidoptname (list_optarg);
+             return (EX_USAGE);
+           }
+         copts |= compopts[ind].optflag;
+         break;
+       case 'A':
+         ind = find_compact (list_optarg);
+         if (ind < 0)
+           {
+             builtin_error (_("%s: invalid action name"), list_optarg);
+             return (EX_USAGE);
+           }
+         acts |= compacts[ind].actflag;
+         break;
+       case 'C':
+         Carg = list_optarg;
+         break;
+       case 'D':
+         if (flagp)
+           {
+             flagp->Dflag = 1;
+             break;
+           }
+         else
+           {
+             sh_invalidopt ("-D");
+             builtin_usage ();
+             return (EX_USAGE);
+           }
+       case 'E':
+         if (flagp)
+           {
+             flagp->Eflag = 1;
+             break;
+           }
+         else
+           {
+             sh_invalidopt ("-E");
+             builtin_usage ();
+             return (EX_USAGE);
+           }
+       case 'F':
+         Farg = list_optarg;
+         break;
+       case 'G':
+         Garg = list_optarg;
+         break;
+       case 'P':
+         Parg = list_optarg;
+         break;
+       case 'S':
+         Sarg = list_optarg;
+         break;
+       case 'W':
+         Warg = list_optarg;
+         break;
+       case 'X':
+         Xarg = list_optarg;
+         break;
+       default:
+         builtin_usage ();
+         return (EX_USAGE);
+       }
+    }
+
+  *actp = acts;
+  *optp = copts;
+
+  return (opt_given ? EXECUTION_SUCCESS : EXECUTION_FAILURE);
+}
+
+/* Add, remove, and display completion specifiers. */
+int
+complete_builtin (list)
+     WORD_LIST *list;
+{
+  int opt_given, rval;
+  unsigned long acts, copts;
+  COMPSPEC *cs;
+  struct _optflags oflags;
+  WORD_LIST *l, *wl;
+
+  if (list == 0)
+    {
+      print_all_completions ();
+      return (EXECUTION_SUCCESS);
+    }
+
+  opt_given = oflags.pflag = oflags.rflag = oflags.Dflag = oflags.Eflag = 0;
+
+  acts = copts = (unsigned long)0L;
+  Garg = Warg = Parg = Sarg = Xarg = Farg = Carg = (char *)NULL;
+  cs = (COMPSPEC *)NULL;
+
+  /* Build the actions from the arguments.  Also sets the [A-Z]arg variables
+     as a side effect if they are supplied as options. */
+  rval = build_actions (list, &oflags, &acts, &copts);
+  if (rval == EX_USAGE)
+    return (rval);
+  opt_given = rval != EXECUTION_FAILURE;
+
+  list = loptend;
+
+  wl = oflags.Dflag ? make_word_list (make_bare_word (DEFAULTCMD), (WORD_LIST *)NULL)
+                   : (oflags.Eflag ? make_word_list (make_bare_word (EMPTYCMD), (WORD_LIST *)NULL) : 0);
+
+  /* -p overrides everything else */
+  if (oflags.pflag || (list == 0 && opt_given == 0))
+    {
+      if (wl)
+       {
+         rval = print_cmd_completions (wl);
+         dispose_words (wl);
+         return rval;
+       }
+      else if (list == 0)
+       {
+         print_all_completions ();
+         return (EXECUTION_SUCCESS);
+       }
+      return (print_cmd_completions (list));
+    }
+
+  /* next, -r overrides everything else. */
+  if (oflags.rflag)
+    {
+      if (wl)
+       {
+         rval = remove_cmd_completions (wl);
+         dispose_words (wl);
+         return rval;
+       }
+      else if (list == 0)
+       {
+         progcomp_flush ();
+         return (EXECUTION_SUCCESS);
+       }
+      return (remove_cmd_completions (list));
+    }
+
+  if (wl == 0 && list == 0 && opt_given)
+    {
+      builtin_usage ();
+      return (EX_USAGE);
+    }
+
+  /* If we get here, we need to build a compspec and add it for each
+     remaining argument. */
+  cs = compspec_create ();
+  cs->actions = acts;
+  cs->options = copts;
+
+  cs->globpat = STRDUP (Garg);
+  cs->words = STRDUP (Warg);
+  cs->prefix = STRDUP (Parg);
+  cs->suffix = STRDUP (Sarg);
+  cs->funcname = STRDUP (Farg);
+  cs->command = STRDUP (Carg);
+  cs->filterpat = STRDUP (Xarg);
+
+  for (rval = EXECUTION_SUCCESS, l = wl ? wl : list ; l; l = l->next)
+    {
+      /* Add CS as the compspec for the specified commands. */
+      if (progcomp_insert (l->word->word, cs) == 0)
+       rval = EXECUTION_FAILURE;
+    }
+
+  dispose_words (wl);
+  return (rval);
+}
+
+static int
+remove_cmd_completions (list)
+     WORD_LIST *list;
+{
+  WORD_LIST *l;
+  int ret;
+
+  for (ret = EXECUTION_SUCCESS, l = list; l; l = l->next)
+    {
+      if (progcomp_remove (l->word->word) == 0)
+       {
+         builtin_error (_("%s: no completion specification"), l->word->word);
+         ret = EXECUTION_FAILURE;
+       }
+    }
+  return ret;
+}
+
+#define SQPRINTARG(a, f) \
+  do { \
+    if (a) \
+      { \
+       x = sh_single_quote (a); \
+       printf ("%s %s ", f, x); \
+       free (x); \
+      } \
+  } while (0)
+
+#define PRINTARG(a, f) \
+  do { \
+    if (a) \
+      printf ("%s %s ", f, a); \
+  } while (0)
+
+#define PRINTOPT(a, f) \
+  do { \
+    if (acts & a) \
+      printf ("%s ", f); \
+  } while (0)
+
+#define PRINTACT(a, f) \
+  do { \
+    if (acts & a) \
+      printf ("-A %s ", f); \
+  } while (0)
+
+#define PRINTCOMPOPT(a, f) \
+  do { \
+    if (copts & a) \
+      printf ("-o %s ", f); \
+  } while (0)
+
+#define XPRINTCOMPOPT(a, f) \
+  do { \
+    if (copts & a) \
+      printf ("-o %s ", f); \
+    else \
+      printf ("+o %s ", f); \
+  } while (0)
+
+static int
+print_one_completion (cmd, cs)
+     char *cmd;
+     COMPSPEC *cs;
+{
+  unsigned long acts, copts;
+  char *x;
+
+  printf ("complete ");
+
+  copts = cs->options;
+
+  /* First, print the -o options. */
+  PRINTCOMPOPT (COPT_BASHDEFAULT, "bashdefault");
+  PRINTCOMPOPT (COPT_DEFAULT, "default");
+  PRINTCOMPOPT (COPT_DIRNAMES, "dirnames");
+  PRINTCOMPOPT (COPT_FILENAMES, "filenames");
+  PRINTCOMPOPT (COPT_NOSPACE, "nospace");
+  PRINTCOMPOPT (COPT_PLUSDIRS, "plusdirs");
+
+  acts = cs->actions;
+
+  /* simple flags next */
+  PRINTOPT (CA_ALIAS, "-a");
+  PRINTOPT (CA_BUILTIN, "-b");
+  PRINTOPT (CA_COMMAND, "-c");
+  PRINTOPT (CA_DIRECTORY, "-d");
+  PRINTOPT (CA_EXPORT, "-e");
+  PRINTOPT (CA_FILE, "-f");
+  PRINTOPT (CA_GROUP, "-g");
+  PRINTOPT (CA_JOB, "-j");
+  PRINTOPT (CA_KEYWORD, "-k");
+  PRINTOPT (CA_SERVICE, "-s");
+  PRINTOPT (CA_USER, "-u");
+  PRINTOPT (CA_VARIABLE, "-v");
+
+  /* now the rest of the actions */
+  PRINTACT (CA_ARRAYVAR, "arrayvar");
+  PRINTACT (CA_BINDING, "binding");
+  PRINTACT (CA_DISABLED, "disabled");
+  PRINTACT (CA_ENABLED, "enabled");
+  PRINTACT (CA_FUNCTION, "function");
+  PRINTACT (CA_HELPTOPIC, "helptopic");
+  PRINTACT (CA_HOSTNAME, "hostname");
+  PRINTACT (CA_RUNNING, "running");
+  PRINTACT (CA_SETOPT, "setopt");
+  PRINTACT (CA_SHOPT, "shopt");
+  PRINTACT (CA_SIGNAL, "signal");
+  PRINTACT (CA_STOPPED, "stopped");
+
+  /* now the rest of the arguments */
+
+  /* arguments that require quoting */
+  SQPRINTARG (cs->globpat, "-G");
+  SQPRINTARG (cs->words, "-W");
+  SQPRINTARG (cs->prefix, "-P");
+  SQPRINTARG (cs->suffix, "-S");
+  SQPRINTARG (cs->filterpat, "-X");
+
+  SQPRINTARG (cs->command, "-C");
+
+  /* simple arguments that don't require quoting */
+  PRINTARG (cs->funcname, "-F");
+
+  if (STREQ (cmd, EMPTYCMD))
+    printf ("-E\n");
+  else if (STREQ (cmd, DEFAULTCMD))
+    printf ("-D\n");
+  else
+    printf ("%s\n", cmd);
+
+  return (0);
+}
+
+static void
+print_compopts (cmd, cs, full)
+     const char *cmd;
+     COMPSPEC *cs;
+     int full;
+{
+  int copts;
+
+  printf ("compopt ");
+  copts = cs->options;
+
+  if (full)
+    {
+      XPRINTCOMPOPT (COPT_BASHDEFAULT, "bashdefault");
+      XPRINTCOMPOPT (COPT_DEFAULT, "default");
+      XPRINTCOMPOPT (COPT_DIRNAMES, "dirnames");
+      XPRINTCOMPOPT (COPT_FILENAMES, "filenames");
+      XPRINTCOMPOPT (COPT_NOSPACE, "nospace");
+      XPRINTCOMPOPT (COPT_PLUSDIRS, "plusdirs");
+    }
+  else
+    {
+      PRINTCOMPOPT (COPT_BASHDEFAULT, "bashdefault");
+      PRINTCOMPOPT (COPT_DEFAULT, "default");
+      PRINTCOMPOPT (COPT_DIRNAMES, "dirnames");
+      PRINTCOMPOPT (COPT_FILENAMES, "filenames");
+      PRINTCOMPOPT (COPT_NOSPACE, "nospace");
+      PRINTCOMPOPT (COPT_PLUSDIRS, "plusdirs");
+    }
+
+  if (STREQ (cmd, EMPTYCMD))
+    printf ("-E\n");
+  else if (STREQ (cmd, DEFAULTCMD))
+    printf ("-D\n");
+  else
+    printf ("%s\n", cmd);
+}
+
+static int
+print_compitem (item)
+     BUCKET_CONTENTS *item;
+{
+  COMPSPEC *cs;
+  char *cmd;
+
+  cmd = item->key;
+  cs = (COMPSPEC *)item->data;
+
+  return (print_one_completion (cmd, cs));
+}
+
+static void
+print_all_completions ()
+{
+  progcomp_walk (print_compitem);
+}
+
+static int
+print_cmd_completions (list)
+     WORD_LIST *list;
+{
+  WORD_LIST *l;
+  COMPSPEC *cs;
+  int ret;
+
+  for (ret = EXECUTION_SUCCESS, l = list; l; l = l->next)
+    {
+      cs = progcomp_search (l->word->word);
+      if (cs)
+       print_one_completion (l->word->word, cs);
+      else
+       {
+         builtin_error (_("%s: no completion specification"), l->word->word);
+         ret = EXECUTION_FAILURE;
+       }
+    }
+
+  return (sh_chkwrite (ret));
+}
+
+$BUILTIN compgen
+$DEPENDS_ON PROGRAMMABLE_COMPLETION
+$FUNCTION compgen_builtin
+$SHORT_DOC compgen [-abcdefgjksuv] [-o option]  [-A action] [-G globpat] [-W wordlist]  [-F function] [-C command] [-X filterpat] [-P prefix] [-S suffix] [word]
+Display possible completions depending on the options.
+
+Intended to be used from within a shell function generating possible
+completions.  If the optional WORD argument is supplied, matches against
+WORD are generated.
+
+Exit Status:
+Returns success unless an invalid option is supplied or an error occurs.
+$END
+
+int
+compgen_builtin (list)
+     WORD_LIST *list;
+{
+  int rval;
+  unsigned long acts, copts;
+  COMPSPEC *cs;
+  STRINGLIST *sl;
+  char *word, **matches;
+
+  if (list == 0)
+    return (EXECUTION_SUCCESS);
+
+  acts = copts = (unsigned long)0L;
+  Garg = Warg = Parg = Sarg = Xarg = Farg = Carg = (char *)NULL;
+  cs = (COMPSPEC *)NULL;
+
+  /* Build the actions from the arguments.  Also sets the [A-Z]arg variables
+     as a side effect if they are supplied as options. */
+  rval = build_actions (list, (struct _optflags *)NULL, &acts, &copts);
+  if (rval == EX_USAGE)
+    return (rval);
+  if (rval == EXECUTION_FAILURE)
+    return (EXECUTION_SUCCESS);
+
+  list = loptend;
+
+  word = (list && list->word) ? list->word->word : "";
+
+  if (Farg)
+    builtin_error (_("warning: -F option may not work as you expect"));
+  if (Carg)
+    builtin_error (_("warning: -C option may not work as you expect"));
+
+  /* If we get here, we need to build a compspec and evaluate it. */
+  cs = compspec_create ();
+  cs->actions = acts;
+  cs->options = copts;
+  cs->refcount = 1;
+
+  cs->globpat = STRDUP (Garg);
+  cs->words = STRDUP (Warg);
+  cs->prefix = STRDUP (Parg);
+  cs->suffix = STRDUP (Sarg);
+  cs->funcname = STRDUP (Farg);
+  cs->command = STRDUP (Carg);
+  cs->filterpat = STRDUP (Xarg);
+
+  rval = EXECUTION_FAILURE;
+  sl = gen_compspec_completions (cs, "compgen", word, 0, 0, 0);
+
+  /* If the compspec wants the bash default completions, temporarily
+     turn off programmable completion and call the bash completion code. */
+  if ((sl == 0 || sl->list_len == 0) && (copts & COPT_BASHDEFAULT))
+    {
+      matches = bash_default_completion (word, 0, 0, 0, 0);
+      sl = completions_to_stringlist (matches);
+      strvec_dispose (matches);
+    }
+
+  /* This isn't perfect, but it's the best we can do, given what readline
+     exports from its set of completion utility functions. */
+  if ((sl == 0 || sl->list_len == 0) && (copts & COPT_DEFAULT))
+    {
+      matches = rl_completion_matches (word, rl_filename_completion_function);
+      sl = completions_to_stringlist (matches);
+      strvec_dispose (matches);
+    }
+
+  if (sl)
+    {
+      if (sl->list && sl->list_len)
+       {
+         rval = EXECUTION_SUCCESS;
+         strlist_print (sl, (char *)NULL);
+       }
+      strlist_dispose (sl);
+    }
+
+  compspec_dispose (cs);
+  return (rval);
+}
+
+$BUILTIN compopt
+$DEPENDS_ON PROGRAMMABLE_COMPLETION
+$FUNCTION compopt_builtin
+$SHORT_DOC compopt [-o|+o option] [-DE] [name ...]
+Modify or display completion options.
+
+Modify the completion options for each NAME, or, if no NAMEs are supplied,
+the completion currently being executed.  If no OPTIONs are given, print
+the completion options for each NAME or the current completion specification.
+
+Options:
+       -o option       Set completion option OPTION for each NAME
+       -D              Change options for the "default" command completion
+       -E              Change options for the "empty" command completion
+
+Using `+o' instead of `-o' turns off the specified option.
+
+Arguments:
+
+Each NAME refers to a command for which a completion specification must
+have previously been defined using the `complete' builtin.  If no NAMEs
+are supplied, compopt must be called by a function currently generating
+completions, and the options for that currently-executing completion
+generator are modified.
+
+Exit Status:
+Returns success unless an invalid option is supplied or NAME does not
+have a completion specification defined.
+$END
+
+int
+compopt_builtin (list)
+     WORD_LIST *list;
+{
+  int opts_on, opts_off, *opts, opt, oind, ret, Dflag, Eflag;
+  WORD_LIST *l, *wl;
+  COMPSPEC *cs;
+
+  opts_on = opts_off = Eflag = Dflag = 0;
+  ret = EXECUTION_SUCCESS;
+
+  reset_internal_getopt ();
+  while ((opt = internal_getopt (list, "+o:DE")) != EOF)
+    {
+      opts = (list_opttype == '-') ? &opts_on : &opts_off;
+
+      switch (opt)
+       {
+       case 'o':
+         oind = find_compopt (list_optarg);
+         if (oind < 0)
+           {
+             sh_invalidoptname (list_optarg);
+             return (EX_USAGE);
+           }
+         *opts |= compopts[oind].optflag;
+         break;
+       case 'D':
+         Dflag = 1;
+         break;
+       case 'E':
+         Eflag = 1;
+         break;
+       default:
+         builtin_usage ();
+         return (EX_USAGE);
+       }
+    }
+  list = loptend;
+
+  wl = Dflag ? make_word_list (make_bare_word (DEFAULTCMD), (WORD_LIST *)NULL)
+            : (Eflag ? make_word_list (make_bare_word (EMPTYCMD), (WORD_LIST *)NULL) : 0);
+
+  if (list == 0 && wl == 0)
+    {
+      if (RL_ISSTATE (RL_STATE_COMPLETING) == 0 || pcomp_curcs == 0)
+       {
+         builtin_error (_("not currently executing completion function"));
+         return (EXECUTION_FAILURE);
+       }
+      cs = pcomp_curcs;
+
+      if (opts_on == 0 && opts_off == 0)
+       {
+         print_compopts (pcomp_curcmd, cs, 1);
+          return (sh_chkwrite (ret));
+       }
+
+      /* Set the compspec options */
+      pcomp_set_compspec_options (cs, opts_on, 1);
+      pcomp_set_compspec_options (cs, opts_off, 0);
+
+      /* And change the readline variables the options control */
+      pcomp_set_readline_variables (opts_on, 1);
+      pcomp_set_readline_variables (opts_off, 0);
+
+      return (ret);
+    }
+
+  for (l = wl ? wl : list; l; l = l->next)
+    {
+      cs = progcomp_search (l->word->word);
+      if (cs == 0)
+       {
+         builtin_error (_("%s: no completion specification"), l->word->word);
+         ret = EXECUTION_FAILURE;
+         continue;
+       }
+      if (opts_on == 0 && opts_off == 0)
+       {
+         print_compopts (l->word->word, cs, 1);
+         continue;                     /* XXX -- fill in later */
+       }
+
+      /* Set the compspec options */
+      pcomp_set_compspec_options (cs, opts_on, 1);
+      pcomp_set_compspec_options (cs, opts_off, 0);
+    }
+
+  return (ret);
+}
index 6724ad178829aa3de1ec2a63291002d472d09cdc..b75a42d43e9e4a01efd38980d77eeb130e898afa 100644 (file)
@@ -276,6 +276,7 @@ list_hashed_filename_targets (list, fmt)
            printf ("%s\t", l->word->word);
          printf ("%s\n", target);
        }
+      free (target);
     }
 
   return (all_found ? EXECUTION_SUCCESS : EXECUTION_FAILURE);
diff --git a/builtins/hash.def~ b/builtins/hash.def~
new file mode 100644 (file)
index 0000000..6724ad1
--- /dev/null
@@ -0,0 +1,282 @@
+This file is hash.def, from which is created hash.c.
+It implements the builtin "hash" in Bash.
+
+Copyright (C) 1987-2010 Free Software Foundation, Inc.
+
+This file is part of GNU Bash, the Bourne Again SHell.
+
+Bash is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Bash is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+
+$PRODUCES hash.c
+
+$BUILTIN hash
+$FUNCTION hash_builtin
+$SHORT_DOC hash [-lr] [-p pathname] [-dt] [name ...]
+Remember or display program locations.
+
+Determine and remember the full pathname of each command NAME.  If
+no arguments are given, information about remembered commands is displayed.
+
+Options:
+  -d           forget the remembered location of each NAME
+  -l           display in a format that may be reused as input
+  -p pathname  use PATHNAME is the full pathname of NAME
+  -r           forget all remembered locations
+  -t           print the remembered location of each NAME, preceding
+               each location with the corresponding NAME if multiple
+               NAMEs are given
+Arguments:
+  NAME         Each NAME is searched for in $PATH and added to the list
+               of remembered commands.
+
+Exit Status:
+Returns success unless NAME is not found or an invalid option is given.
+$END
+
+#include <config.h>
+
+#include <stdio.h>
+
+#include "../bashtypes.h"
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#include <errno.h>
+
+#include "../bashansi.h"
+#include "../bashintl.h"
+
+#include "../shell.h"
+#include "../builtins.h"
+#include "../flags.h"
+#include "../findcmd.h"
+#include "../hashcmd.h"
+#include "common.h"
+#include "bashgetopt.h"
+
+extern int posixly_correct;
+extern int dot_found_in_search;
+extern char *this_command_name;
+
+static int add_hashed_command __P((char *, int));
+static int print_hash_info __P((BUCKET_CONTENTS *));
+static int print_portable_hash_info __P((BUCKET_CONTENTS *));
+static int print_hashed_commands __P((int));
+static int list_hashed_filename_targets __P((WORD_LIST *, int));
+
+/* Print statistics on the current state of hashed commands.  If LIST is
+   not empty, then rehash (or hash in the first place) the specified
+   commands. */
+int
+hash_builtin (list)
+     WORD_LIST *list;
+{
+  int expunge_hash_table, list_targets, list_portably, delete, opt;
+  char *w, *pathname;
+
+  if (hashing_enabled == 0)
+    {
+      builtin_error (_("hashing disabled"));
+      return (EXECUTION_FAILURE);
+    }
+
+  expunge_hash_table = list_targets = list_portably = delete = 0;
+  pathname = (char *)NULL;
+  reset_internal_getopt ();
+  while ((opt = internal_getopt (list, "dlp:rt")) != -1)
+    {
+      switch (opt)
+       {
+       case 'd':
+         delete = 1;
+         break;
+       case 'l':
+         list_portably = 1;
+         break;
+       case 'p':
+         pathname = list_optarg;
+         break;
+       case 'r':
+         expunge_hash_table = 1;
+         break;
+       case 't':
+         list_targets = 1;
+         break;
+       default:
+         builtin_usage ();
+         return (EX_USAGE);
+       }
+    }
+  list = loptend;
+
+  /* hash -t requires at least one argument. */
+  if (list == 0 && list_targets)
+    {
+      sh_needarg ("-t");
+      return (EXECUTION_FAILURE);
+    }
+
+  /* We want hash -r to be silent, but hash -- to print hashing info, so
+     we test expunge_hash_table. */
+  if (list == 0 && expunge_hash_table == 0)
+    {
+      opt = print_hashed_commands (list_portably);
+      if (opt == 0 && posixly_correct == 0)
+       printf (_("%s: hash table empty\n"), this_command_name);
+
+      return (EXECUTION_SUCCESS);
+    }
+
+  if (expunge_hash_table)
+    phash_flush ();
+
+  /* If someone runs `hash -r -t xyz' he will be disappointed. */
+  if (list_targets)
+    return (list_hashed_filename_targets (list, list_portably));
+      
+#if defined (RESTRICTED_SHELL)
+  if (restricted && pathname && strchr (pathname, '/'))
+    {
+      sh_restricted (pathname);
+      return (EXECUTION_FAILURE);
+    }
+#endif
+
+  for (opt = EXECUTION_SUCCESS; list; list = list->next)
+    {
+      /* Add, remove or rehash the specified commands. */
+      w = list->word->word;
+      if (absolute_program (w))
+       continue;
+      else if (pathname)
+       {
+         if (is_directory (pathname))
+           {
+#ifdef EISDIR
+             builtin_error ("%s: %s", pathname, strerror (EISDIR));
+#else
+             builtin_error (_("%s: is a directory"), pathname);
+#endif
+             opt = EXECUTION_FAILURE;
+           }
+         else
+           phash_insert (w, pathname, 0, 0);
+       }
+      else if (delete)
+       {
+         if (phash_remove (w))
+           {
+             sh_notfound (w);
+             opt = EXECUTION_FAILURE;
+           }
+       }
+      else if (add_hashed_command (w, 0))
+       opt = EXECUTION_FAILURE;
+    }
+
+  fflush (stdout);
+  return (opt);
+}
+
+static int
+add_hashed_command (w, quiet)
+     char *w;
+     int quiet;
+{
+  int rv;
+  char *full_path;
+
+  rv = 0;
+  if (find_function (w) == 0 && find_shell_builtin (w) == 0)
+    {
+      phash_remove (w);
+      full_path = find_user_command (w);
+      if (full_path && executable_file (full_path))
+       phash_insert (w, full_path, dot_found_in_search, 0);
+      else
+       {
+         if (quiet == 0)
+           sh_notfound (w);
+         rv++;
+       }
+      FREE (full_path);
+    }
+  return (rv);
+}
+
+/* Print information about current hashed info. */
+static int
+print_hash_info (item)
+     BUCKET_CONTENTS *item;
+{
+  printf ("%4d\t%s\n", item->times_found, pathdata(item)->path);
+  return 0;
+}
+
+static int
+print_portable_hash_info (item)
+     BUCKET_CONTENTS *item;
+{
+  printf ("builtin hash -p %s %s\n", pathdata(item)->path, item->key);
+  return 0;
+}
+
+static int
+print_hashed_commands (fmt)
+     int fmt;
+{
+  if (hashed_filenames == 0 || HASH_ENTRIES (hashed_filenames) == 0)
+    return (0);
+
+  if (fmt == 0)
+    printf (_("hits\tcommand\n"));
+  hash_walk (hashed_filenames, fmt ? print_portable_hash_info : print_hash_info);
+  return (1);
+}
+
+static int
+list_hashed_filename_targets (list, fmt)
+     WORD_LIST *list;
+     int fmt;
+{
+  int all_found, multiple;
+  char *target;
+  WORD_LIST *l;
+
+  all_found = 1;
+  multiple = list->next != 0;
+
+  for (l = list; l; l = l->next)
+    {
+      target = phash_search (l->word->word);
+      if (target == 0)
+       {
+         all_found = 0;
+         sh_notfound (l->word->word);
+         continue;
+       }
+      if (fmt)
+       printf ("builtin hash -p %s %s\n", target, l->word->word);
+      else
+       {
+         if (multiple)
+           printf ("%s\t", l->word->word);
+         printf ("%s\n", target);
+       }
+    }
+
+  return (all_found ? EXECUTION_SUCCESS : EXECUTION_FAILURE);
+}
index 21cdf275578e07e031646a48af82d74cb8a79d02..91eb839896fb85c7054d3a0ee365634788562e5a 100644 (file)
@@ -1,7 +1,7 @@
 This file is help.def, from which is created help.c.
 It implements the builtin "help" in Bash.
 
-Copyright (C) 1987-2009 Free Software Foundation, Inc.
+Copyright (C) 1987-2011 Free Software Foundation, Inc.
 
 This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -209,7 +209,7 @@ show_longdoc (i)
       zcatfd (fd, 1, doc[0]);
       close (fd);
     }
-  else
+  else if (doc)
     for (j = 0; doc[j]; j++)
       printf ("%*s%s\n", BASE_INDENT, " ", _(doc[j]));
 }
diff --git a/builtins/help.def~ b/builtins/help.def~
new file mode 100644 (file)
index 0000000..21cdf27
--- /dev/null
@@ -0,0 +1,385 @@
+This file is help.def, from which is created help.c.
+It implements the builtin "help" 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 help.c
+
+$BUILTIN help
+$FUNCTION help_builtin
+$DEPENDS_ON HELP_BUILTIN
+$SHORT_DOC help [-dms] [pattern ...]
+Display information about builtin commands.
+
+Displays brief summaries of builtin commands.  If PATTERN is
+specified, gives detailed help on all commands matching PATTERN,
+otherwise the list of help topics is printed.
+
+Options:
+  -d   output short description for each topic
+  -m   display usage in pseudo-manpage format
+  -s   output only a short usage synopsis for each topic matching
+       PATTERN
+
+Arguments:
+  PATTERN      Pattern specifiying a help topic
+
+Exit Status:
+Returns success unless PATTERN is not found or an invalid option is given.
+$END
+
+#include <config.h>
+
+#if defined (HELP_BUILTIN)
+#include <stdio.h>
+
+#if defined (HAVE_UNISTD_H)
+#  ifdef _MINIX
+#    include <sys/types.h>
+#  endif
+#  include <unistd.h>
+#endif
+
+#include <errno.h>
+
+#include <filecntl.h>
+
+#include "../bashintl.h"
+
+#include "../shell.h"
+#include "../builtins.h"
+#include "../pathexp.h"
+#include "common.h"
+#include "bashgetopt.h"
+
+#include <glob/strmatch.h>
+#include <glob/glob.h>
+
+#ifndef errno
+extern int errno;
+#endif
+
+extern const char * const bash_copyright;
+extern const char * const bash_license;
+
+static void show_builtin_command_help __P((void));
+static int open_helpfile __P((char *));
+static void show_desc __P((char *, int));
+static void show_manpage __P((char *, int));
+static void show_longdoc __P((int));
+
+/* Print out a list of the known functions in the shell, and what they do.
+   If LIST is supplied, print out the list which matches for each pattern
+   specified. */
+int
+help_builtin (list)
+     WORD_LIST *list;
+{
+  register int i;
+  char *pattern, *name;
+  int plen, match_found, sflag, dflag, mflag;
+
+  dflag = sflag = mflag = 0;
+  reset_internal_getopt ();
+  while ((i = internal_getopt (list, "dms")) != -1)
+    {
+      switch (i)
+       {
+       case 'd':
+         dflag = 1;
+         break;
+       case 'm':
+         mflag = 1;
+         break;
+       case 's':
+         sflag = 1;
+         break;
+       default:
+         builtin_usage ();
+         return (EX_USAGE);
+       }
+    }
+  list = loptend;
+
+  if (list == 0)
+    {
+      show_shell_version (0);
+      show_builtin_command_help ();
+      return (EXECUTION_SUCCESS);
+    }
+
+  /* We should consider making `help bash' do something. */
+
+  if (glob_pattern_p (list->word->word))
+    {
+      printf (ngettext ("Shell commands matching keyword `", "Shell commands matching keywords `", (list->next ? 2 : 1)));
+      print_word_list (list, ", ");
+      printf ("'\n\n");
+    }
+
+  for (match_found = 0, pattern = ""; list; list = list->next)
+    {
+      pattern = list->word->word;
+      plen = strlen (pattern);
+
+      for (i = 0; name = shell_builtins[i].name; i++)
+       {
+         QUIT;
+         if ((strcmp (pattern, name) == 0) ||
+             (strmatch (pattern, name, FNMATCH_EXTFLAG) != FNM_NOMATCH))
+           {
+             match_found++;
+             if (dflag)
+               {
+                 show_desc (name, i);
+                 continue;
+               }
+             else if (mflag)
+               {
+                 show_manpage (name, i);
+                 continue;
+               }
+
+             printf ("%s: %s\n", name, _(shell_builtins[i].short_doc));
+
+             if (sflag == 0)
+               show_longdoc (i);
+           }
+       }
+    }
+
+  if (match_found == 0)
+    {
+      builtin_error (_("no help topics match `%s'.  Try `help help' or `man -k %s' or `info %s'."), pattern, pattern, pattern);
+      return (EXECUTION_FAILURE);
+    }
+
+  fflush (stdout);
+  return (EXECUTION_SUCCESS);
+}
+
+static int
+open_helpfile (name)
+     char *name;
+{
+  int fd;
+
+  fd = open (name, O_RDONLY);
+  if (fd == -1)
+    {
+      builtin_error (_("%s: cannot open: %s"), name, strerror (errno));
+      return -1;
+    }
+  return fd;
+}
+
+/* By convention, enforced by mkbuiltins.c, if separate help files are being
+   used, the long_doc array contains one string -- the full pathname of the
+   help file for this builtin.  */
+static void
+show_longdoc (i)
+     int i;
+{
+  register int j;
+  char * const *doc;
+  int fd;
+
+  doc = shell_builtins[i].long_doc;
+
+  if (doc && doc[0] && *doc[0] == '/' && doc[1] == (char *)NULL)
+    {
+      fd = open_helpfile (doc[0]);
+      if (fd < 0)
+       return;
+      zcatfd (fd, 1, doc[0]);
+      close (fd);
+    }
+  else
+    for (j = 0; doc[j]; j++)
+      printf ("%*s%s\n", BASE_INDENT, " ", _(doc[j]));
+}
+
+static void
+show_desc (name, i)
+     char *name;
+     int i;
+{
+  register int j;
+  char **doc, *line;
+  int fd, usefile;
+
+  doc = (char **)shell_builtins[i].long_doc;
+
+  usefile = (doc && doc[0] && *doc[0] == '/' && doc[1] == (char *)NULL);
+  if (usefile)
+    {
+      fd = open_helpfile (doc[0]);
+      if (fd < 0)
+       return;
+      zmapfd (fd, &line, doc[0]);
+      close (fd);
+    }
+  else
+    line = doc ? doc[0] : (char *)NULL;
+
+  printf ("%s - ", name);
+  for (j = 0; line && line[j]; j++)
+    {
+      putchar (line[j]);
+      if (line[j] == '\n')
+       break;
+    }
+  
+  fflush (stdout);
+
+  if (usefile)
+    free (line);
+}
+
+/* Print builtin help in pseudo-manpage format. */
+static void
+show_manpage (name, i)
+     char *name;
+     int i;
+{
+  register int j;
+  char **doc, *line;
+  int fd, usefile;
+
+  doc = (char **)shell_builtins[i].long_doc;
+
+  usefile = (doc && doc[0] && *doc[0] == '/' && doc[1] == (char *)NULL);
+  if (usefile)
+    {
+      fd = open_helpfile (doc[0]);
+      if (fd < 0)
+       return;
+      zmapfd (fd, &line, doc[0]);
+      close (fd);
+    }
+  else
+    line = doc ? _(doc[0]) : (char *)NULL;
+
+  /* NAME */
+  printf ("NAME\n");
+  printf ("%*s%s - ", BASE_INDENT, " ", name);
+  for (j = 0; line && line[j]; j++)
+    {
+      putchar (line[j]);
+      if (line[j] == '\n')
+       break;
+    }
+  printf ("\n");
+
+  /* SYNOPSIS */
+  printf ("SYNOPSIS\n");
+  printf ("%*s%s\n\n", BASE_INDENT, " ", _(shell_builtins[i].short_doc));
+
+  /* DESCRIPTION */
+  printf ("DESCRIPTION\n");
+  if (usefile == 0)
+    {
+      for (j = 0; doc[j]; j++)
+        printf ("%*s%s\n", BASE_INDENT, " ", _(doc[j]));
+    }
+  else
+    {
+      for (j = 0; line && line[j]; j++)
+       {
+         putchar (line[j]);
+         if (line[j] == '\n')
+           printf ("%*s", BASE_INDENT, " ");
+       }
+    }
+  putchar ('\n');
+
+  /* SEE ALSO */
+  printf ("SEE ALSO\n");
+  printf ("%*sbash(1)\n\n", BASE_INDENT, " ");
+
+  /* IMPLEMENTATION */
+  printf ("IMPLEMENTATION\n");
+  printf ("%*s", BASE_INDENT, " ");
+  show_shell_version (0);
+  printf ("%*s", BASE_INDENT, " ");
+  printf ("%s\n", _(bash_copyright));
+  printf ("%*s", BASE_INDENT, " ");
+  printf ("%s\n", _(bash_license));
+
+  fflush (stdout);
+  if (usefile)
+    free (line);
+}
+
+static void
+show_builtin_command_help ()
+{
+  int i, j;
+  int height, width;
+  char *t, blurb[128];
+
+  printf (
+_("These shell commands are defined internally.  Type `help' to see this list.\n\
+Type `help name' to find out more about the function `name'.\n\
+Use `info bash' to find out more about the shell in general.\n\
+Use `man -k' or `info' to find out more about commands not in this list.\n\
+\n\
+A star (*) next to a name means that the command is disabled.\n\
+\n"));
+
+  t = get_string_value ("COLUMNS");
+  width = (t && *t) ? atoi (t) : 80;
+  if (width <= 0)
+    width = 80;
+
+  width /= 2;
+  if (width > sizeof (blurb))
+    width = sizeof (blurb);
+  if (width <= 3)
+    width = 40;
+  height = (num_shell_builtins + 1) / 2;       /* number of rows */
+
+  for (i = 0; i < height; i++)
+    {
+      QUIT;
+
+      /* first column */
+      blurb[0] = (shell_builtins[i].flags & BUILTIN_ENABLED) ? ' ' : '*';
+      strncpy (blurb + 1, _(shell_builtins[i].short_doc), width - 2);
+      blurb[width - 2] = '>';          /* indicate truncation */
+      blurb[width - 1] = '\0';
+      printf ("%s", blurb);
+      if (((i << 1) >= num_shell_builtins) || (i+height >= num_shell_builtins))
+       {
+         printf ("\n");
+         break;
+       }
+
+      /* two spaces */
+      for (j = strlen (blurb); j < width; j++)
+        putc (' ', stdout);
+
+      /* second column */
+      blurb[0] = (shell_builtins[i+height].flags & BUILTIN_ENABLED) ? ' ' : '*';
+      strncpy (blurb + 1, _(shell_builtins[i+height].short_doc), width - 3);
+      blurb[width - 3] = '>';          /* indicate truncation */
+      blurb[width - 2] = '\0';
+      printf ("%s\n", blurb);
+    }
+}
+#endif /* HELP_BUILTIN */
index c05cf30f567d6d8f6904e7fb90c70a27ed6866dc..95648ddf0eeb64d0ec08e4b62076a6c8b1f2ec68 100644 (file)
@@ -1,7 +1,7 @@
 /* mkbuiltins.c - Create builtins.c, builtext.h, and builtdoc.c from
    a single source file called builtins.def. */
 
-/* Copyright (C) 1987-2010 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2011 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -519,6 +519,7 @@ extract_info (filename, structfile, externfile)
   if (nr == 0)
     {
       fprintf (stderr, "mkbuiltins: %s: skipping zero-length file\n", filename);
+      free (buffer);
       return;
     }
 
@@ -1407,7 +1408,7 @@ write_documentation (stream, documentation, indentation, flags)
 
   base_indent = (string_array && single_longdoc_strings && filename_p == 0) ? BASE_INDENT : 0;
 
-  for (i = 0, texinfo = (flags & TEXINFO); line = documentation[i]; i++)
+  for (i = 0, texinfo = (flags & TEXINFO); documentation && (line = documentation[i]); i++)
     {
       /* Allow #ifdef's to be written out verbatim, but don't put them into
         separate help files. */
diff --git a/builtins/mkbuiltins.c~ b/builtins/mkbuiltins.c~
new file mode 100644 (file)
index 0000000..ad72cdc
--- /dev/null
@@ -0,0 +1,1593 @@
+/* mkbuiltins.c - Create builtins.c, builtext.h, and builtdoc.c from
+   a single source file called builtins.def. */
+
+/* Copyright (C) 1987-2011 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 (CROSS_COMPILING) 
+#  include <config.h>
+#else  /* CROSS_COMPILING */
+/* A conservative set of defines based on POSIX/SUS3/XPG6 */
+#  define HAVE_UNISTD_H
+#  define HAVE_STRING_H
+#  define HAVE_STDLIB_H
+
+#  define HAVE_RENAME
+#endif /* CROSS_COMPILING */
+
+#if defined (HAVE_UNISTD_H)
+#  ifdef _MINIX
+#    include <sys/types.h>
+#  endif
+#  include <unistd.h>
+#endif
+
+#ifndef _MINIX
+#  include "../bashtypes.h"
+#  if defined (HAVE_SYS_FILE_H)
+#    include <sys/file.h>
+#  endif
+#endif
+
+#include "posixstat.h"
+#include "filecntl.h"
+
+#include "../bashansi.h"
+#include <stdio.h>
+#include <errno.h>
+
+#include "stdc.h"
+
+#define DOCFILE "builtins.texi"
+
+#ifndef errno
+extern int errno;
+#endif
+
+static char *xmalloc (), *xrealloc ();
+
+#if !defined (__STDC__) && !defined (strcpy)
+extern char *strcpy ();
+#endif /* !__STDC__ && !strcpy */
+
+#define savestring(x) strcpy (xmalloc (1 + strlen (x)), (x))
+#define whitespace(c) (((c) == ' ') || ((c) == '\t'))
+
+/* Flag values that builtins can have. */
+#define BUILTIN_FLAG_SPECIAL   0x01
+#define BUILTIN_FLAG_ASSIGNMENT 0x02
+#define BUILTIN_FLAG_POSIX_BUILTIN 0x04
+
+#define BASE_INDENT    4
+
+/* If this stream descriptor is non-zero, then write
+   texinfo documentation to it. */
+FILE *documentation_file = (FILE *)NULL;
+
+/* Non-zero means to only produce documentation. */
+int only_documentation = 0;
+
+/* Non-zero means to not do any productions. */
+int inhibit_production = 0;
+
+/* Non-zero means to produce separate help files for each builtin, named by
+   the builtin name, in `./helpfiles'. */
+int separate_helpfiles = 0;
+
+/* Non-zero means to create single C strings for each `longdoc', with
+   embedded newlines, for ease of translation. */
+int single_longdoc_strings = 1;
+
+/* The name of a directory into which the separate external help files will
+   eventually be installed. */
+char *helpfile_directory;
+
+/* The name of a directory to precede the filename when reporting
+   errors. */
+char *error_directory = (char *)NULL;
+
+/* The name of the structure file. */
+char *struct_filename = (char *)NULL;
+
+/* The name of the external declaration file. */
+char *extern_filename = (char *)NULL;
+
+/* Here is a structure for manipulating arrays of data. */
+typedef struct {
+  int size;            /* Number of slots allocated to array. */
+  int sindex;          /* Current location in array. */
+  int width;           /* Size of each element. */
+  int growth_rate;     /* How fast to grow. */
+  char **array;                /* The array itself. */
+} ARRAY;
+
+/* Here is a structure defining a single BUILTIN. */
+typedef struct {
+  char *name;          /* The name of this builtin. */
+  char *function;      /* The name of the function to call. */
+  char *shortdoc;      /* The short documentation for this builtin. */
+  char *docname;       /* Possible name for documentation string. */
+  ARRAY *longdoc;      /* The long documentation for this builtin. */
+  ARRAY *dependencies; /* Null terminated array of #define names. */
+  int flags;           /* Flags for this builtin. */
+} BUILTIN_DESC;
+
+/* Here is a structure which defines a DEF file. */
+typedef struct {
+  char *filename;      /* The name of the input def file. */
+  ARRAY *lines;                /* The contents of the file. */
+  int line_number;     /* The current line number. */
+  char *production;    /* The name of the production file. */
+  FILE *output;                /* Open file stream for PRODUCTION. */
+  ARRAY *builtins;     /* Null terminated array of BUILTIN_DESC *. */
+} DEF_FILE;
+
+/* The array of all builtins encountered during execution of this code. */
+ARRAY *saved_builtins = (ARRAY *)NULL;
+
+/* The Posix.2 so-called `special' builtins. */
+char *special_builtins[] =
+{
+  ":", ".", "source", "break", "continue", "eval", "exec", "exit",
+  "export", "readonly", "return", "set", "shift", "times", "trap", "unset",
+  (char *)NULL
+};
+
+/* The builtin commands that take assignment statements as arguments. */
+char *assignment_builtins[] =
+{
+  "alias", "declare", "export", "local", "readonly", "typeset",
+  (char *)NULL
+};
+
+/* The builtin commands that are special to the POSIX search order. */
+char *posix_builtins[] =
+{
+  "alias", "bg", "cd", "command", "false", "fc", "fg", "getopts", "jobs",
+  "kill", "newgrp", "pwd", "read", "true", "umask", "unalias", "wait",
+  (char *)NULL
+};
+
+/* Forward declarations. */
+static int is_special_builtin ();
+static int is_assignment_builtin ();
+static int is_posix_builtin ();
+
+#if !defined (HAVE_RENAME)
+static int rename ();
+#endif
+
+void extract_info ();
+
+void file_error ();
+void line_error ();
+
+void write_file_headers ();
+void write_file_footers ();
+void write_ifdefs ();
+void write_endifs ();
+void write_documentation ();
+void write_longdocs ();
+void write_builtins ();
+
+int write_helpfiles ();
+
+void free_defs ();
+void add_documentation ();
+
+void must_be_building ();
+void remove_trailing_whitespace ();
+
+#define document_name(b)       ((b)->docname ? (b)->docname : (b)->name)
+
+\f
+/* For each file mentioned on the command line, process it and
+   write the information to STRUCTFILE and EXTERNFILE, while
+   creating the production file if neccessary. */
+int
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int arg_index = 1;
+  FILE *structfile, *externfile;
+  char *documentation_filename, *temp_struct_filename;
+
+  structfile = externfile = (FILE *)NULL;
+  documentation_filename = DOCFILE;
+  temp_struct_filename = (char *)NULL;
+
+  while (arg_index < argc && argv[arg_index][0] == '-')
+    {
+      char *arg = argv[arg_index++];
+
+      if (strcmp (arg, "-externfile") == 0)
+       extern_filename = argv[arg_index++];
+      else if (strcmp (arg, "-structfile") == 0)
+       struct_filename = argv[arg_index++];
+      else if (strcmp (arg, "-noproduction") == 0)
+       inhibit_production = 1;
+      else if (strcmp (arg, "-document") == 0)
+       documentation_file = fopen (documentation_filename, "w");
+      else if (strcmp (arg, "-D") == 0)
+       {
+         int len;
+
+         if (error_directory)
+           free (error_directory);
+
+         error_directory = xmalloc (2 + strlen (argv[arg_index]));
+         strcpy (error_directory, argv[arg_index]);
+         len = strlen (error_directory);
+
+         if (len && error_directory[len - 1] != '/')
+           strcat (error_directory, "/");
+
+         arg_index++;
+       }
+      else if (strcmp (arg, "-documentonly") == 0)
+       {
+         only_documentation = 1;
+         documentation_file = fopen (documentation_filename, "w");
+       }
+      else if (strcmp (arg, "-H") == 0)
+        {
+         separate_helpfiles = 1;
+         helpfile_directory = argv[arg_index++];
+        }
+      else if (strcmp (arg, "-S") == 0)
+       single_longdoc_strings = 0;
+      else
+       {
+         fprintf (stderr, "%s: Unknown flag %s.\n", argv[0], arg);
+         exit (2);
+       }
+    }
+
+  /* If there are no files to process, just quit now. */
+  if (arg_index == argc)
+    exit (0);
+
+  if (!only_documentation)
+    {
+      /* Open the files. */
+      if (struct_filename)
+       {
+         temp_struct_filename = xmalloc (15);
+         sprintf (temp_struct_filename, "mk-%ld", (long) getpid ());
+         structfile = fopen (temp_struct_filename, "w");
+
+         if (!structfile)
+           file_error (temp_struct_filename);
+       }
+
+      if (extern_filename)
+       {
+         externfile = fopen (extern_filename, "w");
+
+         if (!externfile)
+           file_error (extern_filename);
+       }
+
+      /* Write out the headers. */
+      write_file_headers (structfile, externfile);
+    }
+
+  if (documentation_file)
+    {
+      fprintf (documentation_file, "@c Table of builtins created with %s.\n",
+              argv[0]);
+      fprintf (documentation_file, "@ftable @asis\n");
+    }
+
+  /* Process the .def files. */
+  while (arg_index < argc)
+    {
+      register char *arg;
+
+      arg = argv[arg_index++];
+
+      extract_info (arg, structfile, externfile);
+    }
+
+  /* Close the files. */
+  if (!only_documentation)
+    {
+      /* Write the footers. */
+      write_file_footers (structfile, externfile);
+
+      if (structfile)
+       {
+         write_longdocs (structfile, saved_builtins);
+         fclose (structfile);
+         rename (temp_struct_filename, struct_filename);
+       }
+
+      if (externfile)
+       fclose (externfile);
+    }
+
+  if (separate_helpfiles)
+    {
+      write_helpfiles (saved_builtins);
+    }
+
+  if (documentation_file)
+    {
+      fprintf (documentation_file, "@end ftable\n");
+      fclose (documentation_file);
+    }
+
+  exit (0);
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*               Array Functions and Manipulators                  */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Make a new array, and return a pointer to it.  The array will
+   contain elements of size WIDTH, and is initialized to no elements. */
+ARRAY *
+array_create (width)
+     int width;
+{
+  ARRAY *array;
+
+  array = (ARRAY *)xmalloc (sizeof (ARRAY));
+  array->size = 0;
+  array->sindex = 0;
+  array->width = width;
+
+  /* Default to increasing size in units of 20. */
+  array->growth_rate = 20;
+
+  array->array = (char **)NULL;
+
+  return (array);
+}
+
+/* Copy the array of strings in ARRAY. */
+ARRAY *
+copy_string_array (array)
+     ARRAY *array;
+{
+  register int i;
+  ARRAY *copy;
+
+  if (!array)
+    return (ARRAY *)NULL;
+
+  copy = array_create (sizeof (char *));
+
+  copy->size = array->size;
+  copy->sindex = array->sindex;
+  copy->width = array->width;
+
+  copy->array = (char **)xmalloc ((1 + array->sindex) * sizeof (char *));
+  
+  for (i = 0; i < array->sindex; i++)
+    copy->array[i] = savestring (array->array[i]);
+
+  copy->array[i] = (char *)NULL;
+
+  return (copy);
+}
+
+/* Add ELEMENT to ARRAY, growing the array if neccessary. */
+void
+array_add (element, array)
+     char *element;
+     ARRAY *array;
+{
+  if (array->sindex + 2 > array->size)
+    array->array = (char **)xrealloc
+      (array->array, (array->size += array->growth_rate) * array->width);
+
+  array->array[array->sindex++] = element;
+  array->array[array->sindex] = (char *)NULL;
+}
+
+/* Free an allocated array and data pointer. */
+void
+array_free (array)
+     ARRAY *array;
+{
+  if (array->array)
+    free (array->array);
+
+  free (array);
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                    Processing a DEF File                        */
+/*                                                                 */
+/* **************************************************************** */
+
+/* The definition of a function. */
+typedef int Function ();
+typedef int mk_handler_func_t __P((char *, DEF_FILE *, char *));
+
+/* Structure handles processor directives. */
+typedef struct {
+  char *directive;
+  mk_handler_func_t *function;
+} HANDLER_ENTRY;
+
+extern int builtin_handler __P((char *, DEF_FILE *, char *));
+extern int function_handler __P((char *, DEF_FILE *, char *));
+extern int short_doc_handler __P((char *, DEF_FILE *, char *));
+extern int comment_handler __P((char *, DEF_FILE *, char *));
+extern int depends_on_handler __P((char *, DEF_FILE *, char *));
+extern int produces_handler __P((char *, DEF_FILE *, char *));
+extern int end_handler __P((char *, DEF_FILE *, char *));
+extern int docname_handler __P((char *, DEF_FILE *, char *));
+
+HANDLER_ENTRY handlers[] = {
+  { "BUILTIN", builtin_handler },
+  { "DOCNAME", docname_handler },
+  { "FUNCTION", function_handler },
+  { "SHORT_DOC", short_doc_handler },
+  { "$", comment_handler },
+  { "COMMENT", comment_handler },
+  { "DEPENDS_ON", depends_on_handler },
+  { "PRODUCES", produces_handler },
+  { "END", end_handler },
+  { (char *)NULL, (mk_handler_func_t *)NULL }
+};
+
+/* Return the entry in the table of handlers for NAME. */
+HANDLER_ENTRY *
+find_directive (directive)
+     char *directive;
+{
+  register int i;
+
+  for (i = 0; handlers[i].directive; i++)
+    if (strcmp (handlers[i].directive, directive) == 0)
+      return (&handlers[i]);
+
+  return ((HANDLER_ENTRY *)NULL);
+}
+
+/* Non-zero indicates that a $BUILTIN has been seen, but not
+   the corresponding $END. */
+static int building_builtin = 0;
+
+/* Non-zero means to output cpp line and file information before
+   printing the current line to the production file. */
+int output_cpp_line_info = 0;
+
+/* The main function of this program.  Read FILENAME and act on what is
+   found.  Lines not starting with a dollar sign are copied to the
+   $PRODUCES target, if one is present.  Lines starting with a dollar sign
+   are directives to this program, specifying the name of the builtin, the
+   function to call, the short documentation and the long documentation
+   strings.  FILENAME can contain multiple $BUILTINs, but only one $PRODUCES
+   target.  After the file has been processed, write out the names of
+   builtins found in each $BUILTIN.  Plain text found before the $PRODUCES
+   is ignored, as is "$$ comment text". */
+void
+extract_info (filename, structfile, externfile)
+     char *filename;
+     FILE *structfile, *externfile;
+{
+  register int i;
+  DEF_FILE *defs;
+  struct stat finfo;
+  size_t file_size;
+  char *buffer, *line;
+  int fd, nr;
+
+  if (stat (filename, &finfo) == -1)
+    file_error (filename);
+
+  fd = open (filename, O_RDONLY, 0666);
+
+  if (fd == -1)
+    file_error (filename);
+
+  file_size = (size_t)finfo.st_size;
+  buffer = xmalloc (1 + file_size);
+
+  if ((nr = read (fd, buffer, file_size)) < 0)
+    file_error (filename);
+
+  /* This is needed on WIN32, and does not hurt on Unix. */
+  if (nr < file_size)
+    file_size = nr;
+
+  close (fd);
+
+  if (nr == 0)
+    {
+      fprintf (stderr, "mkbuiltins: %s: skipping zero-length file\n", filename);
+      free (buffer);
+      return;
+    }
+
+  /* Create and fill in the initial structure describing this file. */
+  defs = (DEF_FILE *)xmalloc (sizeof (DEF_FILE));
+  defs->filename = filename;
+  defs->lines = array_create (sizeof (char *));
+  defs->line_number = 0;
+  defs->production = (char *)NULL;
+  defs->output = (FILE *)NULL;
+  defs->builtins = (ARRAY *)NULL;
+
+  /* Build the array of lines. */
+  i = 0;
+  while (i < file_size)
+    {
+      array_add (&buffer[i], defs->lines);
+
+      while (i < file_size && buffer[i] != '\n')
+       i++;
+      buffer[i++] = '\0';
+    }
+
+  /* Begin processing the input file.  We don't write any output
+     until we have a file to write output to. */
+  output_cpp_line_info = 1;
+
+  /* Process each line in the array. */
+  for (i = 0; line = defs->lines->array[i]; i++)
+    {
+      defs->line_number = i;
+
+      if (*line == '$')
+       {
+         register int j;
+         char *directive;
+         HANDLER_ENTRY *handler;
+
+         /* Isolate the directive. */
+         for (j = 0; line[j] && !whitespace (line[j]); j++);
+
+         directive = xmalloc (j);
+         strncpy (directive, line + 1, j - 1);
+         directive[j -1] = '\0';
+
+         /* Get the function handler and call it. */
+         handler = find_directive (directive);
+
+         if (!handler)
+           {
+             line_error (defs, "Unknown directive `%s'", directive);
+             free (directive);
+             continue;
+           }
+         else
+           {
+             /* Advance to the first non-whitespace character. */
+             while (whitespace (line[j]))
+               j++;
+
+             /* Call the directive handler with the FILE, and ARGS. */
+             (*(handler->function)) (directive, defs, line + j);
+           }
+         free (directive);
+       }
+      else
+       {
+         if (building_builtin)
+           add_documentation (defs, line);
+         else if (defs->output)
+           {
+             if (output_cpp_line_info)
+               {
+                 /* If we're handed an absolute pathname, don't prepend
+                    the directory name. */
+                 if (defs->filename[0] == '/')
+                   fprintf (defs->output, "#line %d \"%s\"\n",
+                            defs->line_number + 1, defs->filename);
+                 else
+                   fprintf (defs->output, "#line %d \"%s%s\"\n",
+                            defs->line_number + 1,
+                            error_directory ? error_directory : "./",
+                            defs->filename);
+                 output_cpp_line_info = 0;
+               }
+
+             fprintf (defs->output, "%s\n", line);
+           }
+       }
+    }
+
+  /* Close the production file. */
+  if (defs->output)
+    fclose (defs->output);
+
+  /* The file has been processed.  Write the accumulated builtins to
+     the builtins.c file, and write the extern definitions to the
+     builtext.h file. */
+  write_builtins (defs, structfile, externfile);
+
+  free (buffer);
+  free_defs (defs);
+}
+
+#define free_safely(x) if (x) free (x)
+
+static void
+free_builtin (builtin)
+     BUILTIN_DESC *builtin;
+{
+  register int i;
+
+  free_safely (builtin->name);
+  free_safely (builtin->function);
+  free_safely (builtin->shortdoc);
+  free_safely (builtin->docname);
+
+  if (builtin->longdoc)
+    array_free (builtin->longdoc);
+
+  if (builtin->dependencies)
+    {
+      for (i = 0; builtin->dependencies->array[i]; i++)
+       free (builtin->dependencies->array[i]);
+      array_free (builtin->dependencies);
+    }
+}
+
+/* Free all of the memory allocated to a DEF_FILE. */
+void
+free_defs (defs)
+     DEF_FILE *defs;
+{
+  register int i;
+  register BUILTIN_DESC *builtin;
+
+  if (defs->production)
+    free (defs->production);
+
+  if (defs->lines)
+    array_free (defs->lines);
+
+  if (defs->builtins)
+    {
+      for (i = 0; builtin = (BUILTIN_DESC *)defs->builtins->array[i]; i++)
+       {
+         free_builtin (builtin);
+         free (builtin);
+       }
+      array_free (defs->builtins);
+    }
+  free (defs);
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                  The Handler Functions Themselves               */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Strip surrounding whitespace from STRING, and
+   return a pointer to the start of it. */
+char *
+strip_whitespace (string)
+     char *string;
+{
+  while (whitespace (*string))
+      string++;
+
+  remove_trailing_whitespace (string);
+  return (string);
+}
+
+/* Remove only the trailing whitespace from STRING. */
+void
+remove_trailing_whitespace (string)
+     char *string;
+{
+  register int i;
+
+  i = strlen (string) - 1;
+
+  while (i > 0 && whitespace (string[i]))
+    i--;
+
+  string[++i] = '\0';
+}
+
+/* Ensure that there is a argument in STRING and return it.
+   FOR_WHOM is the name of the directive which needs the argument.
+   DEFS is the DEF_FILE in which the directive is found.
+   If there is no argument, produce an error. */
+char *
+get_arg (for_whom, defs, string)
+     char *for_whom, *string;
+     DEF_FILE *defs;
+{
+  char *new;
+
+  new = strip_whitespace (string);
+
+  if (!*new)
+    line_error (defs, "%s requires an argument", for_whom);
+
+  return (savestring (new));
+}
+
+/* Error if not building a builtin. */
+void
+must_be_building (directive, defs)
+     char *directive;
+     DEF_FILE *defs;
+{
+  if (!building_builtin)
+    line_error (defs, "%s must be inside of a $BUILTIN block", directive);
+}
+
+/* Return the current builtin. */
+BUILTIN_DESC *
+current_builtin (directive, defs)
+     char *directive;
+     DEF_FILE *defs;
+{
+  must_be_building (directive, defs);
+  if (defs->builtins)
+    return ((BUILTIN_DESC *)defs->builtins->array[defs->builtins->sindex - 1]);
+  else
+    return ((BUILTIN_DESC *)NULL);
+}
+
+/* Add LINE to the long documentation for the current builtin.
+   Ignore blank lines until the first non-blank line has been seen. */
+void
+add_documentation (defs, line)
+     DEF_FILE *defs;
+     char *line;
+{
+  register BUILTIN_DESC *builtin;
+
+  builtin = current_builtin ("(implied LONGDOC)", defs);
+
+  remove_trailing_whitespace (line);
+
+  if (!*line && !builtin->longdoc)
+    return;
+
+  if (!builtin->longdoc)
+    builtin->longdoc = array_create (sizeof (char *));
+
+  array_add (line, builtin->longdoc);
+}
+
+/* How to handle the $BUILTIN directive. */
+int
+builtin_handler (self, defs, arg)
+     char *self;
+     DEF_FILE *defs;
+     char *arg;
+{
+  BUILTIN_DESC *new;
+  char *name;
+
+  /* If we are already building a builtin, we cannot start a new one. */
+  if (building_builtin)
+    {
+      line_error (defs, "%s found before $END", self);
+      return (-1);
+    }
+
+  output_cpp_line_info++;
+
+  /* Get the name of this builtin, and stick it in the array. */
+  name = get_arg (self, defs, arg);
+
+  /* If this is the first builtin, create the array to hold them. */
+  if (!defs->builtins)
+    defs->builtins = array_create (sizeof (BUILTIN_DESC *));
+
+  new = (BUILTIN_DESC *)xmalloc (sizeof (BUILTIN_DESC));
+  new->name = name;
+  new->function = (char *)NULL;
+  new->shortdoc = (char *)NULL;
+  new->docname = (char *)NULL;
+  new->longdoc = (ARRAY *)NULL;
+  new->dependencies = (ARRAY *)NULL;
+  new->flags = 0;
+
+  if (is_special_builtin (name))
+    new->flags |= BUILTIN_FLAG_SPECIAL;
+  if (is_assignment_builtin (name))
+    new->flags |= BUILTIN_FLAG_ASSIGNMENT;
+  if (is_posix_builtin (name))
+    new->flags |= BUILTIN_FLAG_POSIX_BUILTIN;
+
+  array_add ((char *)new, defs->builtins);
+  building_builtin = 1;
+
+  return (0);
+}
+
+/* How to handle the $FUNCTION directive. */
+int
+function_handler (self, defs, arg)
+     char *self;
+     DEF_FILE *defs;
+     char *arg;
+{
+  register BUILTIN_DESC *builtin;
+
+  builtin = current_builtin (self, defs);
+
+  if (builtin == 0)
+    {
+      line_error (defs, "syntax error: no current builtin for $FUNCTION directive");
+      exit (1);
+    }
+  if (builtin->function)
+    line_error (defs, "%s already has a function (%s)",
+               builtin->name, builtin->function);
+  else
+    builtin->function = get_arg (self, defs, arg);
+
+  return (0);
+}
+
+/* How to handle the $DOCNAME directive. */
+int
+docname_handler (self, defs, arg)
+     char *self;
+     DEF_FILE *defs;
+     char *arg;
+{
+  register BUILTIN_DESC *builtin;
+
+  builtin = current_builtin (self, defs);
+
+  if (builtin->docname)
+    line_error (defs, "%s already had a docname (%s)",
+               builtin->name, builtin->docname);
+  else
+    builtin->docname = get_arg (self, defs, arg);
+
+  return (0);
+}
+
+/* How to handle the $SHORT_DOC directive. */
+int
+short_doc_handler (self, defs, arg)
+     char *self;
+     DEF_FILE *defs;
+     char *arg;
+{
+  register BUILTIN_DESC *builtin;
+
+  builtin = current_builtin (self, defs);
+
+  if (builtin->shortdoc)
+    line_error (defs, "%s already has short documentation (%s)",
+               builtin->name, builtin->shortdoc);
+  else
+    builtin->shortdoc = get_arg (self, defs, arg);
+
+  return (0);
+}
+
+/* How to handle the $COMMENT directive. */
+int
+comment_handler (self, defs, arg)
+     char *self;
+     DEF_FILE *defs;
+     char *arg;
+{
+  return (0);
+}
+
+/* How to handle the $DEPENDS_ON directive. */
+int
+depends_on_handler (self, defs, arg)
+     char *self;
+     DEF_FILE *defs;
+     char *arg;
+{
+  register BUILTIN_DESC *builtin;
+  char *dependent;
+
+  builtin = current_builtin (self, defs);
+  dependent = get_arg (self, defs, arg);
+
+  if (!builtin->dependencies)
+    builtin->dependencies = array_create (sizeof (char *));
+
+  array_add (dependent, builtin->dependencies);
+
+  return (0);
+}
+
+/* How to handle the $PRODUCES directive. */
+int
+produces_handler (self, defs, arg)
+     char *self;
+     DEF_FILE *defs;
+     char *arg;
+{
+  /* If just hacking documentation, don't change any of the production
+     files. */
+  if (only_documentation)
+    return (0);
+
+  output_cpp_line_info++;
+
+  if (defs->production)
+    line_error (defs, "%s already has a %s definition", defs->filename, self);
+  else
+    {
+      defs->production = get_arg (self, defs, arg);
+
+      if (inhibit_production)
+       return (0);
+
+      defs->output = fopen (defs->production, "w");
+
+      if (!defs->output)
+       file_error (defs->production);
+
+      fprintf (defs->output, "/* %s, created from %s. */\n",
+              defs->production, defs->filename);
+    }
+  return (0);
+}
+
+/* How to handle the $END directive. */
+int
+end_handler (self, defs, arg)
+     char *self;
+     DEF_FILE *defs;
+     char *arg;
+{
+  must_be_building (self, defs);
+  building_builtin = 0;
+  return (0);
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                 Error Handling Functions                        */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Produce an error for DEFS with FORMAT and ARGS. */
+void
+line_error (defs, format, arg1, arg2)
+     DEF_FILE *defs;
+     char *format, *arg1, *arg2;
+{
+  if (defs->filename[0] != '/')
+    fprintf (stderr, "%s", error_directory ? error_directory : "./");
+  fprintf (stderr, "%s:%d:", defs->filename, defs->line_number + 1);
+  fprintf (stderr, format, arg1, arg2);
+  fprintf (stderr, "\n");
+  fflush (stderr);
+}
+
+/* Print error message for FILENAME. */
+void
+file_error (filename)
+     char *filename;
+{
+  perror (filename);
+  exit (2);
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                     xmalloc and xrealloc ()                     */
+/*                                                                 */
+/* **************************************************************** */
+
+static void memory_error_and_abort ();
+
+static char *
+xmalloc (bytes)
+     int bytes;
+{
+  char *temp = (char *)malloc (bytes);
+
+  if (!temp)
+    memory_error_and_abort ();
+  return (temp);
+}
+
+static char *
+xrealloc (pointer, bytes)
+     char *pointer;
+     int bytes;
+{
+  char *temp;
+
+  if (!pointer)
+    temp = (char *)malloc (bytes);
+  else
+    temp = (char *)realloc (pointer, bytes);
+
+  if (!temp)
+    memory_error_and_abort ();
+
+  return (temp);
+}
+
+static void
+memory_error_and_abort ()
+{
+  fprintf (stderr, "mkbuiltins: out of virtual memory\n");
+  abort ();
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*               Creating the Struct and Extern Files              */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Return a pointer to a newly allocated builtin which is
+   an exact copy of BUILTIN. */
+BUILTIN_DESC *
+copy_builtin (builtin)
+     BUILTIN_DESC *builtin;
+{
+  BUILTIN_DESC *new;
+
+  new = (BUILTIN_DESC *)xmalloc (sizeof (BUILTIN_DESC));
+
+  new->name = savestring (builtin->name);
+  new->shortdoc = savestring (builtin->shortdoc);
+  new->longdoc = copy_string_array (builtin->longdoc);
+  new->dependencies = copy_string_array (builtin->dependencies);
+
+  new->function =
+    builtin->function ? savestring (builtin->function) : (char *)NULL;
+  new->docname =
+    builtin->docname  ? savestring (builtin->docname)  : (char *)NULL;
+
+  return (new);
+}
+
+/* How to save away a builtin. */
+void
+save_builtin (builtin)
+     BUILTIN_DESC *builtin;
+{
+  BUILTIN_DESC *newbuiltin;
+
+  newbuiltin = copy_builtin (builtin);
+
+  /* If this is the first builtin to be saved, create the array
+     to hold it. */
+  if (!saved_builtins)
+      saved_builtins = array_create (sizeof (BUILTIN_DESC *));
+
+  array_add ((char *)newbuiltin, saved_builtins);
+}
+
+/* Flags that mean something to write_documentation (). */
+#define STRING_ARRAY   0x01
+#define TEXINFO                0x02
+#define PLAINTEXT      0x04
+#define HELPFILE       0x08
+
+char *structfile_header[] = {
+  "/* builtins.c -- the built in shell commands. */",
+  "",
+  "/* This file is manufactured by ./mkbuiltins, and should not be",
+  "   edited by hand.  See the source to mkbuiltins for details. */",
+  "",
+  "/* 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/>.",
+  "*/",
+  "",
+  "/* The list of shell builtins.  Each element is name, function, flags,",
+  "   long-doc, short-doc.  The long-doc field contains a pointer to an array",
+  "   of help lines.  The function takes a WORD_LIST *; the first word in the",
+  "   list is the first arg to the command.  The list has already had word",
+  "   expansion performed.",
+  "",
+  "   Functions which need to look at only the simple commands (e.g.",
+  "   the enable_builtin ()), should ignore entries where",
+  "   (array[i].function == (sh_builtin_func_t *)NULL).  Such entries are for",
+  "   the list of shell reserved control structures, like `if' and `while'.",
+  "   The end of the list is denoted with a NULL name field. */",
+  "",
+  "#include \"../builtins.h\"",
+  (char *)NULL
+  };
+
+char *structfile_footer[] = {
+  "  { (char *)0x0, (sh_builtin_func_t *)0x0, 0, (char **)0x0, (char *)0x0, (char *)0x0 }",
+  "};",
+  "",
+  "struct builtin *shell_builtins = static_shell_builtins;",
+  "struct builtin *current_builtin;",
+  "",
+  "int num_shell_builtins =",
+  "\tsizeof (static_shell_builtins) / sizeof (struct builtin) - 1;",
+  (char *)NULL
+};
+
+/* Write out any neccessary opening information for
+   STRUCTFILE and EXTERNFILE. */
+void
+write_file_headers (structfile, externfile)
+     FILE *structfile, *externfile;
+{
+  register int i;
+
+  if (structfile)
+    {
+      for (i = 0; structfile_header[i]; i++)
+       fprintf (structfile, "%s\n", structfile_header[i]);
+
+      fprintf (structfile, "#include \"%s\"\n",
+              extern_filename ? extern_filename : "builtext.h");
+
+      fprintf (structfile, "#include \"bashintl.h\"\n");
+
+      fprintf (structfile, "\nstruct builtin static_shell_builtins[] = {\n");
+    }
+
+  if (externfile)
+    fprintf (externfile,
+            "/* %s - The list of builtins found in libbuiltins.a. */\n",
+            extern_filename ? extern_filename : "builtext.h");
+}
+
+/* Write out any necessary closing information for
+   STRUCTFILE and EXTERNFILE. */
+void
+write_file_footers (structfile, externfile)
+     FILE *structfile, *externfile;
+{
+  register int i;
+
+  /* Write out the footers. */
+  if (structfile)
+    {
+      for (i = 0; structfile_footer[i]; i++)
+       fprintf (structfile, "%s\n", structfile_footer[i]);
+    }
+}
+
+/* Write out the information accumulated in DEFS to
+   STRUCTFILE and EXTERNFILE. */
+void
+write_builtins (defs, structfile, externfile)
+     DEF_FILE *defs;
+     FILE *structfile, *externfile;
+{
+  register int i;
+
+  /* Write out the information. */
+  if (defs->builtins)
+    {
+      register BUILTIN_DESC *builtin;
+
+      for (i = 0; i < defs->builtins->sindex; i++)
+       {
+         builtin = (BUILTIN_DESC *)defs->builtins->array[i];
+
+         /* Write out any #ifdefs that may be there. */
+         if (!only_documentation)
+           {
+             if (builtin->dependencies)
+               {
+                 write_ifdefs (externfile, builtin->dependencies->array);
+                 write_ifdefs (structfile, builtin->dependencies->array);
+               }
+
+             /* Write the extern definition. */
+             if (externfile)
+               {
+                 if (builtin->function)
+                   fprintf (externfile, "extern int %s __P((WORD_LIST *));\n",
+                            builtin->function);
+
+                 fprintf (externfile, "extern char * const %s_doc[];\n",
+                          document_name (builtin));
+               }
+
+             /* Write the structure definition. */
+             if (structfile)
+               {
+                 fprintf (structfile, "  { \"%s\", ", builtin->name);
+
+                 if (builtin->function)
+                   fprintf (structfile, "%s, ", builtin->function);
+                 else
+                   fprintf (structfile, "(sh_builtin_func_t *)0x0, ");
+
+                 fprintf (structfile, "%s%s%s%s, %s_doc,\n",
+                   "BUILTIN_ENABLED | STATIC_BUILTIN",
+                   (builtin->flags & BUILTIN_FLAG_SPECIAL) ? " | SPECIAL_BUILTIN" : "",
+                   (builtin->flags & BUILTIN_FLAG_ASSIGNMENT) ? " | ASSIGNMENT_BUILTIN" : "",
+                   (builtin->flags & BUILTIN_FLAG_POSIX_BUILTIN) ? " | POSIX_BUILTIN" : "",
+                   document_name (builtin));
+
+                 fprintf
+                   (structfile, "     N_(\"%s\"), (char *)NULL },\n",
+                    builtin->shortdoc ? builtin->shortdoc : builtin->name);
+
+               }
+
+             if (structfile || separate_helpfiles)
+               /* Save away this builtin for later writing of the
+                  long documentation strings. */
+               save_builtin (builtin);
+
+             /* Write out the matching #endif, if neccessary. */
+             if (builtin->dependencies)
+               {
+                 if (externfile)
+                   write_endifs (externfile, builtin->dependencies->array);
+
+                 if (structfile)
+                   write_endifs (structfile, builtin->dependencies->array);
+               }
+           }
+
+         if (documentation_file)
+           {
+             fprintf (documentation_file, "@item %s\n", builtin->name);
+             write_documentation
+               (documentation_file, builtin->longdoc->array, 0, TEXINFO);
+           }
+       }
+    }
+}
+
+/* Write out the long documentation strings in BUILTINS to STREAM. */
+void
+write_longdocs (stream, builtins)
+     FILE *stream;
+     ARRAY *builtins;
+{
+  register int i;
+  register BUILTIN_DESC *builtin;
+  char *dname;
+  char *sarray[2];
+
+  for (i = 0; i < builtins->sindex; i++)
+    {
+      builtin = (BUILTIN_DESC *)builtins->array[i];
+
+      if (builtin->dependencies)
+       write_ifdefs (stream, builtin->dependencies->array);
+
+      /* Write the long documentation strings. */
+      dname = document_name (builtin);
+      fprintf (stream, "char * const %s_doc[] =", dname);
+
+      if (separate_helpfiles)
+       {
+         int l = strlen (helpfile_directory) + strlen (dname) + 1;
+         sarray[0] = (char *)xmalloc (l + 1);
+         sprintf (sarray[0], "%s/%s", helpfile_directory, dname);
+         sarray[1] = (char *)NULL;
+         write_documentation (stream, sarray, 0, STRING_ARRAY|HELPFILE);
+         free (sarray[0]);
+       }
+      else
+       write_documentation (stream, builtin->longdoc->array, 0, STRING_ARRAY);
+
+      if (builtin->dependencies)
+       write_endifs (stream, builtin->dependencies->array);
+
+    }
+}
+
+/* Write an #ifdef string saying what needs to be defined (or not defined)
+   in order to allow compilation of the code that will follow.
+   STREAM is the stream to write the information to,
+   DEFINES is a null terminated array of define names.
+   If a define is preceded by an `!', then the sense of the test is
+   reversed. */
+void
+write_ifdefs (stream, defines)
+     FILE *stream;
+     char **defines;
+{
+  register int i;
+
+  if (!stream)
+    return;
+
+  fprintf (stream, "#if ");
+
+  for (i = 0; defines[i]; i++)
+    {
+      char *def = defines[i];
+
+      if (*def == '!')
+       fprintf (stream, "!defined (%s)", def + 1);
+      else
+       fprintf (stream, "defined (%s)", def);
+
+      if (defines[i + 1])
+       fprintf (stream, " && ");
+    }
+  fprintf (stream, "\n");
+}
+
+/* Write an #endif string saying what defines controlled the compilation
+   of the immediately preceding code.
+   STREAM is the stream to write the information to.
+   DEFINES is a null terminated array of define names. */
+void
+write_endifs (stream, defines)
+     FILE *stream;
+     char **defines;
+{
+  register int i;
+
+  if (!stream)
+    return;
+
+  fprintf (stream, "#endif /* ");
+
+  for (i = 0; defines[i]; i++)
+    {
+      fprintf (stream, "%s", defines[i]);
+
+      if (defines[i + 1])
+       fprintf (stream, " && ");
+    }
+
+  fprintf (stream, " */\n");
+}
+
+/* Write DOCUMENTATION to STREAM, perhaps surrounding it with double-quotes
+   and quoting special characters in the string.  Handle special things for
+   internationalization (gettext) and the single-string vs. multiple-strings
+   issues. */
+void
+write_documentation (stream, documentation, indentation, flags)
+     FILE *stream;
+     char **documentation;
+     int indentation, flags;
+{
+  register int i, j;
+  register char *line;
+  int string_array, texinfo, base_indent, filename_p;
+
+  if (stream == 0)
+    return;
+
+  string_array = flags & STRING_ARRAY;
+  filename_p = flags & HELPFILE;
+
+  if (string_array)
+    {
+      fprintf (stream, " {\n#if defined (HELP_BUILTIN)\n");    /* } */
+      if (single_longdoc_strings)
+       {
+         if (filename_p == 0)
+           {
+             if (documentation && documentation[0] && documentation[0][0])
+               fprintf (stream,  "N_(\"");
+             else
+               fprintf (stream, "N_(\" ");             /* the empty string translates specially. */
+           }
+         else
+           fprintf (stream, "\"");
+       }
+    }
+
+  base_indent = (string_array && single_longdoc_strings && filename_p == 0) ? BASE_INDENT : 0;
+
+  for (i = 0, texinfo = (flags & TEXINFO); line = documentation[i]; i++)
+    {
+      /* Allow #ifdef's to be written out verbatim, but don't put them into
+        separate help files. */
+      if (*line == '#')
+       {
+         if (string_array && filename_p == 0 && single_longdoc_strings == 0)
+           fprintf (stream, "%s\n", line);
+         continue;
+       }
+
+      /* prefix with N_( for gettext */
+      if (string_array && single_longdoc_strings == 0)
+       {
+         if (filename_p == 0)
+           {
+             if (line[0])            
+               fprintf (stream, "  N_(\"");
+             else
+               fprintf (stream, "  N_(\" ");           /* the empty string translates specially. */
+           }
+         else
+           fprintf (stream, "  \"");
+       }
+
+      if (indentation)
+       for (j = 0; j < indentation; j++)
+         fprintf (stream, " ");
+
+      /* Don't indent the first line, because of how the help builtin works. */
+      if (i == 0)
+       indentation += base_indent;
+
+      if (string_array)
+       {
+         for (j = 0; line[j]; j++)
+           {
+             switch (line[j])
+               {
+               case '\\':
+               case '"':
+                 fprintf (stream, "\\%c", line[j]);
+                 break;
+
+               default:
+                 fprintf (stream, "%c", line[j]);
+               }
+           }
+
+         /* closing right paren for gettext */
+         if (single_longdoc_strings == 0)
+           {
+             if (filename_p == 0)
+               fprintf (stream, "\"),\n");
+             else
+               fprintf (stream, "\",\n");
+           }
+         else if (documentation[i+1])
+           /* don't add extra newline after last line */
+           fprintf (stream, "\\n\\\n");
+       }
+      else if (texinfo)
+       {
+         for (j = 0; line[j]; j++)
+           {
+             switch (line[j])
+               {
+               case '@':
+               case '{':
+               case '}':
+                 fprintf (stream, "@%c", line[j]);
+                 break;
+
+               default:
+                 fprintf (stream, "%c", line[j]);
+               }
+           }
+         fprintf (stream, "\n");
+       }
+      else
+       fprintf (stream, "%s\n", line);
+    }
+
+  /* closing right paren for gettext */
+  if (string_array && single_longdoc_strings)
+    {
+      if (filename_p == 0)
+       fprintf (stream, "\"),\n");
+      else
+       fprintf (stream, "\",\n");
+    }
+
+  if (string_array)
+    fprintf (stream, "#endif /* HELP_BUILTIN */\n  (char *)NULL\n};\n");
+}
+
+int
+write_helpfiles (builtins)
+     ARRAY *builtins;
+{
+  char *helpfile, *bname;
+  FILE *helpfp;
+  int i, hdlen;
+  BUILTIN_DESC *builtin;       
+
+  i = mkdir ("helpfiles", 0777);
+  if (i < 0 && errno != EEXIST)
+    {
+      fprintf (stderr, "write_helpfiles: helpfiles: cannot create directory\n");
+      return -1;
+    }
+
+  hdlen = strlen ("helpfiles/");
+  for (i = 0; i < builtins->sindex; i++)
+    {
+      builtin = (BUILTIN_DESC *)builtins->array[i];
+
+      bname = document_name (builtin);
+      helpfile = (char *)xmalloc (hdlen + strlen (bname) + 1);
+      sprintf (helpfile, "helpfiles/%s", bname);
+
+      helpfp = fopen (helpfile, "w");
+      if (helpfp == 0)
+       {
+         fprintf (stderr, "write_helpfiles: cannot open %s\n", helpfile);
+         free (helpfile);
+         continue;
+       }
+
+      write_documentation (helpfp, builtin->longdoc->array, 4, PLAINTEXT);
+
+      fflush (helpfp);
+      fclose (helpfp);
+      free (helpfile);
+    }
+  return 0;
+}      
+               
+static int
+_find_in_table (name, name_table)
+     char *name, *name_table[];
+{
+  register int i;
+
+  for (i = 0; name_table[i]; i++)
+    if (strcmp (name, name_table[i]) == 0)
+      return 1;
+  return 0;
+}
+
+static int
+is_special_builtin (name)
+     char *name;
+{
+  return (_find_in_table (name, special_builtins));
+}
+
+static int
+is_assignment_builtin (name)
+     char *name;
+{
+  return (_find_in_table (name, assignment_builtins));
+}
+
+static int
+is_posix_builtin (name)
+     char *name;
+{
+  return (_find_in_table (name, posix_builtins));
+}
+
+#if !defined (HAVE_RENAME)
+static int
+rename (from, to)
+     char *from, *to;
+{
+  unlink (to);
+  if (link (from, to) < 0)
+    return (-1);
+  unlink (from);
+  return (0);
+}
+#endif /* !HAVE_RENAME */
index 41757c0c89b828a404c9e92be44fc21d6ce3ef72..ba75be2aecfd803c6447ae1ea1316f5ee32c6ef5 100644 (file)
@@ -737,7 +737,7 @@ assign_vars:
              xfree (t1);
            }
          else
-           var = bind_read_variable (varname, t);
+           var = bind_read_variable (varname, t ? t : "");
        }
       else
        {
diff --git a/builtins/read.def~ b/builtins/read.def~
new file mode 100644 (file)
index 0000000..41757c0
--- /dev/null
@@ -0,0 +1,999 @@
+This file is read.def, from which is created read.c.
+It implements the builtin "read" in Bash.
+
+Copyright (C) 1987-2010 Free Software Foundation, Inc.
+
+This file is part of GNU Bash, the Bourne Again SHell.
+
+Bash is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Bash is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+
+$PRODUCES read.c
+
+$BUILTIN read
+$FUNCTION read_builtin
+$SHORT_DOC read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]
+Read a line from the standard input and split it into fields.
+
+Reads a single line from the standard input, or from file descriptor FD
+if the -u option is supplied.  The line is split into fields as with word
+splitting, and the first word is assigned to the first NAME, the second
+word to the second NAME, and so on, with any leftover words assigned to
+the last NAME.  Only the characters found in $IFS are recognized as word
+delimiters.
+
+If no NAMEs are supplied, the line read is stored in the REPLY variable.
+
+Options:
+  -a array     assign the words read to sequential indices of the array
+               variable ARRAY, starting at zero
+  -d delim     continue until the first character of DELIM is read, rather
+               than newline
+  -e           use Readline to obtain the line in an interactive shell
+  -i text      Use TEXT as the initial text for Readline
+  -n nchars    return after reading NCHARS characters rather than waiting
+               for a newline, but honor a delimiter if fewer than NCHARS
+               characters are read before the delimiter
+  -N nchars    return only after reading exactly NCHARS characters, unless
+               EOF is encountered or read times out, ignoring any delimiter
+  -p prompt    output the string PROMPT without a trailing newline before
+               attempting to read
+  -r           do not allow backslashes to escape any characters
+  -s           do not echo input coming from a terminal
+  -t timeout   time out and return failure if a complete line of input is
+               not read withint TIMEOUT seconds.  The value of the TMOUT
+               variable is the default timeout.  TIMEOUT may be a
+               fractional number.  If TIMEOUT is 0, read returns immediately,
+               without trying to read any data, returning success only if
+               input is available on the specified file descriptor.  The
+               exit status is greater than 128 if the timeout is exceeded
+  -u fd                read from file descriptor FD instead of the standard input
+
+Exit Status:
+The return code is zero, unless end-of-file is encountered, read times out,
+or an invalid file descriptor is supplied as the argument to -u.
+$END
+
+#include <config.h>
+
+#include "bashtypes.h"
+#include "posixstat.h"
+
+#include <stdio.h>
+
+#include "bashansi.h"
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#include <signal.h>
+#include <errno.h>
+
+#ifdef __CYGWIN__
+#  include <fcntl.h>
+#  include <io.h>
+#endif
+
+#include "../bashintl.h"
+
+#include "../shell.h"
+#include "common.h"
+#include "bashgetopt.h"
+
+#include <shtty.h>
+
+#if defined (READLINE)
+#include "../bashline.h"
+#include <readline/readline.h>
+#endif
+
+#if defined (BUFFERED_INPUT)
+#  include "input.h"
+#endif
+
+#if !defined(errno)
+extern int errno;
+#endif
+
+struct ttsave
+{
+  int fd;
+  TTYSTRUCT *attrs;
+};
+
+#if defined (READLINE)
+static void reset_attempted_completion_function __P((char *));
+static int set_itext __P((void));
+static char *edit_line __P((char *, char *));
+static void set_eol_delim __P((int));
+static void reset_eol_delim __P((char *));
+#endif
+static SHELL_VAR *bind_read_variable __P((char *, char *));
+#if defined (HANDLE_MULTIBYTE)
+static int read_mbchar __P((int, char *, int, int, int));
+#endif
+static void ttyrestore __P((struct ttsave *));
+
+static sighandler sigalrm __P((int));
+static void reset_alarm __P((void));
+
+static procenv_t alrmbuf;
+static SigHandler *old_alrm;
+static unsigned char delim;
+
+static sighandler
+sigalrm (s)
+     int s;
+{
+  longjmp (alrmbuf, 1);
+}
+
+static void
+reset_alarm ()
+{
+  set_signal_handler (SIGALRM, old_alrm);
+  falarm (0, 0);
+}
+
+/* Read the value of the shell variables whose names follow.
+   The reading is done from the current input stream, whatever
+   that may be.  Successive words of the input line are assigned
+   to the variables mentioned in LIST.  The last variable in LIST
+   gets the remainder of the words on the line.  If no variables
+   are mentioned in LIST, then the default variable is $REPLY. */
+int
+read_builtin (list)
+     WORD_LIST *list;
+{
+  register char *varname;
+  int size, i, nr, pass_next, saw_escape, eof, opt, retval, code, print_ps2;
+  int input_is_tty, input_is_pipe, unbuffered_read, skip_ctlesc, skip_ctlnul;
+  int raw, edit, nchars, silent, have_timeout, ignore_delim, fd;
+  unsigned int tmsec, tmusec;
+  long ival, uval;
+  intmax_t intval;
+  char c;
+  char *input_string, *orig_input_string, *ifs_chars, *prompt, *arrayname;
+  char *e, *t, *t1, *ps2, *tofree;
+  struct stat tsb;
+  SHELL_VAR *var;
+  TTYSTRUCT ttattrs, ttset;
+  struct ttsave termsave;
+#if defined (ARRAY_VARS)
+  WORD_LIST *alist;
+#endif
+#if defined (READLINE)
+  char *rlbuf, *itext;
+  int rlind;
+#endif
+
+  USE_VAR(size);
+  USE_VAR(i);
+  USE_VAR(pass_next);
+  USE_VAR(print_ps2);
+  USE_VAR(saw_escape);
+  USE_VAR(input_is_pipe);
+/*  USE_VAR(raw); */
+  USE_VAR(edit);
+  USE_VAR(tmsec);
+  USE_VAR(tmusec);
+  USE_VAR(nchars);
+  USE_VAR(silent);
+  USE_VAR(ifs_chars);
+  USE_VAR(prompt);
+  USE_VAR(arrayname);
+#if defined (READLINE)
+  USE_VAR(rlbuf);
+  USE_VAR(rlind);
+  USE_VAR(itext);
+#endif
+  USE_VAR(list);
+  USE_VAR(ps2);
+
+  i = 0;               /* Index into the string that we are reading. */
+  raw = edit = 0;      /* Not reading raw input by default. */
+  silent = 0;
+  arrayname = prompt = (char *)NULL;
+  fd = 0;              /* file descriptor to read from */
+
+#if defined (READLINE)
+  rlbuf = itext = (char *)0;
+  rlind = 0;
+#endif
+
+  tmsec = tmusec = 0;          /* no timeout */
+  nr = nchars = input_is_tty = input_is_pipe = unbuffered_read = have_timeout = 0;
+  delim = '\n';                /* read until newline */
+  ignore_delim = 0;
+
+  reset_internal_getopt ();
+  while ((opt = internal_getopt (list, "ersa:d:i:n:p:t:u:N:")) != -1)
+    {
+      switch (opt)
+       {
+       case 'r':
+         raw = 1;
+         break;
+       case 'p':
+         prompt = list_optarg;
+         break;
+       case 's':
+         silent = 1;
+         break;
+       case 'e':
+#if defined (READLINE)
+         edit = 1;
+#endif
+         break;
+       case 'i':
+#if defined (READLINE)
+         itext = list_optarg;
+#endif
+         break;
+#if defined (ARRAY_VARS)
+       case 'a':
+         arrayname = list_optarg;
+         break;
+#endif
+       case 't':
+         code = uconvert (list_optarg, &ival, &uval);
+         if (code == 0 || ival < 0 || uval < 0)
+           {
+             builtin_error (_("%s: invalid timeout specification"), list_optarg);
+             return (EXECUTION_FAILURE);
+           }
+         else
+           {
+             have_timeout = 1;
+             tmsec = ival;
+             tmusec = uval;
+           }
+         break;
+       case 'N':
+         ignore_delim = 1;
+         delim = -1;
+       case 'n':
+         code = legal_number (list_optarg, &intval);
+         if (code == 0 || intval < 0 || intval != (int)intval)
+           {
+             sh_invalidnum (list_optarg);
+             return (EXECUTION_FAILURE);
+           }
+         else
+           nchars = intval;
+         break;
+       case 'u':
+         code = legal_number (list_optarg, &intval);
+         if (code == 0 || intval < 0 || intval != (int)intval)
+           {
+             builtin_error (_("%s: invalid file descriptor specification"), list_optarg);
+             return (EXECUTION_FAILURE);
+           }
+         else
+           fd = intval;
+         if (sh_validfd (fd) == 0)
+           {
+             builtin_error (_("%d: invalid file descriptor: %s"), fd, strerror (errno));
+             return (EXECUTION_FAILURE);
+           }
+         break;
+       case 'd':
+         delim = *list_optarg;
+         break;
+       default:
+         builtin_usage ();
+         return (EX_USAGE);
+       }
+    }
+  list = loptend;
+
+  /* `read -t 0 var' tests whether input is available with select/FIONREAD,
+     and fails if those are unavailable */
+  if (have_timeout && tmsec == 0 && tmusec == 0)
+#if 0
+    return (EXECUTION_FAILURE);
+#else
+    return (input_avail (fd) ? EXECUTION_SUCCESS : EXECUTION_FAILURE);
+#endif
+
+  /* If we're asked to ignore the delimiter, make sure we do. */
+  if (ignore_delim)
+    delim = -1;
+
+  /* IF IFS is unset, we use the default of " \t\n". */
+  ifs_chars = getifs ();
+  if (ifs_chars == 0)          /* XXX - shouldn't happen */
+    ifs_chars = "";
+  /* If we want to read exactly NCHARS chars, don't split on IFS */
+  if (ignore_delim)
+    ifs_chars = "";
+  for (skip_ctlesc = skip_ctlnul = 0, e = ifs_chars; *e; e++)
+    skip_ctlesc |= *e == CTLESC, skip_ctlnul |= *e == CTLNUL;
+
+  input_string = (char *)xmalloc (size = 112); /* XXX was 128 */
+  input_string[0] = '\0';
+
+  /* $TMOUT, if set, is the default timeout for read. */
+  if (have_timeout == 0 && (e = get_string_value ("TMOUT")))
+    {
+      code = uconvert (e, &ival, &uval);
+      if (code == 0 || ival < 0 || uval < 0)
+       tmsec = tmusec = 0;
+      else
+       {
+         tmsec = ival;
+         tmusec = uval;
+       }
+    }
+
+  begin_unwind_frame ("read_builtin");
+
+#if defined (BUFFERED_INPUT)
+  if (interactive == 0 && default_buffered_input >= 0 && fd_is_bash_input (fd))
+    sync_buffered_stream (default_buffered_input);
+#endif
+
+  input_is_tty = isatty (fd);
+  if (input_is_tty == 0)
+#ifndef __CYGWIN__
+    input_is_pipe = (lseek (fd, 0L, SEEK_CUR) < 0) && (errno == ESPIPE);
+#else
+    input_is_pipe = 1;
+#endif
+
+  /* If the -p, -e or -s flags were given, but input is not coming from the
+     terminal, turn them off. */
+  if ((prompt || edit || silent) && input_is_tty == 0)
+    {
+      prompt = (char *)NULL;
+#if defined (READLINE)
+      itext = (char *)NULL;
+#endif
+      edit = silent = 0;
+    }
+
+#if defined (READLINE)
+  if (edit)
+    add_unwind_protect (xfree, rlbuf);
+#endif
+
+  pass_next = 0;       /* Non-zero signifies last char was backslash. */
+  saw_escape = 0;      /* Non-zero signifies that we saw an escape char */
+
+  if (tmsec > 0 || tmusec > 0)
+    {
+      /* Turn off the timeout if stdin is a regular file (e.g. from
+        input redirection). */
+      if ((fstat (fd, &tsb) < 0) || S_ISREG (tsb.st_mode))
+       tmsec = tmusec = 0;
+    }
+
+  if (tmsec > 0 || tmusec > 0)
+    {
+      code = setjmp (alrmbuf);
+      if (code)
+       {
+         /* Tricky.  The top of the unwind-protect stack is the free of
+            input_string.  We want to run all the rest and use input_string,
+            so we have to remove it from the stack. */
+         remove_unwind_protect ();
+         run_unwind_frame ("read_builtin");
+         input_string[i] = '\0';       /* make sure it's terminated */
+         retval = 128+SIGALRM;
+         goto assign_vars;
+       }
+      old_alrm = set_signal_handler (SIGALRM, sigalrm);
+      add_unwind_protect (reset_alarm, (char *)NULL);
+#if defined (READLINE)
+      if (edit)
+       add_unwind_protect (reset_attempted_completion_function, (char *)NULL);
+#endif
+      falarm (tmsec, tmusec);
+    }
+
+  /* If we've been asked to read only NCHARS chars, or we're using some
+     character other than newline to terminate the line, do the right
+     thing to readline or the tty. */
+  if (nchars > 0 || delim != '\n')
+    {
+#if defined (READLINE)
+      if (edit)
+       {
+         if (nchars > 0)
+           {
+             unwind_protect_int (rl_num_chars_to_read);
+             rl_num_chars_to_read = nchars;
+           }
+         if (delim != '\n')
+           {
+             set_eol_delim (delim);
+             add_unwind_protect (reset_eol_delim, (char *)NULL);
+           }
+       }
+      else
+#endif
+      if (input_is_tty)
+       {
+         /* ttsave() */
+         termsave.fd = fd;
+         ttgetattr (fd, &ttattrs);
+         termsave.attrs = &ttattrs;
+
+         ttset = ttattrs;        
+         i = silent ? ttfd_cbreak (fd, &ttset) : ttfd_onechar (fd, &ttset);
+         if (i < 0)
+           sh_ttyerror (1);
+         add_unwind_protect ((Function *)ttyrestore, (char *)&termsave);
+       }
+    }
+  else if (silent)     /* turn off echo but leave term in canonical mode */
+    {
+      /* ttsave (); */
+      termsave.fd = fd;
+      ttgetattr (fd, &ttattrs);
+      termsave.attrs = &ttattrs;
+
+      ttset = ttattrs;
+      i = ttfd_noecho (fd, &ttset);                    /* ttnoecho (); */
+      if (i < 0)
+       sh_ttyerror (1);
+
+      add_unwind_protect ((Function *)ttyrestore, (char *)&termsave);
+    }
+
+  /* This *must* be the top unwind-protect on the stack, so the manipulation
+     of the unwind-protect stack after the realloc() works right. */
+  add_unwind_protect (xfree, input_string);
+  interrupt_immediately++;
+
+  unbuffered_read = (nchars > 0) || (delim != '\n') || input_is_pipe;
+
+  if (prompt && edit == 0)
+    {
+      fprintf (stderr, "%s", prompt);
+      fflush (stderr);
+    }
+
+#if defined (__CYGWIN__) && defined (O_TEXT)
+  setmode (0, O_TEXT);
+#endif
+
+  ps2 = 0;
+  for (print_ps2 = eof = retval = 0;;)
+    {
+#if defined (READLINE)
+      if (edit)
+       {
+         if (rlbuf && rlbuf[rlind] == '\0')
+           {
+             xfree (rlbuf);
+             rlbuf = (char *)0;
+           }
+         if (rlbuf == 0)
+           {
+             rlbuf = edit_line (prompt ? prompt : "", itext);
+             rlind = 0;
+           }
+         if (rlbuf == 0)
+           {
+             eof = 1;
+             break;
+           }
+         c = rlbuf[rlind++];
+       }
+      else
+       {
+#endif
+
+      if (print_ps2)
+       {
+         if (ps2 == 0)
+           ps2 = get_string_value ("PS2");
+         fprintf (stderr, "%s", ps2 ? ps2 : "");
+         fflush (stderr);
+         print_ps2 = 0;
+       }
+
+      if (unbuffered_read)
+       retval = zread (fd, &c, 1);
+      else
+       retval = zreadc (fd, &c);
+
+      if (retval <= 0)
+       {
+         CHECK_TERMSIG;
+         eof = 1;
+         break;
+       }
+
+#if defined (READLINE)
+       }
+#endif
+
+      if (i + 4 >= size)       /* XXX was i + 2; use i + 4 for multibyte/read_mbchar */
+       {
+         input_string = (char *)xrealloc (input_string, size += 128);
+         remove_unwind_protect ();
+         add_unwind_protect (xfree, input_string);
+       }
+
+      /* If the next character is to be accepted verbatim, a backslash
+        newline pair still disappears from the input. */
+      if (pass_next)
+       {
+         pass_next = 0;
+         if (c == '\n')
+           {
+             i--;              /* back up over the CTLESC */
+             if (interactive && input_is_tty && raw == 0)
+               print_ps2 = 1;
+           }
+         else
+           goto add_char;
+         continue;
+       }
+
+      /* This may cause problems if IFS contains CTLESC */
+      if (c == '\\' && raw == 0)
+       {
+         pass_next++;
+         if (skip_ctlesc == 0)
+           {
+             saw_escape++;
+             input_string[i++] = CTLESC;
+           }
+         continue;
+       }
+
+      if ((unsigned char)c == delim)
+       break;
+
+      if ((skip_ctlesc == 0 && c == CTLESC) || (skip_ctlnul == 0 && c == CTLNUL))
+       {
+         saw_escape++;
+         input_string[i++] = CTLESC;
+       }
+
+add_char:
+      input_string[i++] = c;
+
+#if defined (HANDLE_MULTIBYTE)
+      if (nchars > 0 && MB_CUR_MAX > 1)
+       {
+         input_string[i] = '\0';       /* for simplicity and debugging */
+         i += read_mbchar (fd, input_string, i, c, unbuffered_read);
+       }
+#endif
+
+      nr++;
+
+      if (nchars > 0 && nr >= nchars)
+       break;
+    }
+  input_string[i] = '\0';
+
+#if 1
+  if (retval < 0)
+    {
+      builtin_error (_("read error: %d: %s"), fd, strerror (errno));
+      run_unwind_frame ("read_builtin");
+      return (EXECUTION_FAILURE);
+    }
+#endif
+
+  if (tmsec > 0 || tmusec > 0)
+    reset_alarm ();
+
+  if (nchars > 0 || delim != '\n')
+    {
+#if defined (READLINE)
+      if (edit)
+       {
+         if (nchars > 0)
+           rl_num_chars_to_read = 0;
+         if (delim != '\n')
+           reset_eol_delim ((char *)NULL);
+       }
+      else
+#endif
+      if (input_is_tty)
+       ttyrestore (&termsave);
+    }
+  else if (silent)
+    ttyrestore (&termsave);
+
+  if (unbuffered_read == 0)
+    zsyncfd (fd);
+
+  discard_unwind_frame ("read_builtin");
+
+  retval = eof ? EXECUTION_FAILURE : EXECUTION_SUCCESS;
+
+assign_vars:
+
+  interrupt_immediately--;
+
+#if defined (ARRAY_VARS)
+  /* If -a was given, take the string read, break it into a list of words,
+     an assign them to `arrayname' in turn. */
+  if (arrayname)
+    {
+      if (legal_identifier (arrayname) == 0)
+       {
+         sh_invalidid (arrayname);
+         xfree (input_string);
+         return (EXECUTION_FAILURE);
+       }
+
+      var = find_or_make_array_variable (arrayname, 1);
+      if (var == 0)
+       {
+         xfree (input_string);
+         return EXECUTION_FAILURE;     /* readonly or noassign */
+       }
+      if (assoc_p (var))
+       {
+          builtin_error (_("%s: cannot convert associative to indexed array"), arrayname);
+         xfree (input_string);
+         return EXECUTION_FAILURE;     /* existing associative array */
+       }
+      array_flush (array_cell (var));
+
+      alist = list_string (input_string, ifs_chars, 0);
+      if (alist)
+       {
+         if (saw_escape)
+           dequote_list (alist);
+         else
+           word_list_remove_quoted_nulls (alist);
+         assign_array_var_from_word_list (var, alist, 0);
+         dispose_words (alist);
+       }
+      xfree (input_string);
+      return (retval);
+    }
+#endif /* ARRAY_VARS */ 
+
+  /* If there are no variables, save the text of the line read to the
+     variable $REPLY.  ksh93 strips leading and trailing IFS whitespace,
+     so that `read x ; echo "$x"' and `read ; echo "$REPLY"' behave the
+     same way, but I believe that the difference in behaviors is useful
+     enough to not do it.  Without the bash behavior, there is no way
+     to read a line completely without interpretation or modification
+     unless you mess with $IFS (e.g., setting it to the empty string).
+     If you disagree, change the occurrences of `#if 0' to `#if 1' below. */
+  if (list == 0)
+    {
+#if 0
+      orig_input_string = input_string;
+      for (t = input_string; ifs_chars && *ifs_chars && spctabnl(*t) && isifs(*t); t++)
+       ;
+      input_string = t;
+      input_string = strip_trailing_ifs_whitespace (input_string, ifs_chars, saw_escape);
+#endif
+
+      if (saw_escape)
+       {
+         t = dequote_string (input_string);
+         var = bind_variable ("REPLY", t, 0);
+         free (t);
+       }
+      else
+       var = bind_variable ("REPLY", input_string, 0);
+      VUNSETATTR (var, att_invisible);
+
+      free (input_string);
+      return (retval);
+    }
+
+  /* This code implements the Posix.2 spec for splitting the words
+     read and assigning them to variables. */
+  orig_input_string = input_string;
+
+  /* Remove IFS white space at the beginning of the input string.  If
+     $IFS is null, no field splitting is performed. */
+  for (t = input_string; ifs_chars && *ifs_chars && spctabnl(*t) && isifs(*t); t++)
+    ;
+  input_string = t;
+  for (; list->next; list = list->next)
+    {
+      varname = list->word->word;
+#if defined (ARRAY_VARS)
+      if (legal_identifier (varname) == 0 && valid_array_reference (varname) == 0)
+#else
+      if (legal_identifier (varname) == 0)
+#endif
+       {
+         sh_invalidid (varname);
+         xfree (orig_input_string);
+         return (EXECUTION_FAILURE);
+       }
+
+      /* If there are more variables than words read from the input,
+        the remaining variables are set to the empty string. */
+      if (*input_string)
+       {
+         /* This call updates INPUT_STRING. */
+         t = get_word_from_string (&input_string, ifs_chars, &e);
+         if (t)
+           *e = '\0';
+         /* Don't bother to remove the CTLESC unless we added one
+            somewhere while reading the string. */
+         if (t && saw_escape)
+           {
+             t1 = dequote_string (t);
+             var = bind_read_variable (varname, t1);
+             xfree (t1);
+           }
+         else
+           var = bind_read_variable (varname, t);
+       }
+      else
+       {
+         t = (char *)0;
+         var = bind_read_variable (varname, "");
+       }
+
+      FREE (t);
+      if (var == 0)
+       {
+         xfree (orig_input_string);
+         return (EXECUTION_FAILURE);
+       }
+
+      stupidly_hack_special_variables (varname);
+      VUNSETATTR (var, att_invisible);
+    }
+
+  /* Now assign the rest of the line to the last variable argument. */
+#if defined (ARRAY_VARS)
+  if (legal_identifier (list->word->word) == 0 && valid_array_reference (list->word->word) == 0)
+#else
+  if (legal_identifier (list->word->word) == 0)
+#endif
+    {
+      sh_invalidid (list->word->word);
+      xfree (orig_input_string);
+      return (EXECUTION_FAILURE);
+    }
+
+#if 0
+  /* This has to be done this way rather than using string_list
+     and list_string because Posix.2 says that the last variable gets the
+     remaining words and their intervening separators. */
+  input_string = strip_trailing_ifs_whitespace (input_string, ifs_chars, saw_escape);
+#else
+  /* Check whether or not the number of fields is exactly the same as the
+     number of variables. */
+  tofree = NULL;
+  if (*input_string)
+    {
+      t1 = input_string;
+      t = get_word_from_string (&input_string, ifs_chars, &e);
+      if (*input_string == 0)
+       tofree = input_string = t;
+      else
+       {
+         input_string = strip_trailing_ifs_whitespace (t1, ifs_chars, saw_escape);
+         tofree = t;
+       }
+    }
+#endif
+
+  if (saw_escape)
+    {
+      t = dequote_string (input_string);
+      var = bind_read_variable (list->word->word, t);
+      xfree (t);
+    }
+  else
+    var = bind_read_variable (list->word->word, input_string);
+
+  if (var)
+    {
+      stupidly_hack_special_variables (list->word->word);
+      VUNSETATTR (var, att_invisible);
+    }
+  else
+    retval = EXECUTION_FAILURE;
+
+  FREE (tofree);
+  xfree (orig_input_string);
+
+  return (retval);
+}
+
+static SHELL_VAR *
+bind_read_variable (name, value)
+     char *name, *value;
+{
+  SHELL_VAR *v;
+#if defined (ARRAY_VARS)
+  if (valid_array_reference (name) == 0)
+    v = bind_variable (name, value, 0);
+  else
+    v = assign_array_element (name, value, 0);
+#else /* !ARRAY_VARS */
+  v = bind_variable (name, value, 0);
+#endif /* !ARRAY_VARS */
+  return (v == 0 ? v
+                : ((readonly_p (v) || noassign_p (v)) ? (SHELL_VAR *)NULL : v));
+}
+
+#if defined (HANDLE_MULTIBYTE)
+static int
+read_mbchar (fd, string, ind, ch, unbuffered)
+     int fd;
+     char *string;
+     int ind, ch, unbuffered;
+{
+  char mbchar[MB_LEN_MAX + 1];
+  int i, n, r;
+  char c;
+  size_t ret;
+  mbstate_t ps, ps_back;
+  wchar_t wc;
+
+  memset (&ps, '\0', sizeof (mbstate_t));
+  memset (&ps_back, '\0', sizeof (mbstate_t));
+  
+  mbchar[0] = ch;
+  i = 1;
+  for (n = 0; n <= MB_LEN_MAX; n++)
+    {
+      ps_back = ps;
+      ret = mbrtowc (&wc, mbchar, i, &ps);
+      if (ret == (size_t)-2)
+       {
+         ps = ps_back;
+         if (unbuffered)
+           r = zread (fd, &c, 1);
+         else
+           r = zreadc (fd, &c);
+         if (r < 0)
+           goto mbchar_return;
+         mbchar[i++] = c;      
+         continue;
+       }
+      else if (ret == (size_t)-1 || ret == (size_t)0 || ret > (size_t)0)
+       break;
+    }
+
+mbchar_return:
+  if (i > 1)   /* read a multibyte char */
+    /* mbchar[0] is already string[ind-1] */
+    for (r = 1; r < i; r++)
+      string[ind+r-1] = mbchar[r];
+  return i - 1;
+}
+#endif
+
+
+static void
+ttyrestore (ttp)
+     struct ttsave *ttp;
+{
+  ttsetattr (ttp->fd, ttp->attrs);
+}
+
+#if defined (READLINE)
+static rl_completion_func_t *old_attempted_completion_function = 0;
+static rl_hook_func_t *old_startup_hook;
+static char *deftext;
+
+static void
+reset_attempted_completion_function (cp)
+     char *cp;
+{
+  if (rl_attempted_completion_function == 0 && old_attempted_completion_function)
+    rl_attempted_completion_function = old_attempted_completion_function;
+}
+
+static int
+set_itext ()
+{
+  int r1, r2;
+
+  r1 = r2 = 0;
+  if (old_startup_hook)
+    r1 = (*old_startup_hook) ();
+  if (deftext)
+    {
+      r2 = rl_insert_text (deftext);
+      deftext = (char *)NULL;
+      rl_startup_hook = old_startup_hook;
+      old_startup_hook = (rl_hook_func_t *)NULL;
+    }
+  return (r1 || r2);
+}
+
+static char *
+edit_line (p, itext)
+     char *p;
+     char *itext;
+{
+  char *ret;
+  int len;
+
+  if (bash_readline_initialized == 0)
+    initialize_readline ();
+
+  old_attempted_completion_function = rl_attempted_completion_function;
+  rl_attempted_completion_function = (rl_completion_func_t *)NULL;
+  if (itext)
+    {
+      old_startup_hook = rl_startup_hook;
+      rl_startup_hook = set_itext;
+      deftext = itext;
+    }
+  ret = readline (p);
+  rl_attempted_completion_function = old_attempted_completion_function;
+  old_attempted_completion_function = (rl_completion_func_t *)NULL;
+
+  if (ret == 0)
+    return ret;
+  len = strlen (ret);
+  ret = (char *)xrealloc (ret, len + 2);
+  ret[len++] = delim;
+  ret[len] = '\0';
+  return ret;
+}
+
+static int old_delim_ctype;
+static rl_command_func_t *old_delim_func;
+static int old_newline_ctype;
+static rl_command_func_t *old_newline_func;
+
+static unsigned char delim_char;
+
+static void
+set_eol_delim (c)
+     int c;
+{
+  Keymap cmap;
+
+  if (bash_readline_initialized == 0)
+    initialize_readline ();
+  cmap = rl_get_keymap ();
+
+  /* Change newline to self-insert */
+  old_newline_ctype = cmap[RETURN].type;
+  old_newline_func =  cmap[RETURN].function;
+  cmap[RETURN].type = ISFUNC;
+  cmap[RETURN].function = rl_insert;
+
+  /* Bind the delimiter character to accept-line. */
+  old_delim_ctype = cmap[c].type;
+  old_delim_func = cmap[c].function;
+  cmap[c].type = ISFUNC;
+  cmap[c].function = rl_newline;
+
+  delim_char = c;
+}
+
+static void
+reset_eol_delim (cp)
+     char *cp;
+{
+  Keymap cmap;
+
+  cmap = rl_get_keymap ();
+
+  cmap[RETURN].type = old_newline_ctype;
+  cmap[RETURN].function = old_newline_func;
+
+  cmap[delim_char].type = old_delim_ctype;
+  cmap[delim_char].function = old_delim_func;
+}
+#endif
index ee341bb8b0613a32f020233b74f0c409759b5e39..bd9ecfc1f8172403ed35ce1b9242ff7efea4c7ab 100644 (file)
@@ -1,7 +1,7 @@
 This file is type.def, from which is created type.c.
 It implements the builtin "type" in Bash.
 
-Copyright (C) 1987-2009 Free Software Foundation, Inc.
+Copyright (C) 1987-2011 Free Software Foundation, Inc.
 
 This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -353,7 +353,7 @@ describe_command (command, dflags)
          user_command_matches (command, FS_EXEC_ONLY, found_file);
          /* XXX - should that be FS_EXEC_PREFERRED? */
 
-      if (!full_path)
+      if (full_path == 0)
        break;
 
       /* If we found the command as itself by looking through $PATH, it
@@ -375,7 +375,9 @@ describe_command (command, dflags)
          else if (dflags & (CDESC_REUSABLE|CDESC_PATH_ONLY|CDESC_SHORTDESC))
            {
              f = MP_DOCWD | ((dflags & CDESC_ABSPATH) ? MP_RMDOT : 0);
-             full_path = sh_makepath ((char *)NULL, full_path, f);
+             x = sh_makepath ((char *)NULL, full_path, f);
+             free (full_path);
+             full_path = x;
            }
        }
       /* If we require a full path and don't have one, make one */
diff --git a/builtins/type.def~ b/builtins/type.def~
new file mode 100644 (file)
index 0000000..ee341bb
--- /dev/null
@@ -0,0 +1,403 @@
+This file is type.def, from which is created type.c.
+It implements the builtin "type" 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 type.c
+
+$BUILTIN type
+$FUNCTION type_builtin
+$SHORT_DOC type [-afptP] name [name ...]
+Display information about command type.
+
+For each NAME, indicate how it would be interpreted if used as a
+command name.
+
+Options:
+  -a   display all locations containing an executable named NAME;
+       includes aliases, builtins, and functions, if and only if
+       the `-p' option is not also used
+  -f   suppress shell function lookup
+  -P   force a PATH search for each NAME, even if it is an alias,
+       builtin, or function, and returns the name of the disk file
+       that would be executed
+  -p   returns either the name of the disk file that would be executed,
+       or nothing if `type -t NAME' would not return `file'.
+  -t   output a single word which is one of `alias', `keyword',
+       `function', `builtin', `file' or `', if NAME is an alias, shell
+       reserved word, shell function, shell builtin, disk file, or not 
+       found, respectively
+
+Arguments:
+  NAME Command name to be interpreted.
+
+Exit Status:
+Returns success if all of the NAMEs are found; fails if any are not found.
+$END
+
+#include <config.h>
+
+#include "../bashtypes.h"
+#include "posixstat.h"
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include "../bashansi.h"
+#include "../bashintl.h"
+
+#include "../shell.h"
+#include "../findcmd.h"
+#include "../hashcmd.h"
+
+#if defined (ALIAS)
+#include "../alias.h"
+#endif /* ALIAS */
+
+#include "common.h"
+#include "bashgetopt.h"
+
+extern int find_reserved_word __P((char *));
+
+extern char *this_command_name;
+extern int expand_aliases, posixly_correct;
+
+/* For each word in LIST, find out what the shell is going to do with
+   it as a simple command. i.e., which file would this shell use to
+   execve, or if it is a builtin command, or an alias.  Possible flag
+   arguments:
+       -t              Returns the "type" of the object, one of
+                       `alias', `keyword', `function', `builtin',
+                       or `file'.
+
+       -p              Returns the pathname of the file if -type is
+                       a file.
+
+       -a              Returns all occurrences of words, whether they
+                       be a filename in the path, alias, function,
+                       or builtin.
+
+       -f              Suppress shell function lookup, like `command'.
+
+       -P              Force a path search even in the presence of other
+                       definitions.
+
+   Order of evaluation:
+       alias
+       keyword
+       function
+       builtin
+       file
+ */
+
+int
+type_builtin (list)
+     WORD_LIST *list;
+{
+  int dflags, any_failed, opt;
+  WORD_LIST *this;
+
+  if (list == 0)
+    return (EXECUTION_SUCCESS);
+
+  dflags = CDESC_SHORTDESC;    /* default */
+  any_failed = 0;
+
+  /* Handle the obsolescent `-type', `-path', and `-all' by prescanning
+     the arguments and converting those options to the form that
+     internal_getopt recognizes. Converts `--type', `--path', and `--all'
+     also. THIS SHOULD REALLY GO AWAY. */
+  for (this = list; this && this->word->word[0] == '-'; this = this->next)
+    {
+      char *flag = &(this->word->word[1]);
+
+      if (STREQ (flag, "type") || STREQ (flag, "-type"))
+       {
+         this->word->word[1] = 't';
+         this->word->word[2] = '\0';
+       }
+      else if (STREQ (flag, "path") || STREQ (flag, "-path"))
+       {
+         this->word->word[1] = 'p';
+         this->word->word[2] = '\0';
+       }
+      else if (STREQ (flag, "all") || STREQ (flag, "-all"))
+       {
+         this->word->word[1] = 'a';
+         this->word->word[2] = '\0';
+       }
+    }
+
+  reset_internal_getopt ();
+  while ((opt = internal_getopt (list, "afptP")) != -1)
+    {
+      switch (opt)
+       {
+       case 'a':
+         dflags |= CDESC_ALL;
+         break;
+       case 'f':
+         dflags |= CDESC_NOFUNCS;
+         break;
+       case 'p':
+         dflags |= CDESC_PATH_ONLY;
+         dflags &= ~(CDESC_TYPE|CDESC_SHORTDESC);
+         break;
+       case 't':
+         dflags |= CDESC_TYPE;
+         dflags &= ~(CDESC_PATH_ONLY|CDESC_SHORTDESC);
+         break;
+       case 'P':       /* shorthand for type -ap */
+         dflags |= (CDESC_PATH_ONLY|CDESC_FORCE_PATH);
+         dflags &= ~(CDESC_TYPE|CDESC_SHORTDESC);
+         break;
+       default:
+         builtin_usage ();
+         return (EX_USAGE);
+       }
+    }
+  list = loptend;
+
+  while (list)
+    {
+      int found;
+
+      found = describe_command (list->word->word, dflags);
+
+      if (!found && (dflags & (CDESC_PATH_ONLY|CDESC_TYPE)) == 0)
+       sh_notfound (list->word->word);
+
+      any_failed += found == 0;
+      list = list->next;
+    }
+
+  opt = (any_failed == 0) ? EXECUTION_SUCCESS : EXECUTION_FAILURE;
+  return (sh_chkwrite (opt));
+}
+
+/*
+ * Describe COMMAND as required by the type and command builtins.
+ *
+ * Behavior is controlled by DFLAGS.  Flag values are
+ *     CDESC_ALL       print all descriptions of a command
+ *     CDESC_SHORTDESC print the description for type and command -V
+ *     CDESC_REUSABLE  print in a format that may be reused as input
+ *     CDESC_TYPE      print the type for type -t
+ *     CDESC_PATH_ONLY print the path for type -p
+ *     CDESC_FORCE_PATH        force a path search for type -P
+ *     CDESC_NOFUNCS   skip function lookup for type -f
+ *     CDESC_ABSPATH   convert to absolute path, no ./ prefix
+ *
+ * CDESC_ALL says whether or not to look for all occurrences of COMMAND, or
+ * return after finding it once.
+ */
+int
+describe_command (command, dflags)
+     char *command;
+     int dflags;
+{
+  int found, i, found_file, f, all;
+  char *full_path, *x;
+  SHELL_VAR *func;
+#if defined (ALIAS)
+  alias_t *alias;
+#endif
+
+  all = (dflags & CDESC_ALL) != 0;
+  found = found_file = 0;
+  full_path = (char *)NULL;
+
+#if defined (ALIAS)
+  /* Command is an alias? */
+  if (((dflags & CDESC_FORCE_PATH) == 0) && expand_aliases && (alias = find_alias (command)))
+    {
+      if (dflags & CDESC_TYPE)
+       puts ("alias");
+      else if (dflags & CDESC_SHORTDESC)
+       printf (_("%s is aliased to `%s'\n"), command, alias->value);
+      else if (dflags & CDESC_REUSABLE)
+       {
+         x = sh_single_quote (alias->value);
+         printf ("alias %s=%s\n", command, x);
+         free (x);
+       }
+
+      found = 1;
+
+      if (all == 0)
+       return (1);
+    }
+#endif /* ALIAS */
+
+  /* Command is a shell reserved word? */
+  if (((dflags & CDESC_FORCE_PATH) == 0) && (i = find_reserved_word (command)) >= 0)
+    {
+      if (dflags & CDESC_TYPE)
+       puts ("keyword");
+      else if (dflags & CDESC_SHORTDESC)
+       printf (_("%s is a shell keyword\n"), command);
+      else if (dflags & CDESC_REUSABLE)
+       printf ("%s\n", command);
+
+      found = 1;
+
+      if (all == 0)
+       return (1);
+    }
+
+  /* Command is a function? */
+  if (((dflags & (CDESC_FORCE_PATH|CDESC_NOFUNCS)) == 0) && (func = find_function (command)))
+    {
+      if (dflags & CDESC_TYPE)
+       puts ("function");
+      else if (dflags & CDESC_SHORTDESC)
+       {
+         char *result;
+
+         printf (_("%s is a function\n"), command);
+
+         /* We're blowing away THE_PRINTED_COMMAND here... */
+
+         result = named_function_string (command, function_cell (func), FUNC_MULTILINE|FUNC_EXTERNAL);
+         printf ("%s\n", result);
+       }
+      else if (dflags & CDESC_REUSABLE)
+       printf ("%s\n", command);
+
+      found = 1;
+
+      if (all == 0)
+       return (1);
+    }
+
+  /* Command is a builtin? */
+  if (((dflags & CDESC_FORCE_PATH) == 0) && find_shell_builtin (command))
+    {
+      if (dflags & CDESC_TYPE)
+       puts ("builtin");
+      else if (dflags & CDESC_SHORTDESC)
+       printf (_("%s is a shell builtin\n"), command);
+      else if (dflags & CDESC_REUSABLE)
+       printf ("%s\n", command);
+
+      found = 1;
+
+      if (all == 0)
+       return (1);
+    }
+
+  /* Command is a disk file? */
+  /* If the command name given is already an absolute command, just
+     check to see if it is executable. */
+  if (absolute_program (command))
+    {
+      f = file_status (command);
+      if (f & FS_EXECABLE)
+       {
+         if (dflags & CDESC_TYPE)
+           puts ("file");
+         else if (dflags & CDESC_SHORTDESC)
+           printf (_("%s is %s\n"), command, command);
+         else if (dflags & (CDESC_REUSABLE|CDESC_PATH_ONLY))
+           printf ("%s\n", command);
+
+         /* There's no use looking in the hash table or in $PATH,
+            because they're not consulted when an absolute program
+            name is supplied. */
+         return (1);
+       }
+    }
+
+  /* If the user isn't doing "-a", then we might care about
+     whether the file is present in our hash table. */
+  if (all == 0 || (dflags & CDESC_FORCE_PATH))
+    {
+      if (full_path = phash_search (command))
+       {
+         if (dflags & CDESC_TYPE)
+           puts ("file");
+         else if (dflags & CDESC_SHORTDESC)
+           printf (_("%s is hashed (%s)\n"), command, full_path);
+         else if (dflags & (CDESC_REUSABLE|CDESC_PATH_ONLY))
+           printf ("%s\n", full_path);
+
+         free (full_path);
+         return (1);
+       }
+    }
+
+  /* Now search through $PATH. */
+  while (1)
+    {
+      if (all == 0)
+       full_path = find_user_command (command);
+      else
+       full_path =
+         user_command_matches (command, FS_EXEC_ONLY, found_file);
+         /* XXX - should that be FS_EXEC_PREFERRED? */
+
+      if (!full_path)
+       break;
+
+      /* If we found the command as itself by looking through $PATH, it
+        probably doesn't exist.  Check whether or not the command is an
+        executable file.  If it's not, don't report a match.  This is
+        the default posix mode behavior */
+      if (STREQ (full_path, command) || posixly_correct)
+       {
+         f = file_status (full_path);
+         if ((f & FS_EXECABLE) == 0)
+           {
+             free (full_path);
+             full_path = (char *)NULL;
+             if (all == 0)
+               break;
+           }
+         else if (ABSPATH (full_path))
+           ;   /* placeholder; don't need to do anything yet */
+         else if (dflags & (CDESC_REUSABLE|CDESC_PATH_ONLY|CDESC_SHORTDESC))
+           {
+             f = MP_DOCWD | ((dflags & CDESC_ABSPATH) ? MP_RMDOT : 0);
+             full_path = sh_makepath ((char *)NULL, full_path, f);
+           }
+       }
+      /* If we require a full path and don't have one, make one */
+      else if ((dflags & CDESC_ABSPATH) && ABSPATH (full_path) == 0)
+       full_path = sh_makepath ((char *)NULL, full_path, MP_DOCWD|MP_RMDOT);
+
+      found_file++;
+      found = 1;
+
+      if (dflags & CDESC_TYPE)
+       puts ("file");
+      else if (dflags & CDESC_SHORTDESC)
+       printf (_("%s is %s\n"), command, full_path);
+      else if (dflags & (CDESC_REUSABLE|CDESC_PATH_ONLY))
+       printf ("%s\n", full_path);
+
+      free (full_path);
+      full_path = (char *)NULL;
+
+      if (all == 0)
+       break;
+    }
+
+  return (found);
+}
diff --git a/eval.c b/eval.c
index 9011e0bf469c336e950625adb7efab3901b8e4fa..aaa61dd7297f1c9c085e79f746e744d856644ac3 100644 (file)
--- a/eval.c
+++ b/eval.c
@@ -1,6 +1,6 @@
 /* eval.c -- reading and evaluating commands. */
 
-/* Copyright (C) 1996-2009 Free Software Foundation, Inc.
+/* Copyright (C) 1996-2011 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -190,12 +190,14 @@ alrm_catcher(i)
 static void
 send_pwd_to_eterm ()
 {
-  char *pwd;
+  char *pwd, *f;
 
+  f = 0;
   pwd = get_string_value ("PWD");
   if (pwd == 0)
-    pwd = get_working_directory ("eterm");
+    f = pwd = get_working_directory ("eterm");
   fprintf (stderr, "\032/%s\n", pwd);
+  free (f);
 }
 
 /* Call the YACC-generated parser and return the status of the parse.
diff --git a/eval.c~ b/eval.c~
new file mode 100644 (file)
index 0000000..97423ba
--- /dev/null
+++ b/eval.c~
@@ -0,0 +1,283 @@
+/* eval.c -- reading and evaluating commands. */
+
+/* Copyright (C) 1996-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 (HAVE_UNISTD_H)
+#  ifdef _MINIX
+#    include <sys/types.h>
+#  endif
+#  include <unistd.h>
+#endif
+
+#include "bashansi.h"
+#include <stdio.h>
+
+#include "bashintl.h"
+
+#include "shell.h"
+#include "flags.h"
+#include "trap.h"
+
+#include "builtins/common.h"
+
+#include "input.h"
+#include "execute_cmd.h"
+
+#if defined (HISTORY)
+#  include "bashhist.h"
+#endif
+
+extern int EOF_reached;
+extern int indirection_level;
+extern int posixly_correct;
+extern int subshell_environment, running_under_emacs;
+extern int last_command_exit_value, stdin_redir;
+extern int need_here_doc;
+extern int current_command_number, current_command_line_count, line_number;
+extern int expand_aliases;
+
+static void send_pwd_to_eterm __P((void));
+static sighandler alrm_catcher __P((int));
+
+/* Read and execute commands until EOF is reached.  This assumes that
+   the input source has already been initialized. */
+int
+reader_loop ()
+{
+  int our_indirection_level;
+  COMMAND * volatile current_command;
+
+  USE_VAR(current_command);
+
+  current_command = (COMMAND *)NULL;
+
+  our_indirection_level = ++indirection_level;
+
+  while (EOF_Reached == 0)
+    {
+      int code;
+
+      code = setjmp (top_level);
+
+#if defined (PROCESS_SUBSTITUTION)
+      unlink_fifo_list ();
+#endif /* PROCESS_SUBSTITUTION */
+
+      if (interactive_shell && signal_is_ignored (SIGINT) == 0)
+       set_signal_handler (SIGINT, sigint_sighandler);
+
+      if (code != NOT_JUMPED)
+       {
+         indirection_level = our_indirection_level;
+
+         switch (code)
+           {
+             /* Some kind of throw to top_level has occured. */
+           case FORCE_EOF:
+           case ERREXIT:
+           case EXITPROG:
+             current_command = (COMMAND *)NULL;
+             if (exit_immediately_on_error)
+               variable_context = 0;   /* not in a function */
+             EOF_Reached = EOF;
+             goto exec_done;
+
+           case DISCARD:
+             /* Make sure the exit status is reset to a non-zero value, but
+                leave existing non-zero values (e.g., > 128 on signal)
+                alone. */
+             if (last_command_exit_value == 0)
+               last_command_exit_value = EXECUTION_FAILURE;
+             if (subshell_environment)
+               {
+                 current_command = (COMMAND *)NULL;
+                 EOF_Reached = EOF;
+                 goto exec_done;
+               }
+             /* Obstack free command elements, etc. */
+             if (current_command)
+               {
+                 dispose_command (current_command);
+                 current_command = (COMMAND *)NULL;
+               }
+             break;
+
+           default:
+             command_error ("reader_loop", CMDERR_BADJUMP, code, 0);
+           }
+       }
+
+      executing = 0;
+      if (temporary_env)
+       dispose_used_env_vars ();
+
+#if (defined (ultrix) && defined (mips)) || defined (C_ALLOCA)
+      /* Attempt to reclaim memory allocated with alloca (). */
+      (void) alloca (0);
+#endif
+
+      if (read_command () == 0)
+       {
+         if (interactive_shell == 0 && read_but_dont_execute)
+           {
+             last_command_exit_value = EXECUTION_SUCCESS;
+             dispose_command (global_command);
+             global_command = (COMMAND *)NULL;
+           }
+         else if (current_command = global_command)
+           {
+             global_command = (COMMAND *)NULL;
+             current_command_number++;
+
+             executing = 1;
+             stdin_redir = 0;
+             execute_command (current_command);
+
+           exec_done:
+             QUIT;
+
+             if (current_command)
+               {
+                 dispose_command (current_command);
+                 current_command = (COMMAND *)NULL;
+               }
+           }
+       }
+      else
+       {
+         /* Parse error, maybe discard rest of stream if not interactive. */
+         if (interactive == 0)
+           EOF_Reached = EOF;
+       }
+      if (just_one_command)
+       EOF_Reached = EOF;
+    }
+  indirection_level--;
+  return (last_command_exit_value);
+}
+
+static sighandler
+alrm_catcher(i)
+     int i;
+{
+  printf (_("\007timed out waiting for input: auto-logout\n"));
+  fflush (stdout);
+  bash_logout ();      /* run ~/.bash_logout if this is a login shell */
+  jump_to_top_level (EXITPROG);
+  SIGRETURN (0);
+}
+
+/* Send an escape sequence to emacs term mode to tell it the
+   current working directory. */
+static void
+send_pwd_to_eterm ()
+{
+  char *pwd, *f;
+
+  f = 0;
+  pwd = get_string_value ("PWD");
+  if (pwd == 0)
+    f = pwd = get_working_directory ("eterm");
+  fprintf (stderr, "\032/%s\n", pwd);
+  free (f);
+}
+
+/* Call the YACC-generated parser and return the status of the parse.
+   Input is read from the current input stream (bash_input).  yyparse
+   leaves the parsed command in the global variable GLOBAL_COMMAND.
+   This is where PROMPT_COMMAND is executed. */
+int
+parse_command ()
+{
+  int r;
+  char *command_to_execute;
+
+  need_here_doc = 0;
+  run_pending_traps ();
+
+  /* Allow the execution of a random command just before the printing
+     of each primary prompt.  If the shell variable PROMPT_COMMAND
+     is set then the value of it is the command to execute. */
+  if (interactive && bash_input.type != st_string)
+    {
+      command_to_execute = get_string_value ("PROMPT_COMMAND");
+      if (command_to_execute)
+       execute_variable_command (command_to_execute, "PROMPT_COMMAND");
+
+      if (running_under_emacs == 2)
+       send_pwd_to_eterm ();   /* Yuck */
+    }
+
+  current_command_line_count = 0;
+  r = yyparse ();
+
+  if (need_here_doc)
+    gather_here_documents ();
+
+  return (r);
+}
+
+/* Read and parse a command, returning the status of the parse.  The command
+   is left in the globval variable GLOBAL_COMMAND for use by reader_loop.
+   This is where the shell timeout code is executed. */
+int
+read_command ()
+{
+  SHELL_VAR *tmout_var;
+  int tmout_len, result;
+  SigHandler *old_alrm;
+
+  set_current_prompt_level (1);
+  global_command = (COMMAND *)NULL;
+
+  /* Only do timeouts if interactive. */
+  tmout_var = (SHELL_VAR *)NULL;
+  tmout_len = 0;
+  old_alrm = (SigHandler *)NULL;
+
+  if (interactive)
+    {
+      tmout_var = find_variable ("TMOUT");
+
+      if (tmout_var && var_isset (tmout_var))
+       {
+         tmout_len = atoi (value_cell (tmout_var));
+         if (tmout_len > 0)
+           {
+             old_alrm = set_signal_handler (SIGALRM, alrm_catcher);
+             alarm (tmout_len);
+           }
+       }
+    }
+
+  QUIT;
+
+  current_command_line_count = 0;
+  result = parse_command ();
+
+  if (interactive && tmout_var && (tmout_len > 0))
+    {
+      alarm(0);
+      set_signal_handler (SIGALRM, old_alrm);
+    }
+
+  return (result);
+}
index ea521396ad2751b314bb938fde9eb3d9c28e3951..a5bd4bed9991d87bc429a2958c1d09a73025509f 100644 (file)
@@ -1252,7 +1252,7 @@ time_command (command, asynchronous, pipe_in, pipe_out, fds_to_close)
 #  endif
 #endif
 
-  posix_time = (command->flags & CMD_TIME_POSIX);
+  posix_time = command && (command->flags & CMD_TIME_POSIX);
 
   nullcmd = (command == 0) || (command->type == cm_simple && command->value.Simple->words == 0 && command->value.Simple->redirects == 0);
   if (posixly_correct && nullcmd)
@@ -1672,32 +1672,45 @@ cpl_delete (pid)
 static void
 cpl_reap ()
 {
-  struct cpelement *prev, *p;
-
-  for (prev = p = coproc_list.head; p; prev = p, p = p->next)
-    if (p->coproc->c_flags & COPROC_DEAD)
-      {
-        prev->next = p->next;  /* remove from list */
-
-       /* Housekeeping in the border cases. */
-       if (p == coproc_list.head)
-         coproc_list.head = coproc_list.head->next;
-       else if (p == coproc_list.tail)
-         coproc_list.tail = prev;
+  struct cpelement *p, *next, *nh, *nt;
 
-       coproc_list.ncoproc--;
-       if (coproc_list.ncoproc == 0)
-         coproc_list.head = coproc_list.tail = 0;
-       else if (coproc_list.ncoproc == 1)
-         coproc_list.tail = coproc_list.head;          /* just to make sure */
+  /* Build a new list by removing dead coprocs and fix up the coproc_list
+     pointers when done. */
+  nh = nt = next = (struct cpelement *)0;
+  for (p = coproc_list.head; p; p = next)
+    {
+      next = p->next;
+      if (p->coproc->c_flags & COPROC_DEAD)
+       {
+         coproc_list.ncoproc--;        /* keep running count, fix up pointers later */
 
 #if defined (DEBUG)
-       itrace("cpl_reap: deleting %d", p->coproc->c_pid);
+         itrace("cpl_reap: deleting %d", p->coproc->c_pid);
 #endif
 
-       coproc_dispose (p->coproc);
-       cpe_dispose (p);
-      }
+         coproc_dispose (p->coproc);
+         cpe_dispose (p);
+       }
+      else if (nh == 0)
+       nh = nt = p;
+      else
+       {
+         nt->next = p;
+         nt = nt->next;
+       }
+    }
+
+  if (coproc_list.ncoproc == 0)
+    coproc_list.head = coproc_list.tail = 0;
+  else
+    {
+      if (nt)
+        nt->next = 0;
+      coproc_list.head = nh;
+      coproc_list.tail = nt;
+      if (coproc_list.ncoproc == 1)
+       coproc_list.tail = coproc_list.head;            /* just to make sure */  
+    }
 }
 
 /* Clear out the list of saved statuses */
@@ -2321,7 +2334,8 @@ execute_pipeline (command, asynchronous, pipe_in, pipe_out, fds_to_close)
          lastpipe_jid = stop_pipeline (0, (COMMAND *)NULL);    /* XXX */
          add_unwind_protect (lastpipe_cleanup, lastpipe_jid);
        }
-      cmd->flags |= CMD_LASTPIPE;
+      if (cmd)
+       cmd->flags |= CMD_LASTPIPE;
     }    
   if (prev >= 0)
     add_unwind_protect (close, prev);
@@ -2850,6 +2864,8 @@ print_index_and_element (len, ind, list)
     return (0);
   for (i = ind, l = list; l && --i; l = l->next)
     ;
+  if (l == 0)          /* don't think this can happen */
+    return (0);
   fprintf (stderr, "%*d%s%s", len, ind, RP_SPACE, l->word->word);
   return (displen (l->word->word));
 }
@@ -2990,7 +3006,7 @@ select_query (list, list_len, prompt, print_menu)
 
       for (l = list; l && --reply; l = l->next)
        ;
-      return (l->word->word);
+      return (l->word->word);          /* XXX - can't be null? */
     }
 }
 
@@ -3431,6 +3447,7 @@ execute_arith_command (arith_command)
 
 static char * const nullstr = "";
 
+/* XXX - can COND ever be NULL when this is called? */
 static int
 execute_cond_node (cond)
      COND_COM *cond;
@@ -3745,8 +3762,12 @@ is_dirname (pathname)
      char *pathname;
 {
   char *temp;
+  int ret;
+
   temp = search_for_command (pathname);
-  return (temp ? file_isdir (temp) : file_isdir (pathname));
+  ret = (temp ? file_isdir (temp) : file_isdir (pathname));
+  free (temp);
+  return ret;
 }
 
 /* The meaty part of all the executions.  We have to start hacking the
@@ -5092,6 +5113,7 @@ shell_execve (command, args, env)
             run it for some reason.  See why. */
 #if defined (HAVE_HASH_BANG_EXEC)
          READ_SAMPLE_BUF (command, sample, sample_len);
+         sample[sample_len - 1] = '\0';
          if (sample_len > 2 && sample[0] == '#' && sample[1] == '!')
            {
              char *interp;
index 253fcf796f16896f84e34b6a629784184f7ceec0..7d3bc42361f3072f6fca487b4252c5a85955c45d 100644 (file)
@@ -530,6 +530,10 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out,
   REDIRECT *my_undo_list, *exec_undo_list;
   volatile int last_pid;
   volatile int save_line_number;
+#if defined (PROCESS_SUBSTITUTION)
+  volatile int ofifo, nfifo, osize, saved_fifo;
+  volatile char *ofifo_list;
+#endif
 
 #if 0
   if (command == 0 || breaking || continuing || read_but_dont_execute)
@@ -670,6 +674,17 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out,
   if (shell_control_structure (command->type) && command->redirects)
     stdin_redir = stdin_redirects (command->redirects);
 
+#if defined (PROCESS_SUBSTITUTION)
+  if (variable_context != 0)
+    {
+      ofifo = num_fifos ();
+      ofifo_list = copy_fifo_list (&osize);
+      saved_fifo = 1;
+    }
+  else
+    saved_fifo = 0;
+#endif
+
   /* Handle WHILE FOR CASE etc. with redirections.  (Also '&' input
      redirection.)  */
   if (do_redirections (command->redirects, RX_ACTIVE|RX_UNDOABLE) != 0)
@@ -677,6 +692,9 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out,
       cleanup_redirects (redirection_undo_list);
       redirection_undo_list = (REDIRECT *)NULL;
       dispose_exec_redirects ();
+#if defined (PROCESS_SUBSTITUTION)
+      free (ofifo_list);
+#endif
       return (last_command_exit_value = EXECUTION_FAILURE);
     }
 
@@ -971,6 +989,16 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out,
   if (my_undo_list || exec_undo_list)
     discard_unwind_frame ("loop_redirections");
 
+#if defined (PROCESS_SUBSTITUTION)
+  if (saved_fifo)
+    {
+      nfifo = num_fifos ();
+      if (nfifo > ofifo)
+       close_new_fifos (ofifo_list, osize);
+      free (ofifo_list);
+    }
+#endif
+
   /* Invert the return value if we have to */
   if (invert)
     exec_result = (exec_result == EXECUTION_SUCCESS)
@@ -1001,6 +1029,7 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out,
   if (running_trap == 0)
 #endif
     currently_executing_command = (COMMAND *)NULL;
+
   return (last_command_exit_value);
 }
 
@@ -1223,7 +1252,7 @@ time_command (command, asynchronous, pipe_in, pipe_out, fds_to_close)
 #  endif
 #endif
 
-  posix_time = (command->flags & CMD_TIME_POSIX);
+  posix_time = command && (command->flags & CMD_TIME_POSIX);
 
   nullcmd = (command == 0) || (command->type == cm_simple && command->value.Simple->words == 0 && command->value.Simple->redirects == 0);
   if (posixly_correct && nullcmd)
@@ -1643,32 +1672,45 @@ cpl_delete (pid)
 static void
 cpl_reap ()
 {
-  struct cpelement *prev, *p;
+  struct cpelement *p, *next, *nh, *nt;
 
-  for (prev = p = coproc_list.head; p; prev = p, p = p->next)
-    if (p->coproc->c_flags & COPROC_DEAD)
-      {
-        prev->next = p->next;  /* remove from list */
-
-       /* Housekeeping in the border cases. */
-       if (p == coproc_list.head)
-         coproc_list.head = coproc_list.head->next;
-       else if (p == coproc_list.tail)
-         coproc_list.tail = prev;
-
-       coproc_list.ncoproc--;
-       if (coproc_list.ncoproc == 0)
-         coproc_list.head = coproc_list.tail = 0;
-       else if (coproc_list.ncoproc == 1)
-         coproc_list.tail = coproc_list.head;          /* just to make sure */
+  /* Build a new list by removing dead coprocs and fix up the coproc_list
+     pointers when done. */
+  nh = nt = next = (struct cpelement *)0;
+  for (p = coproc_list.head; p; p = next)
+    {
+      next = p->next;
+      if (p->coproc->c_flags & COPROC_DEAD)
+       {
+         coproc_list.ncoproc--;        /* keep running count, fix up pointers later */
 
 #if defined (DEBUG)
-       itrace("cpl_reap: deleting %d", p->coproc->c_pid);
+         itrace("cpl_reap: deleting %d", p->coproc->c_pid);
 #endif
 
-       coproc_dispose (p->coproc);
-       cpe_dispose (p);
-      }
+         coproc_dispose (p->coproc);
+         cpe_dispose (p);
+       }
+      else if (nh == 0)
+       nh = nt = p;
+      else
+       {
+         nt->next = p;
+         nt = nt->next;
+       }
+    }
+
+  nt->next = 0;
+
+  if (coproc_list.ncoproc == 0)
+    coproc_list.head = coproc_list.tail = 0;
+  else
+    {
+      coproc_list.head = nh;
+      coproc_list.tail = nt;
+      if (coproc_list.ncoproc == 1)
+       coproc_list.tail = coproc_list.head;            /* just to make sure */  
+    }
 }
 
 /* Clear out the list of saved statuses */
@@ -2292,7 +2334,8 @@ execute_pipeline (command, asynchronous, pipe_in, pipe_out, fds_to_close)
          lastpipe_jid = stop_pipeline (0, (COMMAND *)NULL);    /* XXX */
          add_unwind_protect (lastpipe_cleanup, lastpipe_jid);
        }
-      cmd->flags |= CMD_LASTPIPE;
+      if (cmd)
+       cmd->flags |= CMD_LASTPIPE;
     }    
   if (prev >= 0)
     add_unwind_protect (close, prev);
@@ -2821,6 +2864,8 @@ print_index_and_element (len, ind, list)
     return (0);
   for (i = ind, l = list; l && --i; l = l->next)
     ;
+  if (l == 0)          /* don't think this can happen */
+    return (0);
   fprintf (stderr, "%*d%s%s", len, ind, RP_SPACE, l->word->word);
   return (displen (l->word->word));
 }
@@ -2961,7 +3006,7 @@ select_query (list, list_len, prompt, print_menu)
 
       for (l = list; l && --reply; l = l->next)
        ;
-      return (l->word->word);
+      return (l->word->word);          /* XXX - can't be null? */
     }
 }
 
@@ -3402,6 +3447,7 @@ execute_arith_command (arith_command)
 
 static char * const nullstr = "";
 
+/* XXX - can COND ever be NULL when this is called? */
 static int
 execute_cond_node (cond)
      COND_COM *cond;
@@ -3716,8 +3762,12 @@ is_dirname (pathname)
      char *pathname;
 {
   char *temp;
+  int ret;
+
   temp = search_for_command (pathname);
-  return (temp ? file_isdir (temp) : file_isdir (pathname));
+  ret = (temp ? file_isdir (temp) : file_isdir (pathname));
+  free (temp);
+  return ret;
 }
 
 /* The meaty part of all the executions.  We have to start hacking the
@@ -5063,6 +5113,7 @@ shell_execve (command, args, env)
             run it for some reason.  See why. */
 #if defined (HAVE_HASH_BANG_EXEC)
          READ_SAMPLE_BUF (command, sample, sample_len);
+         sample[sample_len - 1] = '\0';
          if (sample_len > 2 && sample[0] == '#' && sample[1] == '!')
            {
              char *interp;
diff --git a/expr.c b/expr.c
index 0125df490eb067d312912108e9421b328fbb3e66..b6d3b727f118603b3c6570c6ef0ee24ef01c6d12 100644 (file)
--- a/expr.c
+++ b/expr.c
@@ -1,6 +1,6 @@
 /* expr.c -- arithmetic expression evaluation. */
 
-/* Copyright (C) 1990-2010 Free Software Foundation, Inc.
+/* Copyright (C) 1990-2011 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
diff --git a/expr.c~ b/expr.c~
index a42918f28511a417ab465a31aa3b61a27505caae..0125df490eb067d312912108e9421b328fbb3e66 100644 (file)
--- a/expr.c~
+++ b/expr.c~
@@ -494,10 +494,16 @@ expassign ()
              lvalue *= value;
              break;
            case DIV:
-             lvalue /= value;
+             if (lvalue == INTMAX_MIN && value == -1)
+               value = 1;
+             else
+               lvalue /= value;
              break;
            case MOD:
-             lvalue %= value;
+             if (lvalue == INTMAX_MIN && value == -1)
+               lvalue = 0;
+             else
+               lvalue %= value;
              break;
            case PLUS:
              lvalue += value;
@@ -550,7 +556,6 @@ expcond ()
 {
   intmax_t cval, val1, val2, rval;
   int set_noeval;
-  EXPR_CONTEXT ec;
 
   set_noeval = 0;
   rval = cval = explor ();
@@ -812,6 +817,7 @@ exp2 ()
 
       val2 = exppower ();
 
+      /* Handle division by 0 and twos-complement arithmetic overflow */
       if (((op == DIV) || (op == MOD)) && (val2 == 0))
        {
          if (noeval == 0)
@@ -819,6 +825,13 @@ exp2 ()
          else
            val2 = 1;
        }
+      else if (op == MOD && val1 == INTMAX_MIN && val2 == -1)
+       {
+         val1 = 0;
+         continue;
+       }
+      else if (op == DIV && val1 == INTMAX_MIN && val2 == -1)
+       val2 = 1;
 
       if (op == MUL)
        val1 *= val2;
@@ -830,6 +843,23 @@ exp2 ()
   return (val1);
 }
 
+static intmax_t
+ipow (base, exp)
+     intmax_t base, exp;
+{
+  intmax_t result;
+
+  result = 1;
+  while (exp)
+    {
+      if (exp & 1)
+       result *= base;
+      exp >>= 1;
+      base *= base;
+    }
+  return result;
+}
+
 static intmax_t
 exppower ()
 {
@@ -844,9 +874,7 @@ exppower ()
        return (1);
       if (val2 < 0)
        evalerror (_("exponent less than 0"));
-      for (c = 1; val2--; c *= val1)
-       ;
-      val1 = c;
+      val1 = ipow (val1, val2);
     }
   return (val1);
 }
index 330e39544d5529cbb0bba2c91621a27acf408482..1ebc71ec1f4eba75c597a85400f449832c9bdd01 100644 (file)
--- a/findcmd.c
+++ b/findcmd.c
@@ -398,7 +398,8 @@ user_command_matches (name, flags, state)
          name_len = strlen (name);
          file_to_lose_on = (char *)NULL;
          dot_found_in_search = 0;
-         stat (".", &dotinfo);
+         if (stat (".", &dotinfo) < 0)
+           dotinfo.st_dev = dotinfo.st_ino = 0;        /* so same_file won't match */
          path_list = get_string_value ("PATH");
          path_index = 0;
        }
@@ -569,7 +570,8 @@ find_user_command_in_path (name, path_list, flags)
 
   file_to_lose_on = (char *)NULL;
   name_len = strlen (name);
-  stat (".", &dotinfo);
+  if (stat (".", &dotinfo) < 0)
+    dotinfo.st_dev = dotinfo.st_ino = 0;
   path_index = 0;
 
   while (path_list[path_index])
diff --git a/findcmd.c~ b/findcmd.c~
new file mode 100644 (file)
index 0000000..65e036a
--- /dev/null
@@ -0,0 +1,618 @@
+/* findcmd.c -- Functions to search for commands by name. */
+
+/* Copyright (C) 1997-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>
+#include "chartypes.h"
+#include "bashtypes.h"
+#if !defined (_MINIX) && defined (HAVE_SYS_FILE_H)
+#  include <sys/file.h>
+#endif
+#include "filecntl.h"
+#include "posixstat.h"
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+#include <errno.h>
+
+#include "bashansi.h"
+
+#include "memalloc.h"
+#include "shell.h"
+#include "flags.h"
+#include "hashlib.h"
+#include "pathexp.h"
+#include "hashcmd.h"
+#include "findcmd.h"   /* matching prototypes and declarations */
+
+#if !defined (errno)
+extern int errno;
+#endif
+
+extern int posixly_correct;
+
+/* Static functions defined and used in this file. */
+static char *_find_user_command_internal __P((const char *, int));
+static char *find_user_command_internal __P((const char *, int));
+static char *find_user_command_in_path __P((const char *, char *, int));
+static char *find_in_path_element __P((const char *, char *, int, int, struct stat *));
+static char *find_absolute_program __P((const char *, int));
+
+static char *get_next_path_element __P((char *, int *));
+
+/* The file name which we would try to execute, except that it isn't
+   possible to execute it.  This is the first file that matches the
+   name that we are looking for while we are searching $PATH for a
+   suitable one to execute.  If we cannot find a suitable executable
+   file, then we use this one. */
+static char *file_to_lose_on;
+
+/* Non-zero if we should stat every command found in the hash table to
+   make sure it still exists. */
+int check_hashed_filenames;
+
+/* DOT_FOUND_IN_SEARCH becomes non-zero when find_user_command ()
+   encounters a `.' as the directory pathname while scanning the
+   list of possible pathnames; i.e., if `.' comes before the directory
+   containing the file of interest. */
+int dot_found_in_search = 0;
+
+/* Return some flags based on information about this file.
+   The EXISTS bit is non-zero if the file is found.
+   The EXECABLE bit is non-zero the file is executble.
+   Zero is returned if the file is not found. */
+int
+file_status (name)
+     const char *name;
+{
+  struct stat finfo;
+  int r;
+
+  /* Determine whether this file exists or not. */
+  if (stat (name, &finfo) < 0)
+    return (0);
+
+  /* If the file is a directory, then it is not "executable" in the
+     sense of the shell. */
+  if (S_ISDIR (finfo.st_mode))
+    return (FS_EXISTS|FS_DIRECTORY);
+
+  r = FS_EXISTS;
+
+#if defined (HAVE_EACCESS)
+  /* Use eaccess(2) if we have it to take things like ACLs and other
+     file access mechanisms into account.  eaccess uses the effective
+     user and group IDs, not the real ones.  We could use sh_eaccess,
+     but we don't want any special treatment for /dev/fd. */
+  if (eaccess (name, X_OK) == 0)
+    r |= FS_EXECABLE;
+  if (eaccess (name, R_OK) == 0)
+    r |= FS_READABLE;
+
+  return r;
+#elif defined (AFS)
+  /* We have to use access(2) to determine access because AFS does not
+     support Unix file system semantics.  This may produce wrong
+     answers for non-AFS files when ruid != euid.  I hate AFS. */
+  if (access (name, X_OK) == 0)
+    r |= FS_EXECABLE;
+  if (access (name, R_OK) == 0)
+    r |= FS_READABLE;
+
+  return r;
+#else /* !HAVE_EACCESS && !AFS */
+
+  /* Find out if the file is actually executable.  By definition, the
+     only other criteria is that the file has an execute bit set that
+     we can use.  The same with whether or not a file is readable. */
+
+  /* Root only requires execute permission for any of owner, group or
+     others to be able to exec a file, and can read any file. */
+  if (current_user.euid == (uid_t)0)
+    {
+      r |= FS_READABLE;
+      if (finfo.st_mode & S_IXUGO)
+       r |= FS_EXECABLE;
+      return r;
+    }
+
+  /* If we are the owner of the file, the owner bits apply. */
+  if (current_user.euid == finfo.st_uid)
+    {
+      if (finfo.st_mode & S_IXUSR)
+       r |= FS_EXECABLE;
+      if (finfo.st_mode & S_IRUSR)
+       r |= FS_READABLE;
+    }
+
+  /* If we are in the owning group, the group permissions apply. */
+  else if (group_member (finfo.st_gid))
+    {
+      if (finfo.st_mode & S_IXGRP)
+       r |= FS_EXECABLE;
+      if (finfo.st_mode & S_IRGRP)
+       r |= FS_READABLE;
+    }
+
+  /* Else we check whether `others' have permission to execute the file */
+  else
+    {
+      if (finfo.st_mode & S_IXOTH)
+       r |= FS_EXECABLE;
+      if (finfo.st_mode & S_IROTH)
+       r |= FS_READABLE;
+    }
+
+  return r;
+#endif /* !AFS */
+}
+
+/* Return non-zero if FILE exists and is executable.
+   Note that this function is the definition of what an
+   executable file is; do not change this unless YOU know
+   what an executable file is. */
+int
+executable_file (file)
+     const char *file;
+{
+  int s;
+
+  s = file_status (file);
+#if defined EISDIR
+  if (s & FS_DIRECTORY)
+    errno = EISDIR;    /* let's see if we can improve error messages */
+#endif
+  return ((s & FS_EXECABLE) && ((s & FS_DIRECTORY) == 0));
+}
+
+int
+is_directory (file)
+     const char *file;
+{
+  return (file_status (file) & FS_DIRECTORY);
+}
+
+int
+executable_or_directory (file)
+     const char *file;
+{
+  int s;
+
+  s = file_status (file);
+  return ((s & FS_EXECABLE) || (s & FS_DIRECTORY));
+}
+
+/* Locate the executable file referenced by NAME, searching along
+   the contents of the shell PATH variable.  Return a new string
+   which is the full pathname to the file, or NULL if the file
+   couldn't be found.  If a file is found that isn't executable,
+   and that is the only match, then return that. */
+char *
+find_user_command (name)
+     const char *name;
+{
+  return (find_user_command_internal (name, FS_EXEC_PREFERRED|FS_NODIRS));
+}
+
+/* Locate the file referenced by NAME, searching along the contents
+   of the shell PATH variable.  Return a new string which is the full
+   pathname to the file, or NULL if the file couldn't be found.  This
+   returns the first readable file found; designed to be used to look
+   for shell scripts or files to source. */
+char *
+find_path_file (name)
+     const char *name;
+{
+  return (find_user_command_internal (name, FS_READABLE));
+}
+
+static char *
+_find_user_command_internal (name, flags)
+     const char *name;
+     int flags;
+{
+  char *path_list, *cmd;
+  SHELL_VAR *var;
+
+  /* Search for the value of PATH in both the temporary environments and
+     in the regular list of variables. */
+  if (var = find_variable_internal ("PATH", 1))        /* XXX could be array? */
+    path_list = value_cell (var);
+  else
+    path_list = (char *)NULL;
+
+  if (path_list == 0 || *path_list == '\0')
+    return (savestring (name));
+
+  cmd = find_user_command_in_path (name, path_list, flags);
+
+  return (cmd);
+}
+
+static char *
+find_user_command_internal (name, flags)
+     const char *name;
+     int flags;
+{
+#ifdef __WIN32__
+  char *res, *dotexe;
+
+  dotexe = (char *)xmalloc (strlen (name) + 5);
+  strcpy (dotexe, name);
+  strcat (dotexe, ".exe");
+  res = _find_user_command_internal (dotexe, flags);
+  free (dotexe);
+  if (res == 0)
+    res = _find_user_command_internal (name, flags);
+  return res;
+#else
+  return (_find_user_command_internal (name, flags));
+#endif
+}
+
+/* Return the next element from PATH_LIST, a colon separated list of
+   paths.  PATH_INDEX_POINTER is the address of an index into PATH_LIST;
+   the index is modified by this function.
+   Return the next element of PATH_LIST or NULL if there are no more. */
+static char *
+get_next_path_element (path_list, path_index_pointer)
+     char *path_list;
+     int *path_index_pointer;
+{
+  char *path;
+
+  path = extract_colon_unit (path_list, path_index_pointer);
+
+  if (path == 0)
+    return (path);
+
+  if (*path == '\0')
+    {
+      free (path);
+      path = savestring (".");
+    }
+
+  return (path);
+}
+
+/* Look for PATHNAME in $PATH.  Returns either the hashed command
+   corresponding to PATHNAME or the first instance of PATHNAME found
+   in $PATH.  Returns a newly-allocated string. */
+char *
+search_for_command (pathname)
+     const char *pathname;
+{
+  char *hashed_file, *command;
+  int temp_path, st;
+  SHELL_VAR *path;
+
+  hashed_file = command = (char *)NULL;
+
+  /* If PATH is in the temporary environment for this command, don't use the
+     hash table to search for the full pathname. */
+  path = find_variable_internal ("PATH", 1);
+  temp_path = path && tempvar_p (path);
+  if (temp_path == 0 && path)
+    path = (SHELL_VAR *)NULL;
+
+  /* Don't waste time trying to find hashed data for a pathname
+     that is already completely specified or if we're using a command-
+     specific value for PATH. */
+  if (path == 0 && absolute_program (pathname) == 0)
+    hashed_file = phash_search (pathname);
+
+  /* If a command found in the hash table no longer exists, we need to
+     look for it in $PATH.  Thank you Posix.2.  This forces us to stat
+     every command found in the hash table. */
+
+  if (hashed_file && (posixly_correct || check_hashed_filenames))
+    {
+      st = file_status (hashed_file);
+      if ((st & (FS_EXISTS|FS_EXECABLE)) != (FS_EXISTS|FS_EXECABLE))
+       {
+         phash_remove (pathname);
+         free (hashed_file);
+         hashed_file = (char *)NULL;
+       }
+    }
+
+  if (hashed_file)
+    command = hashed_file;
+  else if (absolute_program (pathname))
+    /* A command containing a slash is not looked up in PATH or saved in
+       the hash table. */
+    command = savestring (pathname);
+  else
+    {
+      /* If $PATH is in the temporary environment, we've already retrieved
+        it, so don't bother trying again. */
+      if (temp_path)
+       {
+         command = find_user_command_in_path (pathname, value_cell (path),
+                                              FS_EXEC_PREFERRED|FS_NODIRS);
+       }
+      else
+       command = find_user_command (pathname);
+      if (command && hashing_enabled && temp_path == 0)
+       phash_insert ((char *)pathname, command, dot_found_in_search, 1);       /* XXX fix const later */
+    }
+  return (command);
+}
+
+char *
+user_command_matches (name, flags, state)
+     const char *name;
+     int flags, state;
+{
+  register int i;
+  int  path_index, name_len;
+  char *path_list, *path_element, *match;
+  struct stat dotinfo;
+  static char **match_list = NULL;
+  static int match_list_size = 0;
+  static int match_index = 0;
+
+  if (state == 0)
+    {
+      /* Create the list of matches. */
+      if (match_list == 0)
+       {
+         match_list_size = 5;
+         match_list = strvec_create (match_list_size);
+       }
+
+      /* Clear out the old match list. */
+      for (i = 0; i < match_list_size; i++)
+       match_list[i] = 0;
+
+      /* We haven't found any files yet. */
+      match_index = 0;
+
+      if (absolute_program (name))
+       {
+         match_list[0] = find_absolute_program (name, flags);
+         match_list[1] = (char *)NULL;
+         path_list = (char *)NULL;
+       }
+      else
+       {
+         name_len = strlen (name);
+         file_to_lose_on = (char *)NULL;
+         dot_found_in_search = 0;
+         if (stat (".", &dotinfo) < 0)
+           dotinfo.st_dev = dotinfo.st_ino = 0;        /* so same_file won't match */
+         path_list = get_string_value ("PATH");
+         path_index = 0;
+       }
+
+      while (path_list && path_list[path_index])
+       {
+         path_element = get_next_path_element (path_list, &path_index);
+
+         if (path_element == 0)
+           break;
+
+         match = find_in_path_element (name, path_element, flags, name_len, &dotinfo);
+
+         free (path_element);
+
+         if (match == 0)
+           continue;
+
+         if (match_index + 1 == match_list_size)
+           {
+             match_list_size += 10;
+             match_list = strvec_resize (match_list, (match_list_size + 1));
+           }
+
+         match_list[match_index++] = match;
+         match_list[match_index] = (char *)NULL;
+         FREE (file_to_lose_on);
+         file_to_lose_on = (char *)NULL;
+       }
+
+      /* We haven't returned any strings yet. */
+      match_index = 0;
+    }
+
+  match = match_list[match_index];
+
+  if (match)
+    match_index++;
+
+  return (match);
+}
+
+static char *
+find_absolute_program (name, flags)
+     const char *name;
+     int flags;
+{
+  int st;
+
+  st = file_status (name);
+
+  /* If the file doesn't exist, quit now. */
+  if ((st & FS_EXISTS) == 0)
+    return ((char *)NULL);
+
+  /* If we only care about whether the file exists or not, return
+     this filename.  Otherwise, maybe we care about whether this
+     file is executable.  If it is, and that is what we want, return it. */
+  if ((flags & FS_EXISTS) || ((flags & FS_EXEC_ONLY) && (st & FS_EXECABLE)))
+    return (savestring (name));
+
+  return (NULL);
+}
+
+static char *
+find_in_path_element (name, path, flags, name_len, dotinfop)
+     const char *name;
+     char *path;
+     int flags, name_len;
+     struct stat *dotinfop;
+{
+  int status;
+  char *full_path, *xpath;
+
+  xpath = (*path == '~') ? bash_tilde_expand (path, 0) : path;
+
+  /* Remember the location of "." in the path, in all its forms
+     (as long as they begin with a `.', e.g. `./.') */
+  if (dot_found_in_search == 0 && *xpath == '.')
+    dot_found_in_search = same_file (".", xpath, dotinfop, (struct stat *)NULL);
+
+  full_path = sh_makepath (xpath, name, 0);
+
+  status = file_status (full_path);
+
+  if (xpath != path)
+    free (xpath);
+
+  if ((status & FS_EXISTS) == 0)
+    {
+      free (full_path);
+      return ((char *)NULL);
+    }
+
+  /* The file exists.  If the caller simply wants the first file, here it is. */
+  if (flags & FS_EXISTS)
+    return (full_path);
+
+  /* If we have a readable file, and the caller wants a readable file, this
+     is it. */
+  if ((flags & FS_READABLE) && (status & FS_READABLE))
+    return (full_path);
+
+  /* If the file is executable, then it satisfies the cases of
+      EXEC_ONLY and EXEC_PREFERRED.  Return this file unconditionally. */
+  if ((status & FS_EXECABLE) && (flags & (FS_EXEC_ONLY|FS_EXEC_PREFERRED)) &&
+      (((flags & FS_NODIRS) == 0) || ((status & FS_DIRECTORY) == 0)))
+    {
+      FREE (file_to_lose_on);
+      file_to_lose_on = (char *)NULL;
+      return (full_path);
+    }
+
+  /* The file is not executable, but it does exist.  If we prefer
+     an executable, then remember this one if it is the first one
+     we have found. */
+  if ((flags & FS_EXEC_PREFERRED) && file_to_lose_on == 0)
+    file_to_lose_on = savestring (full_path);
+
+  /* If we want only executable files, or we don't want directories and
+     this file is a directory, or we want a readable file and this file
+     isn't readable, fail. */
+  if ((flags & (FS_EXEC_ONLY|FS_EXEC_PREFERRED)) ||
+      ((flags & FS_NODIRS) && (status & FS_DIRECTORY)) ||
+      ((flags & FS_READABLE) && (status & FS_READABLE) == 0))
+    {
+      free (full_path);
+      return ((char *)NULL);
+    }
+  else
+    return (full_path);
+}
+
+/* This does the dirty work for find_user_command_internal () and
+   user_command_matches ().
+   NAME is the name of the file to search for.
+   PATH_LIST is a colon separated list of directories to search.
+   FLAGS contains bit fields which control the files which are eligible.
+   Some values are:
+      FS_EXEC_ONLY:            The file must be an executable to be found.
+      FS_EXEC_PREFERRED:       If we can't find an executable, then the
+                               the first file matching NAME will do.
+      FS_EXISTS:               The first file found will do.
+      FS_NODIRS:               Don't find any directories.
+*/
+static char *
+find_user_command_in_path (name, path_list, flags)
+     const char *name;
+     char *path_list;
+     int flags;
+{
+  char *full_path, *path;
+  int path_index, name_len;
+  struct stat dotinfo;
+
+  /* We haven't started looking, so we certainly haven't seen
+     a `.' as the directory path yet. */
+  dot_found_in_search = 0;
+
+  if (absolute_program (name))
+    {
+      full_path = find_absolute_program (name, flags);
+      return (full_path);
+    }
+
+  if (path_list == 0 || *path_list == '\0')
+    return (savestring (name));                /* XXX */
+
+  file_to_lose_on = (char *)NULL;
+  name_len = strlen (name);
+  stat (".", &dotinfo);
+  path_index = 0;
+
+  while (path_list[path_index])
+    {
+      /* Allow the user to interrupt out of a lengthy path search. */
+      QUIT;
+
+      path = get_next_path_element (path_list, &path_index);
+      if (path == 0)
+       break;
+
+      /* Side effects: sets dot_found_in_search, possibly sets
+        file_to_lose_on. */
+      full_path = find_in_path_element (name, path, flags, name_len, &dotinfo);
+      free (path);
+
+      /* This should really be in find_in_path_element, but there isn't the
+        right combination of flags. */
+      if (full_path && is_directory (full_path))
+       {
+         free (full_path);
+         continue;
+       }
+
+      if (full_path)
+       {
+         FREE (file_to_lose_on);
+         return (full_path);
+       }
+    }
+
+  /* We didn't find exactly what the user was looking for.  Return
+     the contents of FILE_TO_LOSE_ON which is NULL when the search
+     required an executable, or non-NULL if a file was found and the
+     search would accept a non-executable as a last resort.  If the
+     caller specified FS_NODIRS, and file_to_lose_on is a directory,
+     return NULL. */
+  if (file_to_lose_on && (flags & FS_NODIRS) && is_directory (file_to_lose_on))
+    {
+      free (file_to_lose_on);
+      file_to_lose_on = (char *)NULL;
+    }
+
+  return (file_to_lose_on);
+}
index 86c8ea1e9e443d8da2891c9fd6407f97426b9c45..aa5ce7e2586c2298d4d790d7052f9d3eb63a202e 100644 (file)
--- a/general.c
+++ b/general.c
@@ -1,6 +1,6 @@
 /* general.c -- Stuff that is used by all files. */
 
-/* Copyright (C) 1987-2009 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2011 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -171,6 +171,9 @@ legal_number (string, result)
   if (result)
     *result = 0;
 
+  if (string == 0)
+    return 0;
+
   errno = 0;
   value = strtoimax (string, &ep, 10);
   if (errno || ep == string)
@@ -182,7 +185,7 @@ legal_number (string, result)
 
   /* If *string is not '\0' but *ep is '\0' on return, the entire string
      is valid. */
-  if (string && *string && *ep == '\0')
+  if (*string && *ep == '\0')
     {
       if (result)
        *result = value;
@@ -398,7 +401,8 @@ check_dev_tty ()
        return;
       tty_fd = open (tty, O_RDWR|O_NONBLOCK);
     }
-  close (tty_fd);
+  if (tty_fd >= 0)
+    close (tty_fd);
 }
 
 /* Return 1 if PATH1 and PATH2 are the same file.  This is kind of
diff --git a/general.c~ b/general.c~
new file mode 100644 (file)
index 0000000..c71cf2f
--- /dev/null
@@ -0,0 +1,1163 @@
+/* general.c -- Stuff that is used by all files. */
+
+/* Copyright (C) 1987-2011 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
+#include "posixstat.h"
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#include "filecntl.h"
+#include "bashansi.h"
+#include <stdio.h>
+#include "chartypes.h"
+#include <errno.h>
+
+#include "bashintl.h"
+
+#include "shell.h"
+#include "test.h"
+
+#include <tilde/tilde.h>
+
+#if !defined (errno)
+extern int errno;
+#endif /* !errno */
+
+extern int expand_aliases;
+extern int interactive_comments;
+extern int check_hashed_filenames;
+extern int source_uses_path;
+extern int source_searches_cwd;
+
+static char *bash_special_tilde_expansions __P((char *));
+static int unquoted_tilde_word __P((const char *));
+static void initialize_group_array __P((void));
+
+/* A standard error message to use when getcwd() returns NULL. */
+const char * const bash_getcwd_errstr = N_("getcwd: cannot access parent directories");
+
+/* Do whatever is necessary to initialize `Posix mode'. */
+void
+posix_initialize (on)
+     int on;
+{
+  /* Things that should be turned on when posix mode is enabled. */
+  if (on != 0)
+    {
+      interactive_comments = source_uses_path = expand_aliases = 1;
+      source_searches_cwd = 0;
+    }
+
+  /* Things that should be turned on when posix mode is disabled. */
+  if (on == 0)
+    {
+      source_searches_cwd = 1;
+      expand_aliases = interactive_shell;
+    }
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*  Functions to convert to and from and display non-standard types */
+/*                                                                 */
+/* **************************************************************** */
+
+#if defined (RLIMTYPE)
+RLIMTYPE
+string_to_rlimtype (s)
+     char *s;
+{
+  RLIMTYPE ret;
+  int neg;
+
+  ret = 0;
+  neg = 0;
+  while (s && *s && whitespace (*s))
+    s++;
+  if (s && (*s == '-' || *s == '+'))
+    {
+      neg = *s == '-';
+      s++;
+    }
+  for ( ; s && *s && DIGIT (*s); s++)
+    ret = (ret * 10) + TODIGIT (*s);
+  return (neg ? -ret : ret);
+}
+
+void
+print_rlimtype (n, addnl)
+     RLIMTYPE n;
+     int addnl;
+{
+  char s[INT_STRLEN_BOUND (RLIMTYPE) + 1], *p;
+
+  p = s + sizeof(s);
+  *--p = '\0';
+
+  if (n < 0)
+    {
+      do
+       *--p = '0' - n % 10;
+      while ((n /= 10) != 0);
+
+      *--p = '-';
+    }
+  else
+    {
+      do
+       *--p = '0' + n % 10;
+      while ((n /= 10) != 0);
+    }
+
+  printf ("%s%s", p, addnl ? "\n" : "");
+}
+#endif /* RLIMTYPE */
+
+/* **************************************************************** */
+/*                                                                 */
+/*                    Input Validation Functions                   */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Return non-zero if all of the characters in STRING are digits. */
+int
+all_digits (string)
+     char *string;
+{
+  register char *s;
+
+  for (s = string; *s; s++)
+    if (DIGIT (*s) == 0)
+      return (0);
+
+  return (1);
+}
+
+/* Return non-zero if the characters pointed to by STRING constitute a
+   valid number.  Stuff the converted number into RESULT if RESULT is
+   not null. */
+int
+legal_number (string, result)
+     const char *string;
+     intmax_t *result;
+{
+  intmax_t value;
+  char *ep;
+
+  if (result)
+    *result = 0;
+
+  errno = 0;
+  value = strtoimax (string, &ep, 10);
+  if (errno || ep == string)
+    return 0;  /* errno is set on overflow or underflow */
+
+  /* Skip any trailing whitespace, since strtoimax does not. */
+  while (whitespace (*ep))
+    ep++;
+
+  /* If *string is not '\0' but *ep is '\0' on return, the entire string
+     is valid. */
+  if (string && *string && *ep == '\0')
+    {
+      if (result)
+       *result = value;
+      /* The SunOS4 implementation of strtol() will happily ignore
+        overflow conditions, so this cannot do overflow correctly
+        on those systems. */
+      return 1;
+    }
+    
+  return (0);
+}
+
+/* Return 1 if this token is a legal shell `identifier'; that is, it consists
+   solely of letters, digits, and underscores, and does not begin with a
+   digit. */
+int
+legal_identifier (name)
+     char *name;
+{
+  register char *s;
+  unsigned char c;
+
+  if (!name || !(c = *name) || (legal_variable_starter (c) == 0))
+    return (0);
+
+  for (s = name + 1; (c = *s) != 0; s++)
+    {
+      if (legal_variable_char (c) == 0)
+       return (0);
+    }
+  return (1);
+}
+
+/* Make sure that WORD is a valid shell identifier, i.e.
+   does not contain a dollar sign, nor is quoted in any way.  Nor
+   does it consist of all digits.  If CHECK_WORD is non-zero,
+   the word is checked to ensure that it consists of only letters,
+   digits, and underscores. */
+int
+check_identifier (word, check_word)
+     WORD_DESC *word;
+     int check_word;
+{
+  if ((word->flags & (W_HASDOLLAR|W_QUOTED)) || all_digits (word->word))
+    {
+      internal_error (_("`%s': not a valid identifier"), word->word);
+      return (0);
+    }
+  else if (check_word && legal_identifier (word->word) == 0)
+    {
+      internal_error (_("`%s': not a valid identifier"), word->word);
+      return (0);
+    }
+  else
+    return (1);
+}
+
+/* Return 1 if STRING comprises a valid alias name.  The shell accepts
+   essentially all characters except those which must be quoted to the
+   parser (which disqualifies them from alias expansion anyway) and `/'. */
+int
+legal_alias_name (string, flags)
+     char *string;
+     int flags;
+{
+  register char *s;
+
+  for (s = string; *s; s++)
+    if (shellbreak (*s) || shellxquote (*s) || shellexp (*s) || (*s == '/'))
+      return 0;
+  return 1;
+}
+
+/* Returns non-zero if STRING is an assignment statement.  The returned value
+   is the index of the `=' sign. */
+int
+assignment (string, flags)
+     const char *string;
+     int flags;
+{
+  register unsigned char c;
+  register int newi, indx;
+
+  c = string[indx = 0];
+
+#if defined (ARRAY_VARS)
+  if ((legal_variable_starter (c) == 0) && (flags == 0 || c != '[')) /* ] */
+#else
+  if (legal_variable_starter (c) == 0)
+#endif
+    return (0);
+
+  while (c = string[indx])
+    {
+      /* The following is safe.  Note that '=' at the start of a word
+        is not an assignment statement. */
+      if (c == '=')
+       return (indx);
+
+#if defined (ARRAY_VARS)
+      if (c == '[')
+       {
+         newi = skipsubscript (string, indx, 0);
+         if (string[newi++] != ']')
+           return (0);
+         if (string[newi] == '+' && string[newi+1] == '=')
+           return (newi + 1);
+         return ((string[newi] == '=') ? newi : 0);
+       }
+#endif /* ARRAY_VARS */
+
+      /* Check for `+=' */
+      if (c == '+' && string[indx+1] == '=')
+       return (indx + 1);
+
+      /* Variable names in assignment statements may contain only letters,
+        digits, and `_'. */
+      if (legal_variable_char (c) == 0)
+       return (0);
+
+      indx++;
+    }
+  return (0);
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*          Functions to manage files and file descriptors         */
+/*                                                                 */
+/* **************************************************************** */
+
+/* A function to unset no-delay mode on a file descriptor.  Used in shell.c
+   to unset it on the fd passed as stdin.  Should be called on stdin if
+   readline gets an EAGAIN or EWOULDBLOCK when trying to read input. */
+
+#if !defined (O_NDELAY)
+#  if defined (FNDELAY)
+#    define O_NDELAY FNDELAY
+#  endif
+#endif /* O_NDELAY */
+
+/* Make sure no-delay mode is not set on file descriptor FD. */
+int
+sh_unset_nodelay_mode (fd)
+     int fd;
+{
+  int flags, bflags;
+
+  if ((flags = fcntl (fd, F_GETFL, 0)) < 0)
+    return -1;
+
+  bflags = 0;
+
+  /* This is defined to O_NDELAY in filecntl.h if O_NONBLOCK is not present
+     and O_NDELAY is defined. */
+#ifdef O_NONBLOCK
+  bflags |= O_NONBLOCK;
+#endif
+
+#ifdef O_NDELAY
+  bflags |= O_NDELAY;
+#endif
+
+  if (flags & bflags)
+    {
+      flags &= ~bflags;
+      return (fcntl (fd, F_SETFL, flags));
+    }
+
+  return 0;
+}
+
+/* Return 1 if file descriptor FD is valid; 0 otherwise. */
+int
+sh_validfd (fd)
+     int fd;
+{
+  return (fcntl (fd, F_GETFD, 0) >= 0);
+}
+
+int
+fd_ispipe (fd)
+     int fd;
+{
+  errno = 0;
+  if (lseek ((fd), 0L, SEEK_CUR) < 0)
+    return (errno == ESPIPE);
+  return 0;
+}
+
+/* There is a bug in the NeXT 2.1 rlogind that causes opens
+   of /dev/tty to fail. */
+
+#if defined (__BEOS__)
+/* On BeOS, opening in non-blocking mode exposes a bug in BeOS, so turn it
+   into a no-op.  This should probably go away in the future. */
+#  undef O_NONBLOCK
+#  define O_NONBLOCK 0
+#endif /* __BEOS__ */
+
+void
+check_dev_tty ()
+{
+  int tty_fd;
+  char *tty;
+
+  tty_fd = open ("/dev/tty", O_RDWR|O_NONBLOCK);
+
+  if (tty_fd < 0)
+    {
+      tty = (char *)ttyname (fileno (stdin));
+      if (tty == 0)
+       return;
+      tty_fd = open (tty, O_RDWR|O_NONBLOCK);
+    }
+  if (tty_fd >= 0)
+    close (tty_fd);
+}
+
+/* Return 1 if PATH1 and PATH2 are the same file.  This is kind of
+   expensive.  If non-NULL STP1 and STP2 point to stat structures
+   corresponding to PATH1 and PATH2, respectively. */
+int
+same_file (path1, path2, stp1, stp2)
+     char *path1, *path2;
+     struct stat *stp1, *stp2;
+{
+  struct stat st1, st2;
+
+  if (stp1 == NULL)
+    {
+      if (stat (path1, &st1) != 0)
+       return (0);
+      stp1 = &st1;
+    }
+
+  if (stp2 == NULL)
+    {
+      if (stat (path2, &st2) != 0)
+       return (0);
+      stp2 = &st2;
+    }
+
+  return ((stp1->st_dev == stp2->st_dev) && (stp1->st_ino == stp2->st_ino));
+}
+
+/* Move FD to a number close to the maximum number of file descriptors
+   allowed in the shell process, to avoid the user stepping on it with
+   redirection and causing us extra work.  If CHECK_NEW is non-zero,
+   we check whether or not the file descriptors are in use before
+   duplicating FD onto them.  MAXFD says where to start checking the
+   file descriptors.  If it's less than 20, we get the maximum value
+   available from getdtablesize(2). */
+int
+move_to_high_fd (fd, check_new, maxfd)
+     int fd, check_new, maxfd;
+{
+  int script_fd, nfds, ignore;
+
+  if (maxfd < 20)
+    {
+      nfds = getdtablesize ();
+      if (nfds <= 0)
+       nfds = 20;
+      if (nfds > HIGH_FD_MAX)
+       nfds = HIGH_FD_MAX;             /* reasonable maximum */
+    }
+  else
+    nfds = maxfd;
+
+  for (nfds--; check_new && nfds > 3; nfds--)
+    if (fcntl (nfds, F_GETFD, &ignore) == -1)
+      break;
+
+  if (nfds > 3 && fd != nfds && (script_fd = dup2 (fd, nfds)) != -1)
+    {
+      if (check_new == 0 || fd != fileno (stderr))     /* don't close stderr */
+       close (fd);
+      return (script_fd);
+    }
+
+  /* OK, we didn't find one less than our artificial maximum; return the
+     original file descriptor. */
+  return (fd);
+}
+/* Return non-zero if the characters from SAMPLE are not all valid
+   characters to be found in the first line of a shell script.  We
+   check up to the first newline, or SAMPLE_LEN, whichever comes first.
+   All of the characters must be printable or whitespace. */
+
+int
+check_binary_file (sample, sample_len)
+     char *sample;
+     int sample_len;
+{
+  register int i;
+  unsigned char c;
+
+  for (i = 0; i < sample_len; i++)
+    {
+      c = sample[i];
+      if (c == '\n')
+       return (0);
+      if (c == '\0')
+       return (1);
+    }
+
+  return (0);
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                 Functions to manipulate pipes                   */
+/*                                                                 */
+/* **************************************************************** */
+
+int
+sh_openpipe (pv)
+     int *pv;
+{
+  int r;
+
+  if ((r = pipe (pv)) < 0)
+    return r;
+
+  pv[0] = move_to_high_fd (pv[0], 1, 64);
+  pv[1] = move_to_high_fd (pv[1], 1, 64);
+
+  return 0;  
+}
+
+int
+sh_closepipe (pv)
+     int *pv;
+{
+  if (pv[0] >= 0)
+    close (pv[0]);
+
+  if (pv[1] >= 0)
+    close (pv[1]);
+
+  pv[0] = pv[1] = -1;
+  return 0;
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                 Functions to inspect pathnames                  */
+/*                                                                 */
+/* **************************************************************** */
+
+int
+file_exists (fn)
+     char *fn;
+{
+  struct stat sb;
+
+  return (stat (fn, &sb) == 0);
+}
+
+int
+file_isdir (fn)
+     char *fn;
+{
+  struct stat sb;
+
+  return ((stat (fn, &sb) == 0) && S_ISDIR (sb.st_mode));
+}
+
+int
+file_iswdir (fn)
+     char *fn;
+{
+  return (file_isdir (fn) && sh_eaccess (fn, W_OK) == 0);
+}
+
+/* Return 1 if STRING is "." or "..", optionally followed by a directory
+   separator */
+int
+path_dot_or_dotdot (string)
+     const char *string;
+{
+  if (string == 0 || *string == '\0' || *string != '.')
+    return (0);
+
+  /* string[0] == '.' */
+  if (PATHSEP(string[1]) || (string[1] == '.' && PATHSEP(string[2])))
+    return (1);
+
+  return (0);
+}
+
+/* Return 1 if STRING contains an absolute pathname, else 0.  Used by `cd'
+   to decide whether or not to look up a directory name in $CDPATH. */
+int
+absolute_pathname (string)
+     const char *string;
+{
+  if (string == 0 || *string == '\0')
+    return (0);
+
+  if (ABSPATH(string))
+    return (1);
+
+  if (string[0] == '.' && PATHSEP(string[1]))  /* . and ./ */
+    return (1);
+
+  if (string[0] == '.' && string[1] == '.' && PATHSEP(string[2]))      /* .. and ../ */
+    return (1);
+
+  return (0);
+}
+
+/* Return 1 if STRING is an absolute program name; it is absolute if it
+   contains any slashes.  This is used to decide whether or not to look
+   up through $PATH. */
+int
+absolute_program (string)
+     const char *string;
+{
+  return ((char *)mbschr (string, '/') != (char *)NULL);
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                 Functions to manipulate pathnames               */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Turn STRING (a pathname) into an absolute pathname, assuming that
+   DOT_PATH contains the symbolic location of `.'.  This always
+   returns a new string, even if STRING was an absolute pathname to
+   begin with. */
+char *
+make_absolute (string, dot_path)
+     char *string, *dot_path;
+{
+  char *result;
+
+  if (dot_path == 0 || ABSPATH(string))
+#ifdef __CYGWIN__
+    {
+      char pathbuf[PATH_MAX + 1];
+
+      cygwin_conv_to_full_posix_path (string, pathbuf);
+      result = savestring (pathbuf);
+    }
+#else
+    result = savestring (string);
+#endif
+  else
+    result = sh_makepath (dot_path, string, 0);
+
+  return (result);
+}
+
+/* Return the `basename' of the pathname in STRING (the stuff after the
+   last '/').  If STRING is `/', just return it. */
+char *
+base_pathname (string)
+     char *string;
+{
+  char *p;
+
+#if 0
+  if (absolute_pathname (string) == 0)
+    return (string);
+#endif
+
+  if (string[0] == '/' && string[1] == 0)
+    return (string);
+
+  p = (char *)strrchr (string, '/');
+  return (p ? ++p : string);
+}
+
+/* Return the full pathname of FILE.  Easy.  Filenames that begin
+   with a '/' are returned as themselves.  Other filenames have
+   the current working directory prepended.  A new string is
+   returned in either case. */
+char *
+full_pathname (file)
+     char *file;
+{
+  char *ret;
+
+  file = (*file == '~') ? bash_tilde_expand (file, 0) : savestring (file);
+
+  if (ABSPATH(file))
+    return (file);
+
+  ret = sh_makepath ((char *)NULL, file, (MP_DOCWD|MP_RMDOT));
+  free (file);
+
+  return (ret);
+}
+
+/* A slightly related function.  Get the prettiest name of this
+   directory possible. */
+static char tdir[PATH_MAX];
+
+/* Return a pretty pathname.  If the first part of the pathname is
+   the same as $HOME, then replace that with `~'.  */
+char *
+polite_directory_format (name)
+     char *name;
+{
+  char *home;
+  int l;
+
+  home = get_string_value ("HOME");
+  l = home ? strlen (home) : 0;
+  if (l > 1 && strncmp (home, name, l) == 0 && (!name[l] || name[l] == '/'))
+    {
+      strncpy (tdir + 1, name + l, sizeof(tdir) - 2);
+      tdir[0] = '~';
+      tdir[sizeof(tdir) - 1] = '\0';
+      return (tdir);
+    }
+  else
+    return (name);
+}
+
+/* Trim NAME.  If NAME begins with `~/', skip over tilde prefix.  Trim to
+   keep any tilde prefix and PROMPT_DIRTRIM trailing directory components
+   and replace the intervening characters with `...' */
+char *
+trim_pathname (name, maxlen)
+     char *name;
+     int maxlen;
+{
+  int nlen, ndirs;
+  intmax_t nskip;
+  char *nbeg, *nend, *ntail, *v;
+
+  if (name == 0 || (nlen = strlen (name)) == 0)
+    return name;
+  nend = name + nlen;
+
+  v = get_string_value ("PROMPT_DIRTRIM");
+  if (v == 0 || *v == 0)
+    return name;
+  if (legal_number (v, &nskip) == 0 || nskip <= 0)
+    return name;
+
+  /* Skip over tilde prefix */
+  nbeg = name;
+  if (name[0] == '~')
+    for (nbeg = name; *nbeg; nbeg++)
+      if (*nbeg == '/')
+       {
+         nbeg++;
+         break;
+       }
+  if (*nbeg == 0)
+    return name;
+
+  for (ndirs = 0, ntail = nbeg; *ntail; ntail++)
+    if (*ntail == '/')
+      ndirs++;
+  if (ndirs < nskip)
+    return name;
+
+  for (ntail = (*nend == '/') ? nend : nend - 1; ntail > nbeg; ntail--)
+    {
+      if (*ntail == '/')
+       nskip--;
+      if (nskip == 0)
+       break;
+    }
+  if (ntail == nbeg)
+    return name;
+
+  /* Now we want to return name[0..nbeg]+"..."+ntail, modifying name in place */
+  nlen = ntail - nbeg;
+  if (nlen <= 3)
+    return name;
+
+  *nbeg++ = '.';
+  *nbeg++ = '.';
+  *nbeg++ = '.';
+
+  nlen = nend - ntail;
+  memcpy (nbeg, ntail, nlen);
+  nbeg[nlen] = '\0';
+
+  return name;
+}
+
+/* Given a string containing units of information separated by colons,
+   return the next one pointed to by (P_INDEX), or NULL if there are no more.
+   Advance (P_INDEX) to the character after the colon. */
+char *
+extract_colon_unit (string, p_index)
+     char *string;
+     int *p_index;
+{
+  int i, start, len;
+  char *value;
+
+  if (string == 0)
+    return (string);
+
+  len = strlen (string);
+  if (*p_index >= len)
+    return ((char *)NULL);
+
+  i = *p_index;
+
+  /* Each call to this routine leaves the index pointing at a colon if
+     there is more to the path.  If I is > 0, then increment past the
+     `:'.  If I is 0, then the path has a leading colon.  Trailing colons
+     are handled OK by the `else' part of the if statement; an empty
+     string is returned in that case. */
+  if (i && string[i] == ':')
+    i++;
+
+  for (start = i; string[i] && string[i] != ':'; i++)
+    ;
+
+  *p_index = i;
+
+  if (i == start)
+    {
+      if (string[i])
+       (*p_index)++;
+      /* Return "" in the case of a trailing `:'. */
+      value = (char *)xmalloc (1);
+      value[0] = '\0';
+    }
+  else
+    value = substring (string, start, i);
+
+  return (value);
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                 Tilde Initialization and Expansion              */
+/*                                                                 */
+/* **************************************************************** */
+
+#if defined (PUSHD_AND_POPD)
+extern char *get_dirstack_from_string __P((char *));
+#endif
+
+static char **bash_tilde_prefixes;
+static char **bash_tilde_prefixes2;
+static char **bash_tilde_suffixes;
+static char **bash_tilde_suffixes2;
+
+/* If tilde_expand hasn't been able to expand the text, perhaps it
+   is a special shell expansion.  This function is installed as the
+   tilde_expansion_preexpansion_hook.  It knows how to expand ~- and ~+.
+   If PUSHD_AND_POPD is defined, ~[+-]N expands to directories from the
+   directory stack. */
+static char *
+bash_special_tilde_expansions (text)
+     char *text;
+{
+  char *result;
+
+  result = (char *)NULL;
+
+  if (text[0] == '+' && text[1] == '\0')
+    result = get_string_value ("PWD");
+  else if (text[0] == '-' && text[1] == '\0')
+    result = get_string_value ("OLDPWD");
+#if defined (PUSHD_AND_POPD)
+  else if (DIGIT (*text) || ((*text == '+' || *text == '-') && DIGIT (text[1])))
+    result = get_dirstack_from_string (text);
+#endif
+
+  return (result ? savestring (result) : (char *)NULL);
+}
+
+/* Initialize the tilde expander.  In Bash, we handle `~-' and `~+', as
+   well as handling special tilde prefixes; `:~" and `=~' are indications
+   that we should do tilde expansion. */
+void
+tilde_initialize ()
+{
+  static int times_called = 0;
+
+  /* Tell the tilde expander that we want a crack first. */
+  tilde_expansion_preexpansion_hook = bash_special_tilde_expansions;
+
+  /* Tell the tilde expander about special strings which start a tilde
+     expansion, and the special strings that end one.  Only do this once.
+     tilde_initialize () is called from within bashline_reinitialize (). */
+  if (times_called++ == 0)
+    {
+      bash_tilde_prefixes = strvec_create (3);
+      bash_tilde_prefixes[0] = "=~";
+      bash_tilde_prefixes[1] = ":~";
+      bash_tilde_prefixes[2] = (char *)NULL;
+
+      bash_tilde_prefixes2 = strvec_create (2);
+      bash_tilde_prefixes2[0] = ":~";
+      bash_tilde_prefixes2[1] = (char *)NULL;
+
+      tilde_additional_prefixes = bash_tilde_prefixes;
+
+      bash_tilde_suffixes = strvec_create (3);
+      bash_tilde_suffixes[0] = ":";
+      bash_tilde_suffixes[1] = "=~";   /* XXX - ?? */
+      bash_tilde_suffixes[2] = (char *)NULL;
+
+      tilde_additional_suffixes = bash_tilde_suffixes;
+
+      bash_tilde_suffixes2 = strvec_create (2);
+      bash_tilde_suffixes2[0] = ":";
+      bash_tilde_suffixes2[1] = (char *)NULL;
+    }
+}
+
+/* POSIX.2, 3.6.1:  A tilde-prefix consists of an unquoted tilde character
+   at the beginning of the word, followed by all of the characters preceding
+   the first unquoted slash in the word, or all the characters in the word
+   if there is no slash...If none of the characters in the tilde-prefix are
+   quoted, the characters in the tilde-prefix following the tilde shell be
+   treated as a possible login name. */
+
+#define TILDE_END(c)   ((c) == '\0' || (c) == '/' || (c) == ':')
+
+static int
+unquoted_tilde_word (s)
+     const char *s;
+{
+  const char *r;
+
+  for (r = s; TILDE_END(*r) == 0; r++)
+    {
+      switch (*r)
+       {
+       case '\\':
+       case '\'':
+       case '"':
+         return 0;
+       }
+    }
+  return 1;
+}
+
+/* Find the end of the tilde-prefix starting at S, and return the tilde
+   prefix in newly-allocated memory.  Return the length of the string in
+   *LENP.  FLAGS tells whether or not we're in an assignment context --
+   if so, `:' delimits the end of the tilde prefix as well. */
+char *
+bash_tilde_find_word (s, flags, lenp)
+     const char *s;
+     int flags, *lenp;
+{
+  const char *r;
+  char *ret;
+  int l;
+
+  for (r = s; *r && *r != '/'; r++)
+    {
+      /* Short-circuit immediately if we see a quote character.  Even though
+        POSIX says that `the first unquoted slash' (or `:') terminates the
+        tilde-prefix, in practice, any quoted portion of the tilde prefix
+        will cause it to not be expanded. */
+      if (*r == '\\' || *r == '\'' || *r == '"')  
+       {
+         ret = savestring (s);
+         if (lenp)
+           *lenp = 0;
+         return ret;
+       }
+      else if (flags && *r == ':')
+       break;
+    }
+  l = r - s;
+  ret = xmalloc (l + 1);
+  strncpy (ret, s, l);
+  ret[l] = '\0';
+  if (lenp)
+    *lenp = l;
+  return ret;
+}
+    
+/* Tilde-expand S by running it through the tilde expansion library.
+   ASSIGN_P is 1 if this is a variable assignment, so the alternate
+   tilde prefixes should be enabled (`=~' and `:~', see above).  If
+   ASSIGN_P is 2, we are expanding the rhs of an assignment statement,
+   so `=~' is not valid. */
+char *
+bash_tilde_expand (s, assign_p)
+     const char *s;
+     int assign_p;
+{
+  int old_immed, old_term, r;
+  char *ret;
+
+  old_immed = interrupt_immediately;
+  old_term = terminate_immediately;
+  interrupt_immediately = terminate_immediately = 1;
+
+  tilde_additional_prefixes = assign_p == 0 ? (char **)0
+                                           : (assign_p == 2 ? bash_tilde_prefixes2 : bash_tilde_prefixes);
+  if (assign_p == 2)
+    tilde_additional_suffixes = bash_tilde_suffixes2;
+
+  r = (*s == '~') ? unquoted_tilde_word (s) : 1;
+  ret = r ? tilde_expand (s) : savestring (s);
+  interrupt_immediately = old_immed;
+  terminate_immediately = old_term;
+  return (ret);
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*       Functions to manipulate and search the group list         */
+/*                                                                 */
+/* **************************************************************** */
+
+static int ngroups, maxgroups;
+
+/* The set of groups that this user is a member of. */
+static GETGROUPS_T *group_array = (GETGROUPS_T *)NULL;
+
+#if !defined (NOGROUP)
+#  define NOGROUP (gid_t) -1
+#endif
+
+static void
+initialize_group_array ()
+{
+  register int i;
+
+  if (maxgroups == 0)
+    maxgroups = getmaxgroups ();
+
+  ngroups = 0;
+  group_array = (GETGROUPS_T *)xrealloc (group_array, maxgroups * sizeof (GETGROUPS_T));
+
+#if defined (HAVE_GETGROUPS)
+  ngroups = getgroups (maxgroups, group_array);
+#endif
+
+  /* If getgroups returns nothing, or the OS does not support getgroups(),
+     make sure the groups array includes at least the current gid. */
+  if (ngroups == 0)
+    {
+      group_array[0] = current_user.gid;
+      ngroups = 1;
+    }
+
+  /* If the primary group is not in the groups array, add it as group_array[0]
+     and shuffle everything else up 1, if there's room. */
+  for (i = 0; i < ngroups; i++)
+    if (current_user.gid == (gid_t)group_array[i])
+      break;
+  if (i == ngroups && ngroups < maxgroups)
+    {
+      for (i = ngroups; i > 0; i--)
+       group_array[i] = group_array[i - 1];
+      group_array[0] = current_user.gid;
+      ngroups++;
+    }
+
+  /* If the primary group is not group_array[0], swap group_array[0] and
+     whatever the current group is.  The vast majority of systems should
+     not need this; a notable exception is Linux. */
+  if (group_array[0] != current_user.gid)
+    {
+      for (i = 0; i < ngroups; i++)
+       if (group_array[i] == current_user.gid)
+         break;
+      if (i < ngroups)
+       {
+         group_array[i] = group_array[0];
+         group_array[0] = current_user.gid;
+       }
+    }
+}
+
+/* Return non-zero if GID is one that we have in our groups list. */
+int
+#if defined (__STDC__) || defined ( _MINIX)
+group_member (gid_t gid)
+#else
+group_member (gid)
+     gid_t gid;
+#endif /* !__STDC__ && !_MINIX */
+{
+#if defined (HAVE_GETGROUPS)
+  register int i;
+#endif
+
+  /* Short-circuit if possible, maybe saving a call to getgroups(). */
+  if (gid == current_user.gid || gid == current_user.egid)
+    return (1);
+
+#if defined (HAVE_GETGROUPS)
+  if (ngroups == 0)
+    initialize_group_array ();
+
+  /* In case of error, the user loses. */
+  if (ngroups <= 0)
+    return (0);
+
+  /* Search through the list looking for GID. */
+  for (i = 0; i < ngroups; i++)
+    if (gid == (gid_t)group_array[i])
+      return (1);
+#endif
+
+  return (0);
+}
+
+char **
+get_group_list (ngp)
+     int *ngp;
+{
+  static char **group_vector = (char **)NULL;
+  register int i;
+
+  if (group_vector)
+    {
+      if (ngp)
+       *ngp = ngroups;
+      return group_vector;
+    }
+
+  if (ngroups == 0)
+    initialize_group_array ();
+
+  if (ngroups <= 0)
+    {
+      if (ngp)
+       *ngp = 0;
+      return (char **)NULL;
+    }
+
+  group_vector = strvec_create (ngroups);
+  for (i = 0; i < ngroups; i++)
+    group_vector[i] = itos (group_array[i]);
+
+  if (ngp)
+    *ngp = ngroups;
+  return group_vector;
+}
+
+int *
+get_group_array (ngp)
+     int *ngp;
+{
+  int i;
+  static int *group_iarray = (int *)NULL;
+
+  if (group_iarray)
+    {
+      if (ngp)
+       *ngp = ngroups;
+      return (group_iarray);
+    }
+
+  if (ngroups == 0)
+    initialize_group_array ();    
+
+  if (ngroups <= 0)
+    {
+      if (ngp)
+       *ngp = 0;
+      return (int *)NULL;
+    }
+
+  group_iarray = (int *)xmalloc (ngroups * sizeof (int));
+  for (i = 0; i < ngroups; i++)
+    group_iarray[i] = (int)group_array[i];
+
+  if (ngp)
+    *ngp = ngroups;
+  return group_iarray;
+}
diff --git a/jobs.c b/jobs.c
index 2f21b17152c44291bbce480b2b4d37bc6521fb22..aef4164cfc9254ebfbce49fd9383ff4f7355febb 100644 (file)
--- a/jobs.c
+++ b/jobs.c
@@ -3669,7 +3669,8 @@ initialize_job_control (force)
       if (shell_tty == -1)
        shell_tty = dup (fileno (stderr));      /* fd 2 */
 
-      shell_tty = move_to_high_fd (shell_tty, 1, -1);
+      if (shell_tty != -1)
+       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. */
diff --git a/jobs.c~ b/jobs.c~
index 17615c712de9645f91f83084891aa9efe107c3e2..2f21b17152c44291bbce480b2b4d37bc6521fb22 100644 (file)
--- a/jobs.c~
+++ b/jobs.c~
@@ -45,7 +45,9 @@
 
 #include "filecntl.h"
 #include <sys/ioctl.h>
+#ifdef HAVE_SYS_PARAM_H
 #include <sys/param.h>
+#endif
 
 #if defined (BUFFERED_INPUT)
 #  include "input.h"
index 2cea8a6247d751a081bef82ef70dd51e1aec6929..bfd46ce2ca2fb0c8e36772f0b40fede842f47677 100644 (file)
@@ -404,7 +404,6 @@ finddirs (pat, sdir, flags, ep, np)
 
   return ret;
 }
-
        
 /* Return a vector of names of files in directory DIR
    whose names match glob pattern PAT.
@@ -475,7 +474,7 @@ glob_vector (pat, dir, flags)
       skip = 1;
     }
 
-  patlen = strlen (pat);
+  patlen = (pat && *pat) ? strlen (pat) : 0;
 
   /* If the filename pattern (PAT) does not contain any globbing characters,
      we can dispense with reading the directory, and just see if there is
@@ -493,7 +492,11 @@ glob_vector (pat, dir, flags)
       nextname = (char *)malloc (dirlen + patlen + 2);
       npat = (char *)malloc (patlen + 1);
       if (nextname == 0 || npat == 0)
-       lose = 1;
+        {
+          FREE (nextname);
+          FREE (npat);
+         lose = 1;
+        }
       else
        {
          strcpy (npat, pat);
@@ -515,7 +518,10 @@ glob_vector (pat, dir, flags)
                  count = 1;
                }
              else
-               lose = 1;
+               {
+                 free (npat);
+                 lose = 1;
+               }
            }
          else
            {
@@ -632,6 +638,8 @@ glob_vector (pat, dir, flags)
              nextname = (char *) malloc (sdlen + 1);
              if (nextlink == 0 || nextname == 0)
                {
+                 FREE (nextlink);
+                 FREE (nextname);
                  free (subdir);
                  lose = 1;
                  break;
@@ -644,6 +652,8 @@ glob_vector (pat, dir, flags)
              ++count;
              continue;
            }
+         else if (flags & GX_MATCHDIRS)
+           free (subdir);
 
          convfn = fnx_fromfs (dp->d_name, D_NAMLEN (dp));
          if (strmatch (pat, convfn, mflags) != FNM_NOMATCH)
@@ -663,6 +673,8 @@ glob_vector (pat, dir, flags)
              nextname = (char *) malloc (D_NAMLEN (dp) + 1);
              if (nextlink == 0 || nextname == 0)
                {
+                 FREE (nextlink);
+                 FREE (nextname);
                  lose = 1;
                  break;
                }
@@ -686,7 +698,11 @@ glob_vector (pat, dir, flags)
       nextname = (char *)malloc (sdlen + 1);
       nextlink = (struct globval *) malloc (sizeof (struct globval));
       if (nextlink == 0 || nextname == 0)
-       lose = 1;
+       {
+         FREE (nextlink);
+         FREE (nextname);
+         lose = 1;
+       }
       else
        {
          nextlink->name = nextname;
@@ -812,7 +828,13 @@ glob_dir_to_array (dir, array, flags)
       result[i] = (char *) malloc (l + strlen (array[i]) + 3);
 
       if (result[i] == NULL)
-       return (NULL);
+       {
+         int ind;
+         for (ind = 0; ind < i; ind++)
+           free (result[ind]);
+         free (result);
+         return (NULL);
+       }
 
       strcpy (result[i], dir);
       if (add_slash)
@@ -983,6 +1005,8 @@ glob_filename (pathname, flags)
              /* Note that the elements of ARRAY are not freed.  */
              if (array != temp_results)
                free ((char *) array);
+             else if ((dflags & GX_ALLDIRS) && filename[0] == '*' && filename[1] == '*' && filename[2] == '\0')
+               free (temp_results);    /* expanding ** case above */
            }
        }
       /* Free the directories.  */
diff --git a/lib/glob/glob.c~ b/lib/glob/glob.c~
new file mode 100644 (file)
index 0000000..cb7d242
--- /dev/null
@@ -0,0 +1,1129 @@
+/* glob.c -- file-name wildcard pattern matching for Bash.
+
+   Copyright (C) 1985-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/>.
+*/
+
+/* To whomever it may concern: I have never seen the code which most
+   Unix programs use to perform this function.  I wrote this from scratch
+   based on specifications for the pattern matching.  --RMS.  */
+
+#include <config.h>
+
+#if !defined (__GNUC__) && !defined (HAVE_ALLOCA_H) && defined (_AIX)
+  #pragma alloca
+#endif /* _AIX && RISC6000 && !__GNUC__ */
+
+#include "bashtypes.h"
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#include "bashansi.h"
+#include "posixdir.h"
+#include "posixstat.h"
+#include "shmbutil.h"
+#include "xmalloc.h"
+
+#include "filecntl.h"
+#if !defined (F_OK)
+#  define F_OK 0
+#endif
+
+#include "stdc.h"
+#include "memalloc.h"
+
+#include "shell.h"
+
+#include "glob.h"
+#include "strmatch.h"
+
+#if !defined (HAVE_BCOPY) && !defined (bcopy)
+#  define bcopy(s, d, n) ((void) memcpy ((d), (s), (n)))
+#endif /* !HAVE_BCOPY && !bcopy */
+
+#if !defined (NULL)
+#  if defined (__STDC__)
+#    define NULL ((void *) 0)
+#  else
+#    define NULL 0x0
+#  endif /* __STDC__ */
+#endif /* !NULL */
+
+#if !defined (FREE)
+#  define FREE(x)      if (x) free (x)
+#endif
+
+/* Don't try to alloca() more than this much memory for `struct globval'
+   in glob_vector() */
+#ifndef ALLOCA_MAX
+#  define ALLOCA_MAX   100000
+#endif
+
+struct globval
+  {
+    struct globval *next;
+    char *name;
+  };
+
+extern void throw_to_top_level __P((void));
+extern int sh_eaccess __P((char *, int));
+extern char *sh_makepath __P((const char *, const char *, int));
+
+extern int extended_glob;
+
+/* Global variable which controls whether or not * matches .*.
+   Non-zero means don't match .*.  */
+int noglob_dot_filenames = 1;
+
+/* Global variable which controls whether or not filename globbing
+   is done without regard to case. */
+int glob_ignore_case = 0;
+
+/* Global variable to return to signify an error in globbing. */
+char *glob_error_return;
+
+static struct globval finddirs_error_return;
+
+/* Some forward declarations. */
+static int skipname __P((char *, char *, int));
+#if HANDLE_MULTIBYTE
+static int mbskipname __P((char *, char *, int));
+#endif
+#if HANDLE_MULTIBYTE
+static void udequote_pathname __P((char *));
+static void wdequote_pathname __P((char *));
+#else
+#  define dequote_pathname udequote_pathname
+#endif
+static void dequote_pathname __P((char *));
+static int glob_testdir __P((char *));
+static char **glob_dir_to_array __P((char *, char **, int));
+
+/* Compile `glob_loop.c' for single-byte characters. */
+#define CHAR   unsigned char
+#define INT    int
+#define L(CS)  CS
+#define INTERNAL_GLOB_PATTERN_P internal_glob_pattern_p
+#include "glob_loop.c"
+
+/* Compile `glob_loop.c' again for multibyte characters. */
+#if HANDLE_MULTIBYTE
+
+#define CHAR   wchar_t
+#define INT    wint_t
+#define L(CS)  L##CS
+#define INTERNAL_GLOB_PATTERN_P internal_glob_wpattern_p
+#include "glob_loop.c"
+
+#endif /* HANDLE_MULTIBYTE */
+
+/* And now a function that calls either the single-byte or multibyte version
+   of internal_glob_pattern_p. */
+int
+glob_pattern_p (pattern)
+     const char *pattern;
+{
+#if HANDLE_MULTIBYTE
+  size_t n;
+  wchar_t *wpattern;
+  int r;
+
+  if (MB_CUR_MAX == 1)
+    return (internal_glob_pattern_p ((unsigned char *)pattern));
+
+  /* Convert strings to wide chars, and call the multibyte version. */
+  n = xdupmbstowcs (&wpattern, NULL, pattern);
+  if (n == (size_t)-1)
+    /* Oops.  Invalid multibyte sequence.  Try it as single-byte sequence. */
+    return (internal_glob_pattern_p ((unsigned char *)pattern));
+
+  r = internal_glob_wpattern_p (wpattern);
+  free (wpattern);
+
+  return r;
+#else
+  return (internal_glob_pattern_p (pattern));
+#endif
+}
+
+/* Return 1 if DNAME should be skipped according to PAT.  Mostly concerned
+   with matching leading `.'. */
+
+static int
+skipname (pat, dname, flags)
+     char *pat;
+     char *dname;
+     int flags;
+{
+  /* If a leading dot need not be explicitly matched, and the pattern
+     doesn't start with a `.', don't match `.' or `..' */
+  if (noglob_dot_filenames == 0 && pat[0] != '.' &&
+       (pat[0] != '\\' || pat[1] != '.') &&
+       (dname[0] == '.' &&
+         (dname[1] == '\0' || (dname[1] == '.' && dname[2] == '\0'))))
+    return 1;
+
+  /* If a dot must be explicity matched, check to see if they do. */
+  else if (noglob_dot_filenames && dname[0] == '.' && pat[0] != '.' &&
+       (pat[0] != '\\' || pat[1] != '.'))
+    return 1;
+
+  return 0;
+}
+
+#if HANDLE_MULTIBYTE
+/* Return 1 if DNAME should be skipped according to PAT.  Handles multibyte
+   characters in PAT and DNAME.  Mostly concerned with matching leading `.'. */
+
+static int
+mbskipname (pat, dname, flags)
+     char *pat, *dname;
+     int flags;
+{
+  int ret;
+  wchar_t *pat_wc, *dn_wc;
+  size_t pat_n, dn_n;
+
+  pat_wc = dn_wc = (wchar_t *)NULL;
+
+  pat_n = xdupmbstowcs (&pat_wc, NULL, pat);
+  if (pat_n != (size_t)-1)
+    dn_n = xdupmbstowcs (&dn_wc, NULL, dname);
+
+  ret = 0;
+  if (pat_n != (size_t)-1 && dn_n !=(size_t)-1)
+    {
+      /* If a leading dot need not be explicitly matched, and the
+        pattern doesn't start with a `.', don't match `.' or `..' */
+      if (noglob_dot_filenames == 0 && pat_wc[0] != L'.' &&
+           (pat_wc[0] != L'\\' || pat_wc[1] != L'.') &&
+           (dn_wc[0] == L'.' &&
+             (dn_wc[1] == L'\0' || (dn_wc[1] == L'.' && dn_wc[2] == L'\0'))))
+       ret = 1;
+
+      /* If a leading dot must be explicity matched, check to see if the
+        pattern and dirname both have one. */
+     else if (noglob_dot_filenames && dn_wc[0] == L'.' &&
+          pat_wc[0] != L'.' &&
+          (pat_wc[0] != L'\\' || pat_wc[1] != L'.'))
+       ret = 1;
+    }
+  else
+    ret = skipname (pat, dname, flags);
+
+  FREE (pat_wc);
+  FREE (dn_wc);
+
+  return ret;
+}
+#endif /* HANDLE_MULTIBYTE */
+
+/* Remove backslashes quoting characters in PATHNAME by modifying PATHNAME. */
+static void
+udequote_pathname (pathname)
+     char *pathname;
+{
+  register int i, j;
+
+  for (i = j = 0; pathname && pathname[i]; )
+    {
+      if (pathname[i] == '\\')
+       i++;
+
+      pathname[j++] = pathname[i++];
+
+      if (pathname[i - 1] == 0)
+       break;
+    }
+  if (pathname)
+    pathname[j] = '\0';
+}
+
+#if HANDLE_MULTIBYTE
+/* Remove backslashes quoting characters in PATHNAME by modifying PATHNAME. */
+static void
+wdequote_pathname (pathname)
+     char *pathname;
+{
+  mbstate_t ps;
+  size_t len, n;
+  wchar_t *wpathname;
+  int i, j;
+  wchar_t *orig_wpathname;
+
+  len = strlen (pathname);
+  /* Convert the strings into wide characters.  */
+  n = xdupmbstowcs (&wpathname, NULL, pathname);
+  if (n == (size_t) -1)
+    /* Something wrong.  Fall back to single-byte */
+    return udequote_pathname (pathname);
+  orig_wpathname = wpathname;
+
+  for (i = j = 0; wpathname && wpathname[i]; )
+    {
+      if (wpathname[i] == L'\\')
+       i++;
+
+      wpathname[j++] = wpathname[i++];
+
+      if (wpathname[i - 1] == L'\0')
+       break;
+    }
+  if (wpathname)
+    wpathname[j] = L'\0';
+
+  /* Convert the wide character string into unibyte character set. */
+  memset (&ps, '\0', sizeof(mbstate_t));
+  n = wcsrtombs(pathname, (const wchar_t **)&wpathname, len, &ps);
+  pathname[len] = '\0';
+
+  /* Can't just free wpathname here; wcsrtombs changes it in many cases. */
+  free (orig_wpathname);
+}
+
+static void
+dequote_pathname (pathname)
+     char *pathname;
+{
+  if (MB_CUR_MAX > 1)
+    wdequote_pathname (pathname);
+  else
+    udequote_pathname (pathname);
+}
+#endif /* HANDLE_MULTIBYTE */
+
+/* Test whether NAME exists. */
+
+#if defined (HAVE_LSTAT)
+#  define GLOB_TESTNAME(name)  (lstat (name, &finfo))
+#else /* !HAVE_LSTAT */
+#  if !defined (AFS)
+#    define GLOB_TESTNAME(name)  (sh_eaccess (name, F_OK))
+#  else /* AFS */
+#    define GLOB_TESTNAME(name)  (access (name, F_OK))
+#  endif /* AFS */
+#endif /* !HAVE_LSTAT */
+
+/* Return 0 if DIR is a directory, -1 otherwise. */
+static int
+glob_testdir (dir)
+     char *dir;
+{
+  struct stat finfo;
+
+/*itrace("glob_testdir: testing %s", dir);*/
+  if (stat (dir, &finfo) < 0)
+    return (-1);
+
+  if (S_ISDIR (finfo.st_mode) == 0)
+    return (-1);
+
+  return (0);
+}
+
+/* Recursively scan SDIR for directories matching PAT (PAT is always `**').
+   FLAGS is simply passed down to the recursive call to glob_vector.  Returns
+   a list of matching directory names.  EP, if non-null, is set to the last
+   element of the returned list.  NP, if non-null, is set to the number of
+   directories in the returned list.  These two variables exist for the
+   convenience of the caller (always glob_vector). */
+static struct globval *
+finddirs (pat, sdir, flags, ep, np)
+     char *pat;
+     char *sdir;
+     int flags;
+     struct globval **ep;
+     int *np;
+{
+  char **r, *n;
+  int ndirs;
+  struct globval *ret, *e, *g;
+
+/*itrace("finddirs: pat = `%s' sdir = `%s' flags = 0x%x", pat, sdir, flags);*/
+  e = ret = 0;
+  r = glob_vector (pat, sdir, flags);
+  if (r == 0 || r[0] == 0)
+    {
+      if (np)
+       *np = 0;
+      if (ep)
+        *ep = 0;
+      if (r && r != &glob_error_return)
+       free (r);
+      return (struct globval *)0;
+    }
+  for (ndirs = 0; r[ndirs] != 0; ndirs++)
+    {
+      g = (struct globval *) malloc (sizeof (struct globval));
+      if (g == 0)
+       {
+         while (ret)           /* free list built so far */
+           {
+             g = ret->next;
+             free (ret);
+             ret = g;
+           }
+
+         free (r);
+         if (np)
+           *np = 0;
+         if (ep)
+           *ep = 0;
+         return (&finddirs_error_return);
+       }
+      if (e == 0)
+       e = g;
+
+      g->next = ret;
+      ret = g;
+
+      g->name = r[ndirs];
+    }
+
+  free (r);
+  if (ep)
+    *ep = e;
+  if (np)
+    *np = ndirs;
+
+  return ret;
+}
+       
+/* Return a vector of names of files in directory DIR
+   whose names match glob pattern PAT.
+   The names are not in any particular order.
+   Wildcards at the beginning of PAT do not match an initial period.
+
+   The vector is terminated by an element that is a null pointer.
+
+   To free the space allocated, first free the vector's elements,
+   then free the vector.
+
+   Return 0 if cannot get enough memory to hold the pointer
+   and the names.
+
+   Return -1 if cannot access directory DIR.
+   Look in errno for more information.  */
+
+char **
+glob_vector (pat, dir, flags)
+     char *pat;
+     char *dir;
+     int flags;
+{
+  DIR *d;
+  register struct dirent *dp;
+  struct globval *lastlink, *e, *dirlist;
+  register struct globval *nextlink;
+  register char *nextname, *npat, *subdir;
+  unsigned int count;
+  int lose, skip, ndirs, isdir, sdlen, add_current, patlen;
+  register char **name_vector;
+  register unsigned int i;
+  int mflags;          /* Flags passed to strmatch (). */
+  int pflags;          /* flags passed to sh_makepath () */
+  int nalloca;
+  struct globval *firstmalloc, *tmplink;
+  char *convfn;
+
+  lastlink = 0;
+  count = lose = skip = add_current = 0;
+
+  firstmalloc = 0;
+  nalloca = 0;
+
+/*itrace("glob_vector: pat = `%s' dir = `%s' flags = 0x%x", pat, dir, flags);*/
+  /* If PAT is empty, skip the loop, but return one (empty) filename. */
+  if (pat == 0 || *pat == '\0')
+    {
+      if (glob_testdir (dir) < 0)
+       return ((char **) &glob_error_return);
+
+      nextlink = (struct globval *)alloca (sizeof (struct globval));
+      if (nextlink == NULL)
+       return ((char **) NULL);
+
+      nextlink->next = (struct globval *)0;
+      nextname = (char *) malloc (1);
+      if (nextname == 0)
+       lose = 1;
+      else
+       {
+         lastlink = nextlink;
+         nextlink->name = nextname;
+         nextname[0] = '\0';
+         count = 1;
+       }
+
+      skip = 1;
+    }
+
+  patlen = (pat && *pat) ? strlen (pat) : 0;
+
+  /* If the filename pattern (PAT) does not contain any globbing characters,
+     we can dispense with reading the directory, and just see if there is
+     a filename `DIR/PAT'.  If there is, and we can access it, just make the
+     vector to return and bail immediately. */
+  if (skip == 0 && glob_pattern_p (pat) == 0)
+    {
+      int dirlen;
+      struct stat finfo;
+
+      if (glob_testdir (dir) < 0)
+       return ((char **) &glob_error_return);
+
+      dirlen = strlen (dir);
+      nextname = (char *)malloc (dirlen + patlen + 2);
+      npat = (char *)malloc (patlen + 1);
+      if (nextname == 0 || npat == 0)
+        {
+          FREE (nextname);
+          FREE (npat);
+         lose = 1;
+        }
+      else
+       {
+         strcpy (npat, pat);
+         dequote_pathname (npat);
+
+         strcpy (nextname, dir);
+         nextname[dirlen++] = '/';
+         strcpy (nextname + dirlen, npat);
+
+         if (GLOB_TESTNAME (nextname) >= 0)
+           {
+             free (nextname);
+             nextlink = (struct globval *)alloca (sizeof (struct globval));
+             if (nextlink)
+               {
+                 nextlink->next = (struct globval *)0;
+                 lastlink = nextlink;
+                 nextlink->name = npat;
+                 count = 1;
+               }
+             else
+               {
+                 free (npat);
+                 lose = 1;
+               }
+           }
+         else
+           {
+             free (nextname);
+             free (npat);
+           }
+       }
+
+      skip = 1;
+    }
+
+  if (skip == 0)
+    {
+      /* Open the directory, punting immediately if we cannot.  If opendir
+        is not robust (i.e., it opens non-directories successfully), test
+        that DIR is a directory and punt if it's not. */
+#if defined (OPENDIR_NOT_ROBUST)
+      if (glob_testdir (dir) < 0)
+       return ((char **) &glob_error_return);
+#endif
+
+      d = opendir (dir);
+      if (d == NULL)
+       return ((char **) &glob_error_return);
+
+      /* Compute the flags that will be passed to strmatch().  We don't
+        need to do this every time through the loop. */
+      mflags = (noglob_dot_filenames ? FNM_PERIOD : 0) | FNM_PATHNAME;
+
+#ifdef FNM_CASEFOLD
+      if (glob_ignore_case)
+       mflags |= FNM_CASEFOLD;
+#endif
+
+      if (extended_glob)
+       mflags |= FNM_EXTMATCH;
+
+      add_current = ((flags & (GX_ALLDIRS|GX_ADDCURDIR)) == (GX_ALLDIRS|GX_ADDCURDIR));
+
+      /* Scan the directory, finding all names that match.
+        For each name that matches, allocate a struct globval
+        on the stack and store the name in it.
+        Chain those structs together; lastlink is the front of the chain.  */
+      while (1)
+       {
+         /* Make globbing interruptible in the shell. */
+         if (interrupt_state || terminating_signal)
+           {
+             lose = 1;
+             break;
+           }
+         
+         dp = readdir (d);
+         if (dp == NULL)
+           break;
+
+         /* If this directory entry is not to be used, try again. */
+         if (REAL_DIR_ENTRY (dp) == 0)
+           continue;
+
+#if 0
+         if (dp->d_name == 0 || *dp->d_name == 0)
+           continue;
+#endif
+
+#if HANDLE_MULTIBYTE
+         if (MB_CUR_MAX > 1 && mbskipname (pat, dp->d_name, flags))
+           continue;
+         else
+#endif
+         if (skipname (pat, dp->d_name, flags))
+           continue;
+
+         /* If we're only interested in directories, don't bother with files */
+         if (flags & (GX_MATCHDIRS|GX_ALLDIRS))
+           {
+             pflags = (flags & GX_ALLDIRS) ? MP_RMDOT : 0;
+             if (flags & GX_NULLDIR)
+               pflags |= MP_IGNDOT;
+             subdir = sh_makepath (dir, dp->d_name, pflags);
+             isdir = glob_testdir (subdir);
+             if (isdir < 0 && (flags & GX_MATCHDIRS))
+               {
+                 free (subdir);
+                 continue;
+               }
+           }
+
+         if (flags & GX_ALLDIRS)
+           {
+             if (isdir == 0)
+               {
+                 dirlist = finddirs (pat, subdir, (flags & ~GX_ADDCURDIR), &e, &ndirs);
+                 if (dirlist == &finddirs_error_return)
+                   {
+                     free (subdir);
+                     lose = 1;
+                     break;
+                   }
+                 if (ndirs)            /* add recursive directories to list */
+                   {
+                     if (firstmalloc == 0)
+                       firstmalloc = e;
+                     e->next = lastlink;
+                     lastlink = dirlist;
+                     count += ndirs;
+                   }
+               }
+
+             nextlink = (struct globval *) malloc (sizeof (struct globval));
+             if (firstmalloc == 0)
+               firstmalloc = nextlink;
+             sdlen = strlen (subdir);
+             nextname = (char *) malloc (sdlen + 1);
+             if (nextlink == 0 || nextname == 0)
+               {
+                 FREE (nextlink);
+                 FREE (nextname);
+                 free (subdir);
+                 lose = 1;
+                 break;
+               }
+             nextlink->next = lastlink;
+             lastlink = nextlink;
+             nextlink->name = nextname;
+             bcopy (subdir, nextname, sdlen + 1);
+             free (subdir);
+             ++count;
+             continue;
+           }
+         else if (flags & GX_MATCHDIRS)
+           free (subdir);
+
+         convfn = fnx_fromfs (dp->d_name, D_NAMLEN (dp));
+         if (strmatch (pat, convfn, mflags) != FNM_NOMATCH)
+           {
+             if (nalloca < ALLOCA_MAX)
+               {
+                 nextlink = (struct globval *) alloca (sizeof (struct globval));
+                 nalloca += sizeof (struct globval);
+               }
+             else
+               {
+                 nextlink = (struct globval *) malloc (sizeof (struct globval));
+                 if (firstmalloc == 0)
+                   firstmalloc = nextlink;
+               }
+
+             nextname = (char *) malloc (D_NAMLEN (dp) + 1);
+             if (nextlink == 0 || nextname == 0)
+               {
+                 FREE (nextlink);
+                 FREE (nextname);
+                 lose = 1;
+                 break;
+               }
+             nextlink->next = lastlink;
+             lastlink = nextlink;
+             nextlink->name = nextname;
+             bcopy (dp->d_name, nextname, D_NAMLEN (dp) + 1);
+             ++count;
+           }
+       }
+
+      (void) closedir (d);
+    }
+
+  /* compat: if GX_ADDCURDIR, add the passed directory also.  Add an empty
+     directory name as a placeholder if GX_NULLDIR (in which case the passed
+     directory name is "."). */
+  if (add_current)
+    {
+      sdlen = strlen (dir);
+      nextname = (char *)malloc (sdlen + 1);
+      nextlink = (struct globval *) malloc (sizeof (struct globval));
+      if (nextlink == 0 || nextname == 0)
+       {
+         FREE (nextlink);
+         FREE (nextname);
+         lose = 1;
+       }
+      else
+       {
+         nextlink->name = nextname;
+         nextlink->next = lastlink;
+         lastlink = nextlink;
+         if (flags & GX_NULLDIR)
+           nextname[0] = '\0';
+         else
+           bcopy (dir, nextname, sdlen + 1);
+         ++count;
+       }
+    }
+
+  if (lose == 0)
+    {
+      name_vector = (char **) malloc ((count + 1) * sizeof (char *));
+      lose |= name_vector == NULL;
+    }
+
+  /* Have we run out of memory?         */
+  if (lose)
+    {
+      tmplink = 0;
+
+      /* Here free the strings we have got.  */
+      while (lastlink)
+       {
+         /* Since we build the list in reverse order, the first N entries
+            will be allocated with malloc, if firstmalloc is set, from
+            lastlink to firstmalloc. */
+         if (firstmalloc)
+           {
+             if (lastlink == firstmalloc)
+               firstmalloc = 0;
+             tmplink = lastlink;
+           }
+         else
+           tmplink = 0;
+         free (lastlink->name);
+         lastlink = lastlink->next;
+         FREE (tmplink);
+       }
+
+      QUIT;
+
+      return ((char **)NULL);
+    }
+
+  /* Copy the name pointers from the linked list into the vector.  */
+  for (tmplink = lastlink, i = 0; i < count; ++i)
+    {
+      name_vector[i] = tmplink->name;
+      tmplink = tmplink->next;
+    }
+
+  name_vector[count] = NULL;
+
+  /* If we allocated some of the struct globvals, free them now. */
+  if (firstmalloc)
+    {
+      tmplink = 0;
+      while (lastlink)
+       {
+         tmplink = lastlink;
+         if (lastlink == firstmalloc)
+           lastlink = firstmalloc = 0;
+         else
+           lastlink = lastlink->next;
+         free (tmplink);
+       }
+    }
+
+  return (name_vector);
+}
+
+/* Return a new array which is the concatenation of each string in ARRAY
+   to DIR.  This function expects you to pass in an allocated ARRAY, and
+   it takes care of free()ing that array.  Thus, you might think of this
+   function as side-effecting ARRAY.  This should handle GX_MARKDIRS. */
+static char **
+glob_dir_to_array (dir, array, flags)
+     char *dir, **array;
+     int flags;
+{
+  register unsigned int i, l;
+  int add_slash;
+  char **result, *new;
+  struct stat sb;
+
+  l = strlen (dir);
+  if (l == 0)
+    {
+      if (flags & GX_MARKDIRS)
+       for (i = 0; array[i]; i++)
+         {
+           if ((stat (array[i], &sb) == 0) && S_ISDIR (sb.st_mode))
+             {
+               l = strlen (array[i]);
+               new = (char *)realloc (array[i], l + 2);
+               if (new == 0)
+                 return NULL;
+               new[l] = '/';
+               new[l+1] = '\0';
+               array[i] = new;
+             }
+         }
+      return (array);
+    }
+
+  add_slash = dir[l - 1] != '/';
+
+  i = 0;
+  while (array[i] != NULL)
+    ++i;
+
+  result = (char **) malloc ((i + 1) * sizeof (char *));
+  if (result == NULL)
+    return (NULL);
+
+  for (i = 0; array[i] != NULL; i++)
+    {
+      /* 3 == 1 for NUL, 1 for slash at end of DIR, 1 for GX_MARKDIRS */
+      result[i] = (char *) malloc (l + strlen (array[i]) + 3);
+
+      if (result[i] == NULL)
+       {
+         int ind;
+         for (ind = 0; ind < i; ind++)
+           free (result[ind]);
+         free (result);
+         return (NULL);
+       }
+
+      strcpy (result[i], dir);
+      if (add_slash)
+       result[i][l] = '/';
+      strcpy (result[i] + l + add_slash, array[i]);
+      if (flags & GX_MARKDIRS)
+       {
+         if ((stat (result[i], &sb) == 0) && S_ISDIR (sb.st_mode))
+           {
+             size_t rlen;
+             rlen = strlen (result[i]);
+             result[i][rlen] = '/';
+             result[i][rlen+1] = '\0';
+           }
+       }
+    }
+  result[i] = NULL;
+
+  /* Free the input array.  */
+  for (i = 0; array[i] != NULL; i++)
+    free (array[i]);
+  free ((char *) array);
+
+  return (result);
+}
+
+/* Do globbing on PATHNAME.  Return an array of pathnames that match,
+   marking the end of the array with a null-pointer as an element.
+   If no pathnames match, then the array is empty (first element is null).
+   If there isn't enough memory, then return NULL.
+   If a file system error occurs, return -1; `errno' has the error code.  */
+char **
+glob_filename (pathname, flags)
+     char *pathname;
+     int flags;
+{
+  char **result;
+  unsigned int result_size;
+  char *directory_name, *filename, *dname;
+  unsigned int directory_len;
+  int free_dirname;                    /* flag */
+  int dflags;
+
+  result = (char **) malloc (sizeof (char *));
+  result_size = 1;
+  if (result == NULL)
+    return (NULL);
+
+  result[0] = NULL;
+
+  directory_name = NULL;
+
+  /* Find the filename.  */
+  filename = strrchr (pathname, '/');
+  if (filename == NULL)
+    {
+      filename = pathname;
+      directory_name = "";
+      directory_len = 0;
+      free_dirname = 0;
+    }
+  else
+    {
+      directory_len = (filename - pathname) + 1;
+      directory_name = (char *) malloc (directory_len + 1);
+
+      if (directory_name == 0)         /* allocation failed? */
+       return (NULL);
+
+      bcopy (pathname, directory_name, directory_len);
+      directory_name[directory_len] = '\0';
+      ++filename;
+      free_dirname = 1;
+    }
+
+  /* If directory_name contains globbing characters, then we
+     have to expand the previous levels.  Just recurse. */
+  if (glob_pattern_p (directory_name))
+    {
+      char **directories;
+      register unsigned int i;
+
+      dflags = flags & ~GX_MARKDIRS;
+      if ((flags & GX_GLOBSTAR) && directory_name[0] == '*' && directory_name[1] == '*' && (directory_name[2] == '/' || directory_name[2] == '\0'))
+       dflags |= GX_ALLDIRS|GX_ADDCURDIR;
+
+      if (directory_name[directory_len - 1] == '/')
+       directory_name[directory_len - 1] = '\0';
+
+      directories = glob_filename (directory_name, dflags);
+
+      if (free_dirname)
+       {
+         free (directory_name);
+         directory_name = NULL;
+       }
+
+      if (directories == NULL)
+       goto memory_error;
+      else if (directories == (char **)&glob_error_return)
+       {
+         free ((char *) result);
+         return ((char **) &glob_error_return);
+       }
+      else if (*directories == NULL)
+       {
+         free ((char *) directories);
+         free ((char *) result);
+         return ((char **) &glob_error_return);
+       }
+
+      /* We have successfully globbed the preceding directory name.
+        For each name in DIRECTORIES, call glob_vector on it and
+        FILENAME.  Concatenate the results together.  */
+      for (i = 0; directories[i] != NULL; ++i)
+       {
+         char **temp_results;
+
+         /* XXX -- we've recursively scanned any directories resulting from
+            a `**', so turn off the flag.  We turn it on again below if
+            filename is `**' */
+         /* Scan directory even on a NULL filename.  That way, `*h/'
+            returns only directories ending in `h', instead of all
+            files ending in `h' with a `/' appended. */
+         dname = directories[i];
+         dflags = flags & ~(GX_MARKDIRS|GX_ALLDIRS|GX_ADDCURDIR);
+         if ((flags & GX_GLOBSTAR) && filename[0] == '*' && filename[1] == '*' && filename[2] == '\0')
+           dflags |= GX_ALLDIRS|GX_ADDCURDIR;
+         if (dname[0] == '\0' && filename[0])
+           {
+             dflags |= GX_NULLDIR;
+             dname = ".";      /* treat null directory name and non-null filename as current directory */
+           }
+         temp_results = glob_vector (filename, dname, dflags);
+
+         /* Handle error cases. */
+         if (temp_results == NULL)
+           goto memory_error;
+         else if (temp_results == (char **)&glob_error_return)
+           /* This filename is probably not a directory.  Ignore it.  */
+           ;
+         else
+           {
+             char **array;
+             register unsigned int l;
+
+             /* If we're expanding **, we don't need to glue the directory
+                name to the results; we've already done it in glob_vector */
+             if ((dflags & GX_ALLDIRS) && filename[0] == '*' && filename[1] == '*' && filename[2] == '\0')
+               array = temp_results;
+             else
+               array = glob_dir_to_array (directories[i], temp_results, flags);
+             l = 0;
+             while (array[l] != NULL)
+               ++l;
+
+             result =
+               (char **)realloc (result, (result_size + l) * sizeof (char *));
+
+             if (result == NULL)
+               goto memory_error;
+
+             for (l = 0; array[l] != NULL; ++l)
+               result[result_size++ - 1] = array[l];
+
+             result[result_size - 1] = NULL;
+
+             /* Note that the elements of ARRAY are not freed.  */
+             if (array != temp_results)
+               free ((char *) array);
+             else
+               free (temp_results);    /* expanding ** case above */
+           }
+       }
+      /* Free the directories.  */
+      for (i = 0; directories[i]; i++)
+       free (directories[i]);
+
+      free ((char *) directories);
+
+      return (result);
+    }
+
+  /* If there is only a directory name, return it. */
+  if (*filename == '\0')
+    {
+      result = (char **) realloc ((char *) result, 2 * sizeof (char *));
+      if (result == NULL)
+       return (NULL);
+      /* Handle GX_MARKDIRS here. */
+      result[0] = (char *) malloc (directory_len + 1);
+      if (result[0] == NULL)
+       goto memory_error;
+      bcopy (directory_name, result[0], directory_len + 1);
+      if (free_dirname)
+       free (directory_name);
+      result[1] = NULL;
+      return (result);
+    }
+  else
+    {
+      char **temp_results;
+
+      /* There are no unquoted globbing characters in DIRECTORY_NAME.
+        Dequote it before we try to open the directory since there may
+        be quoted globbing characters which should be treated verbatim. */
+      if (directory_len > 0)
+       dequote_pathname (directory_name);
+
+      /* We allocated a small array called RESULT, which we won't be using.
+        Free that memory now. */
+      free (result);
+
+      /* Just return what glob_vector () returns appended to the
+        directory name. */
+      /* If flags & GX_ALLDIRS, we're called recursively */
+      dflags = flags & ~GX_MARKDIRS;
+      if (directory_len == 0)
+       dflags |= GX_NULLDIR;
+      if ((flags & GX_GLOBSTAR) && filename[0] == '*' && filename[1] == '*' && filename[2] == '\0')
+       {
+         dflags |= GX_ALLDIRS|GX_ADDCURDIR;
+#if 0
+         /* If we want all directories (dflags & GX_ALLDIRS) and we're not
+            being called recursively as something like `echo [star][star]/[star].o'
+            ((flags & GX_ALLDIRS) == 0), we want to prevent glob_vector from
+            adding a null directory name to the front of the temp_results
+            array.  We turn off ADDCURDIR if not called recursively and
+            dlen == 0 */
+#endif
+         if (directory_len == 0 && (flags & GX_ALLDIRS) == 0)
+           dflags &= ~GX_ADDCURDIR;
+       }
+      temp_results = glob_vector (filename,
+                                 (directory_len == 0 ? "." : directory_name),
+                                 dflags);
+
+      if (temp_results == NULL || temp_results == (char **)&glob_error_return)
+       {
+         if (free_dirname)
+           free (directory_name);
+         return (temp_results);
+       }
+
+      result = glob_dir_to_array ((dflags & GX_ALLDIRS) ? "" : directory_name, temp_results, flags);
+      if (free_dirname)
+       free (directory_name);
+      return (result);
+    }
+
+  /* We get to memory_error if the program has run out of memory, or
+     if this is the shell, and we have been interrupted. */
+ memory_error:
+  if (result != NULL)
+    {
+      register unsigned int i;
+      for (i = 0; result[i] != NULL; ++i)
+       free (result[i]);
+      free ((char *) result);
+    }
+
+  if (free_dirname && directory_name)
+    free (directory_name);
+
+  QUIT;
+
+  return (NULL);
+}
+
+#if defined (TEST)
+
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  unsigned int i;
+
+  for (i = 1; i < argc; ++i)
+    {
+      char **value = glob_filename (argv[i], 0);
+      if (value == NULL)
+       puts ("Out of memory.");
+      else if (value == &glob_error_return)
+       perror (argv[i]);
+      else
+       for (i = 0; value[i] != NULL; i++)
+         puts (value[i]);
+    }
+
+  exit (0);
+}
+#endif /* TEST.  */
index 84b99416ff8ed90ac1b32a7d7ca3de6268362a3d..5af8983b73b758900d06d58f12382c53e61a6ab7 100644 (file)
@@ -271,6 +271,8 @@ xdupmbstowcs (destp, indicesp, src)
     {
       if (destp)
        *destp = NULL;
+      if (indicesp)
+       *indicesp = NULL;
       return (size_t)-1;
     }
 
@@ -286,6 +288,8 @@ xdupmbstowcs (destp, indicesp, src)
   if (wsbuf == NULL)
     {
       *destp = NULL;
+      if (indicesp)
+        *indicesp = NULL;
       return (size_t)-1;
     }
 
@@ -297,6 +301,7 @@ xdupmbstowcs (destp, indicesp, src)
        {
          free (wsbuf);
          *destp = NULL;
+         *indicesp = NULL;
          return (size_t)-1;
        }
     }
@@ -331,6 +336,8 @@ xdupmbstowcs (destp, indicesp, src)
          free (wsbuf);
          FREE (indices);
          *destp = NULL;
+         if (indicesp)
+           *indicesp = NULL;
          return (size_t)-1;
        }
 
@@ -350,18 +357,22 @@ xdupmbstowcs (destp, indicesp, src)
              free (wsbuf);
              FREE (indices);
              *destp = NULL;
+             if (indicesp)
+               *indicesp = NULL;
              return (size_t)-1;
            }
          wsbuf = wstmp;
 
          if (indicesp)
            {
-             idxtmp = (char **) realloc (indices, wsbuf_size * sizeof (char **));
+             idxtmp = (char **) realloc (indices, wsbuf_size * sizeof (char *));
              if (idxtmp == NULL)
                {
                  free (wsbuf);
                  free (indices);
                  *destp = NULL;
+                 if (indicesp)
+                   *indicesp = NULL;
                  return (size_t)-1;
                }
              indices = idxtmp;
diff --git a/lib/glob/xmbsrtowcs.c~ b/lib/glob/xmbsrtowcs.c~
new file mode 100644 (file)
index 0000000..3baeee0
--- /dev/null
@@ -0,0 +1,397 @@
+/* xmbsrtowcs.c -- replacement function for mbsrtowcs */
+
+/* Copyright (C) 2002-2011 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/>.
+*/
+
+/* Ask for GNU extensions to get extern declaration for mbsnrtowcs if
+   available via glibc. */
+#ifndef _GNU_SOURCE
+#  define _GNU_SOURCE 1
+#endif
+
+#include <config.h>
+
+#include <bashansi.h>
+
+/* <wchar.h>, <wctype.h> and <stdlib.h> are included in "shmbutil.h".
+   If <wchar.h>, <wctype.h>, mbsrtowcs(), exist, HANDLE_MULTIBYTE
+   is defined as 1. */
+#include <shmbutil.h>
+
+#if HANDLE_MULTIBYTE
+
+#define WSBUF_INC 32
+
+#ifndef FREE
+#  define FREE(x)      do { if (x) free (x); } while (0)
+#endif
+
+#if ! HAVE_STRCHRNUL
+extern char *strchrnul __P((const char *, int));
+#endif
+
+/* On some locales (ex. ja_JP.sjis), mbsrtowc doesn't convert 0x5c to U<0x5c>.
+   So, this function is made for converting 0x5c to U<0x5c>. */
+
+static mbstate_t local_state;
+static int local_state_use = 0;
+
+size_t
+xmbsrtowcs (dest, src, len, pstate)
+    wchar_t *dest;
+    const char **src;
+    size_t len;
+    mbstate_t *pstate;
+{
+  mbstate_t *ps;
+  size_t mblength, wclength, n;
+
+  ps = pstate;
+  if (pstate == NULL)
+    {
+      if (!local_state_use)
+       {
+         memset (&local_state, '\0', sizeof(mbstate_t));
+         local_state_use = 1;
+       }
+      ps = &local_state;
+    }
+
+  n = strlen (*src);
+
+  if (dest == NULL)
+    {
+      wchar_t *wsbuf;
+      const char *mbs;
+      mbstate_t psbuf;
+
+      /* It doesn't matter if malloc fails here, since mbsrtowcs should do
+        the right thing with a NULL first argument. */
+      wsbuf = (wchar_t *) malloc ((n + 1) * sizeof(wchar_t));
+      mbs = *src;
+      psbuf = *ps;
+
+      wclength = mbsrtowcs (wsbuf, &mbs, n, &psbuf);
+
+      if (wsbuf)
+       free (wsbuf);
+      return wclength;
+    }
+      
+  for (wclength = 0; wclength < len; wclength++, dest++)
+    {
+      if (mbsinit(ps))
+       {
+         if (**src == '\0')
+           {
+             *dest = L'\0';
+             *src = NULL;
+             return (wclength);
+           }
+         else if (**src == '\\')
+           {
+             *dest = L'\\';
+             mblength = 1;
+           }
+         else
+           mblength = mbrtowc(dest, *src, n, ps);
+       }
+      else
+       mblength = mbrtowc(dest, *src, n, ps);
+
+      /* Cannot convert multibyte character to wide character. */
+      if (mblength == (size_t)-1 || mblength == (size_t)-2)
+       return (size_t)-1;
+
+      *src += mblength;
+      n -= mblength;
+
+      /* The multibyte string  has  been  completely  converted,
+        including  the terminating '\0'. */
+      if (*dest == L'\0')
+       {
+         *src = NULL;
+         break;
+       }
+    }
+
+    return (wclength);
+}
+
+#if HAVE_MBSNRTOWCS
+/* Convert a multibyte string to a wide character string. Memory for the
+   new wide character string is obtained with malloc.
+
+   Fast multiple-character version of xdupmbstowcs used when the indices are
+   not required and mbsnrtowcs is available. */
+
+static size_t
+xdupmbstowcs2 (destp, src)
+    wchar_t **destp;   /* Store the pointer to the wide character string */
+    const char *src;   /* Multibyte character string */
+{
+  const char *p;       /* Conversion start position of src */
+  wchar_t *wsbuf;      /* Buffer for wide characters. */
+  size_t wsbuf_size;   /* Size of WSBUF */
+  size_t wcnum;                /* Number of wide characters in WSBUF */
+  mbstate_t state;     /* Conversion State */
+  size_t n, wcslength; /* Number of wide characters produced by the conversion. */
+  const char *end_or_backslash;
+  size_t nms;  /* Number of multibyte characters to convert at one time. */
+  mbstate_t tmp_state;
+  const char *tmp_p;
+
+  memset (&state, '\0', sizeof(mbstate_t));
+
+  wsbuf_size = 0;
+  wsbuf = NULL;
+
+  p = src;
+  wcnum = 0;
+  do
+    {
+      end_or_backslash = strchrnul(p, '\\');
+      nms = end_or_backslash - p;
+      if (*end_or_backslash == '\0')
+       nms++;
+
+      /* Compute the number of produced wide-characters. */
+      tmp_p = p;
+      tmp_state = state;
+
+      if (nms == 0 && *p == '\\')      /* special initial case */
+       nms = wcslength = 1;
+      else
+       wcslength = mbsnrtowcs (NULL, &tmp_p, nms, 0, &tmp_state);
+
+      if (wcslength == 0)
+       {
+         tmp_p = p;            /* will need below */
+         tmp_state = state;
+         wcslength = 1;        /* take a single byte */
+       }
+
+      /* Conversion failed. */
+      if (wcslength == (size_t)-1)
+       {
+         free (wsbuf);
+         *destp = NULL;
+         return (size_t)-1;
+       }
+
+      /* Resize the buffer if it is not large enough. */
+      if (wsbuf_size < wcnum+wcslength+1)      /* 1 for the L'\0' or the potential L'\\' */
+       {
+         wchar_t *wstmp;
+
+         while (wsbuf_size < wcnum+wcslength+1) /* 1 for the L'\0' or the potential L'\\' */
+           wsbuf_size += WSBUF_INC;
+
+         wstmp = (wchar_t *) realloc (wsbuf, wsbuf_size * sizeof (wchar_t));
+         if (wstmp == NULL)
+           {
+             free (wsbuf);
+             *destp = NULL;
+             return (size_t)-1;
+           }
+         wsbuf = wstmp;
+       }
+
+      /* Perform the conversion. This is assumed to return 'wcslength'.
+        It may set 'p' to NULL. */
+      n = mbsnrtowcs(wsbuf+wcnum, &p, nms, wsbuf_size-wcnum, &state);
+
+      /* Compensate for taking single byte on wcs conversion failure above. */
+      if (wcslength == 1 && (n == 0 || n == (size_t)-1))
+       {
+         state = tmp_state;
+         p = tmp_p;
+         wsbuf[wcnum++] = *p++;
+       }
+      else
+        wcnum += wcslength;
+
+      if (mbsinit (&state) && (p != NULL) && (*p == '\\'))
+       {
+         wsbuf[wcnum++] = L'\\';
+         p++;
+       }
+    }
+  while (p != NULL);
+
+  *destp = wsbuf;
+
+  /* Return the length of the wide character string, not including `\0'. */
+  return wcnum;
+}
+#endif /* HAVE_MBSNRTOWCS */
+
+/* Convert a multibyte string to a wide character string. Memory for the
+   new wide character string is obtained with malloc.
+
+   The return value is the length of the wide character string. Returns a
+   pointer to the wide character string in DESTP. If INDICESP is not NULL,
+   INDICESP stores the pointer to the pointer array. Each pointer is to
+   the first byte of each multibyte character. Memory for the pointer array
+   is obtained with malloc, too.
+   If conversion is failed, the return value is (size_t)-1 and the values
+   of DESTP and INDICESP are NULL. */
+
+size_t
+xdupmbstowcs (destp, indicesp, src)
+    wchar_t **destp;   /* Store the pointer to the wide character string */
+    char ***indicesp;  /* Store the pointer to the pointer array. */
+    const char *src;   /* Multibyte character string */
+{
+  const char *p;       /* Conversion start position of src */
+  wchar_t wc;          /* Created wide character by conversion */
+  wchar_t *wsbuf;      /* Buffer for wide characters. */
+  char **indices;      /* Buffer for indices. */
+  size_t wsbuf_size;   /* Size of WSBUF */
+  size_t wcnum;                /* Number of wide characters in WSBUF */
+  mbstate_t state;     /* Conversion State */
+
+  /* In case SRC or DESP is NULL, conversion doesn't take place. */
+  if (src == NULL || destp == NULL)
+    {
+      if (destp)
+       *destp = NULL;
+      if (indicesp)
+       *indicesp = NULL;
+      return (size_t)-1;
+    }
+
+#if HAVE_MBSNRTOWCS
+  if (indicesp == NULL)
+    return (xdupmbstowcs2 (destp, src));
+#endif
+
+  memset (&state, '\0', sizeof(mbstate_t));
+  wsbuf_size = WSBUF_INC;
+
+  wsbuf = (wchar_t *) malloc (wsbuf_size * sizeof(wchar_t));
+  if (wsbuf == NULL)
+    {
+      *destp = NULL;
+      if (indicesp)
+        *indicesp = NULL;
+      return (size_t)-1;
+    }
+
+  indices = NULL;
+  if (indicesp)
+    {
+      indices = (char **) malloc (wsbuf_size * sizeof(char *));
+      if (indices == NULL)
+       {
+         free (wsbuf);
+         *destp = NULL;
+         *indicesp = NULL;
+         return (size_t)-1;
+       }
+    }
+
+  p = src;
+  wcnum = 0;
+  do
+    {
+      size_t mblength; /* Byte length of one multibyte character. */
+
+      if (mbsinit (&state))
+       {
+         if (*p == '\0')
+           {
+             wc = L'\0';
+             mblength = 1;
+           }
+         else if (*p == '\\')
+           {
+             wc = L'\\';
+             mblength = 1;
+           }
+         else
+           mblength = mbrtowc(&wc, p, MB_LEN_MAX, &state);
+       }
+      else
+       mblength = mbrtowc(&wc, p, MB_LEN_MAX, &state);
+
+      /* Conversion failed. */
+      if (MB_INVALIDCH (mblength))
+       {
+         free (wsbuf);
+         FREE (indices);
+         *destp = NULL;
+         if (indicesp)
+           *indicesp = NULL;
+         return (size_t)-1;
+       }
+
+      ++wcnum;
+
+      /* Resize buffers when they are not large enough. */
+      if (wsbuf_size < wcnum)
+       {
+         wchar_t *wstmp;
+         char **idxtmp;
+
+         wsbuf_size += WSBUF_INC;
+
+         wstmp = (wchar_t *) realloc (wsbuf, wsbuf_size * sizeof (wchar_t));
+         if (wstmp == NULL)
+           {
+             free (wsbuf);
+             FREE (indices);
+             *destp = NULL;
+             if (indicesp)
+               *indicesp = NULL;
+             return (size_t)-1;
+           }
+         wsbuf = wstmp;
+
+         if (indicesp)
+           {
+             idxtmp = (char **) realloc (indices, wsbuf_size * sizeof (char **));
+             if (idxtmp == NULL)
+               {
+                 free (wsbuf);
+                 free (indices);
+                 *destp = NULL;
+                 if (indicesp)
+                   *indicesp = NULL;
+                 return (size_t)-1;
+               }
+             indices = idxtmp;
+           }
+       }
+
+      wsbuf[wcnum - 1] = wc;
+      if (indices)
+        indices[wcnum - 1] = (char *)p;
+      p += mblength;
+    }
+  while (MB_NULLWCH (wc) == 0);
+
+  /* Return the length of the wide character string, not including `\0'. */
+  *destp = wsbuf;
+  if (indicesp != NULL)
+    *indicesp = indices;
+
+  return (wcnum - 1);
+}
+
+#endif /* HANDLE_MULTIBYTE */
index 214b1466a50ad8a8a188111af768e8c0984b9d7f..0ccc22b4c9f257d4b2e40fc6f586699925d34079 100644 (file)
@@ -1,6 +1,6 @@
 /* fmtulong.c -- Convert unsigned long int to string. */
 
-/* Copyright (C) 1998-2002 Free Software Foundation, Inc.
+/* Copyright (C) 1998-2011 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -98,8 +98,9 @@ fmtulong (ui, base, buf, len, flags)
   if (base < 2 || base > 64)
     {
 #if 1
+      /* XXX - truncation possible with long translation */
       strncpy (buf, _("invalid base"), len - 1);
-      buf[len] = '\0';
+      buf[len-1] = '\0';
       errno = EINVAL;
       return (p = buf);
 #else
diff --git a/lib/sh/fmtulong.c~ b/lib/sh/fmtulong.c~
new file mode 100644 (file)
index 0000000..f55774b
--- /dev/null
@@ -0,0 +1,191 @@
+/* fmtulong.c -- Convert unsigned long int to string. */
+
+/* Copyright (C) 1998-2002 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/>.
+*/
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#if defined (HAVE_LIMITS_H)
+#  include <limits.h>
+#endif
+
+#include <bashansi.h>
+#ifdef HAVE_STDDEF_H
+#  include <stddef.h>
+#endif
+
+#ifdef HAVE_STDINT_H
+#  include <stdint.h>
+#endif
+#ifdef HAVE_INTTYPES_H
+#  include <inttypes.h>
+#endif
+#include <chartypes.h>
+#include <errno.h>
+
+#include <bashintl.h>
+
+#include "stdc.h"
+
+#include <typemax.h>
+
+#ifndef errno
+extern int errno;
+#endif
+
+#define x_digs  "0123456789abcdef"
+#define X_digs  "0123456789ABCDEF"
+
+/* XXX -- assumes uppercase letters, lowercase letters, and digits are
+   contiguous */
+#define FMTCHAR(x) \
+  ((x) < 10) ? (x) + '0' \
+            : (((x) < 36) ? (x) - 10 + 'a' \
+                          : (((x) < 62) ? (x) - 36 + 'A' \
+                                        : (((x) == 62) ? '@' : '_')))
+
+#ifndef FL_PREFIX
+#  define FL_PREFIX    0x01    /* add 0x, 0X, or 0 prefix as appropriate */
+#  define FL_ADDBASE   0x02    /* add base# prefix to converted value */
+#  define FL_HEXUPPER  0x04    /* use uppercase when converting to hex */
+#  define FL_UNSIGNED  0x08    /* don't add any sign */
+#endif
+
+#ifndef LONG
+#  define LONG long
+#  define UNSIGNED_LONG unsigned long
+#endif
+
+/* `unsigned long' (or unsigned long long) to string conversion for a given
+   base.  The caller passes the output buffer and the size.  This should
+   check for buffer underflow, but currently does not. */
+char *
+fmtulong (ui, base, buf, len, flags)
+     UNSIGNED_LONG ui;
+     int base;
+     char *buf;
+     size_t len;
+     int flags;
+{
+  char *p;
+  int sign;
+  LONG si;
+
+  if (base == 0)
+    base = 10;
+
+  if (base < 2 || base > 64)
+    {
+#if 1
+      /* XXX - truncation possible with long translation */
+      strncpy (buf, _("invalid base"), len - 1);
+      buf[len-1] = '\0';
+      errno = EINVAL;
+      return (p = buf);
+#else
+      base = 10;
+#endif
+    }
+
+  sign = 0;
+  if ((flags & FL_UNSIGNED) == 0 && (LONG)ui < 0)
+    {
+      ui = -ui;
+      sign = '-';
+    }
+
+  p = buf + len - 2;
+  p[1] = '\0';
+
+  /* handle common cases explicitly */
+  switch (base)
+    {
+    case 10:
+      if (ui < 10)
+       {
+         *p-- = TOCHAR (ui);
+         break;
+       }
+      /* Favor signed arithmetic over unsigned arithmetic; it is faster on
+        many machines. */
+      if ((LONG)ui < 0)
+       {
+         *p-- = TOCHAR (ui % 10);
+         si = ui / 10;
+       }
+      else
+        si = ui;
+      do
+       *p-- = TOCHAR (si % 10);
+      while (si /= 10);
+      break;
+
+    case 8:
+      do
+       *p-- = TOCHAR (ui & 7);
+      while (ui >>= 3);
+      break;
+
+    case 16:
+      do
+       *p-- = (flags & FL_HEXUPPER) ? X_digs[ui & 15] : x_digs[ui & 15];
+      while (ui >>= 4);
+      break;
+
+    case 2:
+      do
+       *p-- = TOCHAR (ui & 1);
+      while (ui >>= 1);
+      break;
+
+    default:
+      do
+       *p-- = FMTCHAR (ui % base);
+      while (ui /= base);
+      break;
+    }
+
+  if ((flags & FL_PREFIX) && (base == 8 || base == 16))
+    {
+      if (base == 16)
+       {
+         *p-- = (flags & FL_HEXUPPER) ? 'X' : 'x';
+         *p-- = '0';
+       }
+      else if (p[1] != '0')
+       *p-- = '0';
+    }
+  else if ((flags & FL_ADDBASE) && base != 10)
+    {
+      *p-- = '#';
+      *p-- = TOCHAR (base % 10);
+      if (base > 10)
+        *p-- = TOCHAR (base / 10);
+    }
+
+  if (sign)
+    *p-- = '-';
+
+  return (p + 1);
+}
index 4000c4a5a86000ea6034919bc0e17209b057e76a..e720892111c6f76915faaea1f4bed62871ecb94d 100644 (file)
@@ -66,11 +66,10 @@ zmapfd (fd, ostr, fn)
        }
       else if (nr < 0)
        {
-         rval = -1;
          free (result);
          if (ostr)
            *ostr = (char *)NULL;
-         break;
+         return -1;
        }
 
       RESIZE_MALLOCED_BUFFER (result, rind, nr, rsize, 128);
diff --git a/lib/sh/zmapfd.c~ b/lib/sh/zmapfd.c~
new file mode 100644 (file)
index 0000000..4000c4a
--- /dev/null
@@ -0,0 +1,90 @@
+/* zmapfd - read contents of file descriptor into a newly-allocated buffer */
+
+/* Copyright (C) 2006 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 <sys/types.h>
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#include <errno.h>
+
+#include "bashansi.h"
+#include "command.h"
+#include "general.h"
+
+#if !defined (errno)
+extern int errno;
+#endif
+
+extern ssize_t zread __P((int, char *, size_t));
+
+/* Dump contents of file descriptor FD to *OSTR.  FN is the filename for
+   error messages (not used right now). */
+int
+zmapfd (fd, ostr, fn)
+     int fd;
+     char **ostr;
+     char *fn;
+{
+  ssize_t nr;
+  int rval;
+  char lbuf[128];
+  char *result;
+  int rsize, rind;
+
+  rval = 0;
+  result = (char *)xmalloc (rsize = 64);
+  rind = 0;
+
+  while (1)
+    {
+      nr = zread (fd, lbuf, sizeof (lbuf));
+      if (nr == 0)
+       {
+         rval = rind;
+         break;
+       }
+      else if (nr < 0)
+       {
+         rval = -1;
+         free (result);
+         if (ostr)
+           *ostr = (char *)NULL;
+         break;
+       }
+
+      RESIZE_MALLOCED_BUFFER (result, rind, nr, rsize, 128);
+      memcpy (result+rind, lbuf, nr);
+      rind += nr;
+    }
+
+  RESIZE_MALLOCED_BUFFER (result, rind, 1, rsize, 128);
+  result[rind] = '\0';
+
+  if (ostr)
+    *ostr = result;
+  else
+    free (result);
+
+  return rval;
+}
index 7db5bc76e30ea7e51a1138e361a27ebac97e529b..c984c8aed93fb63c41fe686375bd8c9323d23244 100644 (file)
@@ -324,6 +324,9 @@ make_arith_for_command (exprs, action, lineno)
       else
        parser_error (lineno, _("syntax error: `;' unexpected"));
       parser_error (lineno, _("syntax error: `((%s))'"), exprs->word->word);
+      free (init);
+      free (test);
+      free (step);
       last_command_exit_value = 2;
       return ((COMMAND *)NULL);
     }
index d42f32f6d6b62568c084f6c4198c761fb53ae5a8..7db5bc76e30ea7e51a1138e361a27ebac97e529b 100644 (file)
@@ -292,7 +292,7 @@ make_arith_for_command (exprs, action, lineno)
        s++;
       start = s;
       /* skip to the semicolon or EOS */
-      i = skip_to_delim (s, 0, ";", SD_NOJMP);
+      i = skip_to_delim (start, 0, ";", SD_NOJMP);
       s = start + i;
 
       t = (i > 0) ? substring (start, 0, i) : (char *)NULL;
index 1ed5ac934104e9c7d2797fba3fa4fa6def360683..baa86643f88bbb231be6dedb274d6185731ba24e 100644 (file)
@@ -1,6 +1,6 @@
 /* pcomplete.c - functions to generate lists of matches for programmable completion. */
 
-/* Copyright (C) 1999-2009 Free Software Foundation, Inc.
+/* Copyright (C) 1999-2011 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -372,6 +372,7 @@ it_init_aliases (itp)
 #else
   itp->slist = (STRINGLIST *)NULL;
 #endif
+  free (alias_list);
   return 1;
 }
 
@@ -1191,7 +1192,8 @@ gen_command_matches (cs, text, line, ind, lwords, nw, cw)
 
   tw = command_substitute (cscmd, 0);
   csbuf = tw ? tw->word : (char *)NULL;
-  dispose_word_desc (tw);
+  if (tw)
+    dispose_word_desc (tw);
 
   /* Now clean up and destroy everything. */
   dispose_words (cmdlist);
@@ -1431,6 +1433,7 @@ gen_compspec_completions (cs, cmd, word, start, end, foundp)
     {
       tcs = compspec_create ();
       tcs->actions = CA_DIRECTORY;
+      FREE (ret);
       ret = gen_action_completions (tcs, word);
       compspec_dispose (tcs);
     }
@@ -1491,7 +1494,13 @@ gen_progcomp_completions (ocmd, cmd, word, start, end, foundp, retryp, lastcs)
   cs = progcomp_search (ocmd);
 
   if (cs == 0 || cs == *lastcs)
-    return (NULL);
+    {
+#if 0
+      if (foundp)
+       *foundp = 0;
+#endif
+      return (NULL);
+    }
 
   if (*lastcs)
     compspec_dispose (*lastcs);
index 522f765b4312300f8db9b313577f13bee69a2f0a..34c232d46f98860036be25e70df10f74777e5117 100644 (file)
@@ -83,6 +83,7 @@ extern char *strpbrk __P((char *, char *));
 extern int array_needs_making;
 extern STRING_INT_ALIST word_token_alist[];
 extern char *signal_names[];
+extern sh_builtin_func_t *last_shell_builtin, *this_shell_builtin;
 
 #if defined (DEBUG)
 #if defined (PREFER_STDARG)
@@ -1190,7 +1191,8 @@ gen_command_matches (cs, text, line, ind, lwords, nw, cw)
 
   tw = command_substitute (cscmd, 0);
   csbuf = tw ? tw->word : (char *)NULL;
-  dispose_word_desc (tw);
+  if (tw)
+    dispose_word_desc (tw);
 
   /* Now clean up and destroy everything. */
   dispose_words (cmdlist);
@@ -1430,6 +1432,7 @@ gen_compspec_completions (cs, cmd, word, start, end, foundp)
     {
       tcs = compspec_create ();
       tcs->actions = CA_DIRECTORY;
+      FREE (ret);
       ret = gen_action_completions (tcs, word);
       compspec_dispose (tcs);
     }
@@ -1490,7 +1493,13 @@ gen_progcomp_completions (ocmd, cmd, word, start, end, foundp, retryp, lastcs)
   cs = progcomp_search (ocmd);
 
   if (cs == 0 || cs == *lastcs)
-    return (NULL);
+    {
+#if 0
+      if (foundp)
+       *foundp = 0;
+#endif
+      return (NULL);
+    }
 
   if (*lastcs)
     compspec_dispose (*lastcs);
index e4590ebc7127f2705e8ee5d5a0bea506b0c37a11..0ccc3191b7e2e1dbdfce19c6965ece9641518e58 100644 (file)
@@ -972,7 +972,7 @@ print_deferred_heredocs (cstring)
       cprintf (" ");
       print_heredoc_header (hdtail);
     }
-  if (cstring[0] && (cstring[0] != ';' || cstring[1]))
+  if (cstring && cstring[0] && (cstring[0] != ';' || cstring[1]))
     cprintf ("%s", cstring); 
   if (deferred_heredocs)
     cprintf ("\n");
@@ -1507,6 +1507,8 @@ cprintf (control, va_alist)
        }
     }
 
+  va_end (args);
+
   the_printed_command[command_string_index] = '\0';
 }
 
index 3ceccc6645903f953ca0ebbc8c393d486cda2330..b9045321bc02ffc8202654f4a2fa21686a513a39 100644 (file)
@@ -431,7 +431,7 @@ indirection_level_string ()
 
   old = change_flag ('x', FLAG_OFF);
   ps4 = decode_prompt_string (ps4);
-  if (old == FLAG_ON)
+  if (old)
     change_flag ('x', FLAG_ON);
 
   if (ps4 == 0 || *ps4 == '\0')
@@ -972,7 +972,7 @@ print_deferred_heredocs (cstring)
       cprintf (" ");
       print_heredoc_header (hdtail);
     }
-  if (cstring[0] && (cstring[0] != ';' || cstring[1]))
+  if (cstring && cstring[0] && (cstring[0] != ';' || cstring[1]))
     cprintf ("%s", cstring); 
   if (deferred_heredocs)
     cprintf ("\n");
diff --git a/redir.c b/redir.c
index 921be8cad4c684bff3c1ed1b96567a2c79c14c92..022da6800559f0fb1fc76608f08da821f816915a 100644 (file)
--- a/redir.c
+++ b/redir.c
@@ -115,7 +115,7 @@ redirection_error (temp, error)
 
   allocname = 0;
   if (temp->rflags & REDIR_VARASSIGN)
-    filename = savestring (temp->redirector.filename->word);
+    filename = allocname = savestring (temp->redirector.filename->word);
   else if (temp->redirector.dest < 0)
     /* This can happen when read_token_word encounters overflow, like in
        exec 4294967297>x */
diff --git a/redir.c~ b/redir.c~
new file mode 100644 (file)
index 0000000..921be8c
--- /dev/null
+++ b/redir.c~
@@ -0,0 +1,1314 @@
+/* redir.c -- Functions to perform input and output redirection. */
+
+/* Copyright (C) 1997-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 (__GNUC__) && !defined (HAVE_ALLOCA_H) && defined (_AIX)
+  #pragma alloca
+#endif /* _AIX && RISC6000 && !__GNUC__ */
+
+#include <stdio.h>
+#include "bashtypes.h"
+#if !defined (_MINIX) && defined (HAVE_SYS_FILE_H)
+#  include <sys/file.h>
+#endif
+#include "filecntl.h"
+#include "posixstat.h"
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#include <errno.h>
+
+#if !defined (errno)
+extern int errno;
+#endif
+
+#include "bashansi.h"
+#include "bashintl.h"
+#include "memalloc.h"
+
+#define NEED_FPURGE_DECL
+
+#include "shell.h"
+#include "flags.h"
+#include "execute_cmd.h"
+#include "redir.h"
+
+#if defined (BUFFERED_INPUT)
+#  include "input.h"
+#endif
+
+#define SHELL_FD_BASE  10
+
+int expanding_redir;
+
+extern int posixly_correct;
+extern int last_command_exit_value;
+extern REDIRECT *redirection_undo_list;
+extern REDIRECT *exec_redirection_undo_list;
+
+/* Static functions defined and used in this file. */
+static void add_exec_redirect __P((REDIRECT *));
+static int add_undo_redirect __P((int, enum r_instruction, int));
+static int add_undo_close_redirect __P((int));
+static int expandable_redirection_filename __P((REDIRECT *));
+static int stdin_redirection __P((enum r_instruction, int));
+static int undoablefd __P((int));
+static int do_redirection_internal __P((REDIRECT *, int));
+
+static int write_here_document __P((int, WORD_DESC *));
+static int write_here_string __P((int, WORD_DESC *));
+static int here_document_to_fd __P((WORD_DESC *, enum r_instruction));
+
+static int redir_special_open __P((int, char *, int, int, enum r_instruction));
+static int noclobber_open __P((char *, int, int, enum r_instruction));
+static int redir_open __P((char *, int, int, enum r_instruction));
+
+static int redir_varassign __P((REDIRECT *, int));
+static int redir_varvalue __P((REDIRECT *));
+
+/* Spare redirector used when translating [N]>&WORD[-] or [N]<&WORD[-] to
+   a new redirection and when creating the redirection undo list. */
+static REDIRECTEE rd;
+
+/* Set to errno when a here document cannot be created for some reason.
+   Used to print a reasonable error message. */
+static int heredoc_errno;
+
+#define REDIRECTION_ERROR(r, e, fd) \
+do { \
+  if ((r) < 0) \
+    { \
+      if (fd >= 0) \
+       close (fd); \
+      last_command_exit_value = EXECUTION_FAILURE;\
+      return ((e) == 0 ? EINVAL : (e));\
+    } \
+} while (0)
+
+void
+redirection_error (temp, error)
+     REDIRECT *temp;
+     int error;
+{
+  char *filename, *allocname;
+  int oflags;
+
+  allocname = 0;
+  if (temp->rflags & REDIR_VARASSIGN)
+    filename = savestring (temp->redirector.filename->word);
+  else if (temp->redirector.dest < 0)
+    /* This can happen when read_token_word encounters overflow, like in
+       exec 4294967297>x */
+    filename = _("file descriptor out of range");
+#ifdef EBADF
+  /* This error can never involve NOCLOBBER */
+  else if (error != NOCLOBBER_REDIRECT && temp->redirector.dest >= 0 && error == EBADF)
+    {
+      /* If we're dealing with two file descriptors, we have to guess about
+         which one is invalid; in the cases of r_{duplicating,move}_input and
+         r_{duplicating,move}_output we're here because dup2() failed. */
+      switch (temp->instruction)
+        {
+        case r_duplicating_input:
+        case r_duplicating_output:
+        case r_move_input:
+        case r_move_output:
+         filename = allocname = itos (temp->redirectee.dest);
+         break;
+       case r_duplicating_input_word:
+         if (temp->redirector.dest == 0)       /* Guess */
+           filename = temp->redirectee.filename->word; /* XXX */
+         else
+           filename = allocname = itos (temp->redirector.dest);
+         break;
+       case r_duplicating_output_word:
+         if (temp->redirector.dest == 1)       /* Guess */
+           filename = temp->redirectee.filename->word; /* XXX */
+         else
+           filename = allocname = itos (temp->redirector.dest);
+         break;
+       default:
+         filename = allocname = itos (temp->redirector.dest);
+         break;
+        }
+    }
+#endif
+  else if (expandable_redirection_filename (temp))
+    {
+expandable_filename:
+      if (posixly_correct && interactive_shell == 0)
+       {
+         oflags = temp->redirectee.filename->flags;
+         temp->redirectee.filename->flags |= W_NOGLOB;
+       }
+      filename = allocname = redirection_expand (temp->redirectee.filename);
+      if (posixly_correct && interactive_shell == 0)
+       temp->redirectee.filename->flags = oflags;
+      if (filename == 0)
+       filename = temp->redirectee.filename->word;
+    }
+  else if (temp->redirectee.dest < 0)
+    filename = "file descriptor out of range";
+  else
+    filename = allocname = itos (temp->redirectee.dest);
+
+  switch (error)
+    {
+    case AMBIGUOUS_REDIRECT:
+      internal_error (_("%s: ambiguous redirect"), filename);
+      break;
+
+    case NOCLOBBER_REDIRECT:
+      internal_error (_("%s: cannot overwrite existing file"), filename);
+      break;
+
+#if defined (RESTRICTED_SHELL)
+    case RESTRICTED_REDIRECT:
+      internal_error (_("%s: restricted: cannot redirect output"), filename);
+      break;
+#endif /* RESTRICTED_SHELL */
+
+    case HEREDOC_REDIRECT:
+      internal_error (_("cannot create temp file for here-document: %s"), strerror (heredoc_errno));
+      break;
+
+    case BADVAR_REDIRECT:
+      internal_error (_("%s: cannot assign fd to variable"), filename);
+      break;
+
+    default:
+      internal_error ("%s: %s", filename, strerror (error));
+      break;
+    }
+
+  FREE (allocname);
+}
+
+/* Perform the redirections on LIST.  If flags & RX_ACTIVE, then actually
+   make input and output file descriptors, otherwise just do whatever is
+   neccessary for side effecting.  flags & RX_UNDOABLE says to remember
+   how to undo the redirections later, if non-zero.  If flags & RX_CLEXEC
+   is non-zero, file descriptors opened in do_redirection () have their
+   close-on-exec flag set. */
+int
+do_redirections (list, flags)
+     REDIRECT *list;
+     int flags;
+{
+  int error;
+  REDIRECT *temp;
+
+  if (flags & RX_UNDOABLE)
+    {
+      if (redirection_undo_list)
+       {
+         dispose_redirects (redirection_undo_list);
+         redirection_undo_list = (REDIRECT *)NULL;
+       }
+      if (exec_redirection_undo_list)
+       dispose_exec_redirects ();
+    }
+
+  for (temp = list; temp; temp = temp->next)
+    {
+      error = do_redirection_internal (temp, flags);
+      if (error)
+       {
+         redirection_error (temp, error);
+         return (error);
+       }
+    }
+  return (0);
+}
+
+/* Return non-zero if the redirection pointed to by REDIRECT has a
+   redirectee.filename that can be expanded. */
+static int
+expandable_redirection_filename (redirect)
+     REDIRECT *redirect;
+{
+  switch (redirect->instruction)
+    {
+    case r_output_direction:
+    case r_appending_to:
+    case r_input_direction:
+    case r_inputa_direction:
+    case r_err_and_out:
+    case r_append_err_and_out:
+    case r_input_output:
+    case r_output_force:
+    case r_duplicating_input_word:
+    case r_duplicating_output_word:
+    case r_move_input_word:
+    case r_move_output_word:
+      return 1;
+
+    default:
+      return 0;
+    }
+}
+
+/* Expand the word in WORD returning a string.  If WORD expands to
+   multiple words (or no words), then return NULL. */
+char *
+redirection_expand (word)
+     WORD_DESC *word;
+{
+  char *result;
+  WORD_LIST *tlist1, *tlist2;
+  WORD_DESC *w;
+
+  w = copy_word (word);
+  if (posixly_correct)
+    w->flags |= W_NOSPLIT;
+
+  tlist1 = make_word_list (w, (WORD_LIST *)NULL);
+  expanding_redir = 1;
+  tlist2 = expand_words_no_vars (tlist1);
+  expanding_redir = 0;
+  dispose_words (tlist1);
+
+  if (!tlist2 || tlist2->next)
+    {
+      /* We expanded to no words, or to more than a single word.
+        Dispose of the word list and return NULL. */
+      if (tlist2)
+       dispose_words (tlist2);
+      return ((char *)NULL);
+    }
+  result = string_list (tlist2);  /* XXX savestring (tlist2->word->word)? */
+  dispose_words (tlist2);
+  return (result);
+}
+
+static int
+write_here_string (fd, redirectee)
+     int fd;
+     WORD_DESC *redirectee;
+{
+  char *herestr;
+  int herelen, n, e;
+
+  expanding_redir = 1;
+  herestr = expand_string_to_string (redirectee->word, 0);
+  expanding_redir = 0;
+  herelen = STRLEN (herestr);
+
+  n = write (fd, herestr, herelen);
+  if (n == herelen)
+    {
+      n = write (fd, "\n", 1);
+      herelen = 1;
+    }
+  e = errno;
+  FREE (herestr);
+  if (n != herelen)
+    {
+      if (e == 0)
+       e = ENOSPC;
+      return e;
+    }
+  return 0;
+}  
+
+/* Write the text of the here document pointed to by REDIRECTEE to the file
+   descriptor FD, which is already open to a temp file.  Return 0 if the
+   write is successful, otherwise return errno. */
+static int
+write_here_document (fd, redirectee)
+     int fd;
+     WORD_DESC *redirectee;
+{
+  char *document;
+  int document_len, fd2;
+  FILE *fp;
+  register WORD_LIST *t, *tlist;
+
+  /* Expand the text if the word that was specified had
+     no quoting.  The text that we expand is treated
+     exactly as if it were surrounded by double quotes. */
+
+  if (redirectee->flags & W_QUOTED)
+    {
+      document = redirectee->word;
+      document_len = strlen (document);
+      /* Set errno to something reasonable if the write fails. */
+      if (write (fd, document, document_len) < document_len)
+       {
+         if (errno == 0)
+           errno = ENOSPC;
+         return (errno);
+       }
+      else
+       return 0;
+    }
+
+  expanding_redir = 1;
+  tlist = expand_string (redirectee->word, Q_HERE_DOCUMENT);
+  expanding_redir = 0;
+
+  if (tlist)
+    {
+      /* Try using buffered I/O (stdio) and writing a word
+        at a time, letting stdio do the work of buffering
+        for us rather than managing our own strings.  Most
+        stdios are not particularly fast, however -- this
+        may need to be reconsidered later. */
+      if ((fd2 = dup (fd)) < 0 || (fp = fdopen (fd2, "w")) == NULL)
+       {
+         if (fd2 >= 0)
+           close (fd2);
+         return (errno);
+       }
+      errno = 0;
+      for (t = tlist; t; t = t->next)
+       {
+         /* This is essentially the body of
+            string_list_internal expanded inline. */
+         document = t->word->word;
+         document_len = strlen (document);
+         if (t != tlist)
+           putc (' ', fp);     /* separator */
+         fwrite (document, document_len, 1, fp);
+         if (ferror (fp))
+           {
+             if (errno == 0)
+               errno = ENOSPC;
+             fd2 = errno;
+             fclose(fp);
+             dispose_words (tlist);
+             return (fd2);
+           }
+       }
+      dispose_words (tlist);
+      if (fclose (fp) != 0)
+       {
+         if (errno == 0)
+           errno = ENOSPC;
+         return (errno);
+       }
+    }
+  return 0;
+}
+
+/* Create a temporary file holding the text of the here document pointed to
+   by REDIRECTEE, and return a file descriptor open for reading to the temp
+   file.  Return -1 on any error, and make sure errno is set appropriately. */
+static int
+here_document_to_fd (redirectee, ri)
+     WORD_DESC *redirectee;
+     enum r_instruction ri;
+{
+  char *filename;
+  int r, fd, fd2;
+
+  fd = sh_mktmpfd ("sh-thd", MT_USERANDOM|MT_USETMPDIR, &filename);
+
+  /* If we failed for some reason other than the file existing, abort */
+  if (fd < 0)
+    {
+      FREE (filename);
+      return (fd);
+    }
+
+  errno = r = 0;               /* XXX */
+  /* write_here_document returns 0 on success, errno on failure. */
+  if (redirectee->word)
+    r = (ri != r_reading_string) ? write_here_document (fd, redirectee)
+                                : write_here_string (fd, redirectee);
+
+  if (r)
+    {
+      close (fd);
+      unlink (filename);
+      free (filename);
+      errno = r;
+      return (-1);
+    }
+
+  /* In an attempt to avoid races, we close the first fd only after opening
+     the second. */
+  /* Make the document really temporary.  Also make it the input. */
+  fd2 = open (filename, O_RDONLY|O_BINARY, 0600);
+
+  if (fd2 < 0)
+    {
+      r = errno;
+      unlink (filename);
+      free (filename);
+      close (fd);
+      errno = r;
+      return -1;
+    }
+
+  close (fd);
+  if (unlink (filename) < 0)
+    {
+      r = errno;
+      close (fd2);
+      free (filename);
+      errno = r;
+      return (-1);
+    }
+
+  free (filename);
+  return (fd2);
+}
+
+#define RF_DEVFD       1
+#define RF_DEVSTDERR   2
+#define RF_DEVSTDIN    3
+#define RF_DEVSTDOUT   4
+#define RF_DEVTCP      5
+#define RF_DEVUDP      6
+
+/* A list of pattern/value pairs for filenames that the redirection
+   code handles specially. */
+static STRING_INT_ALIST _redir_special_filenames[] = {
+#if !defined (HAVE_DEV_FD)
+  { "/dev/fd/[0-9]*", RF_DEVFD },
+#endif
+#if !defined (HAVE_DEV_STDIN)
+  { "/dev/stderr", RF_DEVSTDERR },
+  { "/dev/stdin", RF_DEVSTDIN },
+  { "/dev/stdout", RF_DEVSTDOUT },
+#endif
+#if defined (NETWORK_REDIRECTIONS)
+  { "/dev/tcp/*/*", RF_DEVTCP },
+  { "/dev/udp/*/*", RF_DEVUDP },
+#endif
+  { (char *)NULL, -1 }
+};
+
+static int
+redir_special_open (spec, filename, flags, mode, ri)
+     int spec;
+     char *filename;
+     int flags, mode;
+     enum r_instruction ri;
+{
+  int fd;
+#if !defined (HAVE_DEV_FD)
+  intmax_t lfd;
+#endif
+
+  fd = -1;
+  switch (spec)
+    {
+#if !defined (HAVE_DEV_FD)
+    case RF_DEVFD:
+      if (all_digits (filename+8) && legal_number (filename+8, &lfd) && lfd == (int)lfd)
+       {
+         fd = lfd;
+         fd = fcntl (fd, F_DUPFD, SHELL_FD_BASE);
+       }
+      else
+       fd = AMBIGUOUS_REDIRECT;
+      break;
+#endif
+
+#if !defined (HAVE_DEV_STDIN)
+    case RF_DEVSTDIN:
+      fd = fcntl (0, F_DUPFD, SHELL_FD_BASE);
+      break;
+    case RF_DEVSTDOUT:
+      fd = fcntl (1, F_DUPFD, SHELL_FD_BASE);
+      break;
+    case RF_DEVSTDERR:
+      fd = fcntl (2, F_DUPFD, SHELL_FD_BASE);
+      break;
+#endif
+
+#if defined (NETWORK_REDIRECTIONS)
+    case RF_DEVTCP:
+    case RF_DEVUDP:
+#if defined (HAVE_NETWORK)
+      fd = netopen (filename);
+#else
+      internal_warning (_("/dev/(tcp|udp)/host/port not supported without networking"));
+      fd = open (filename, flags, mode);
+#endif
+      break;
+#endif /* NETWORK_REDIRECTIONS */
+    }
+
+  return fd;
+}
+      
+/* Open FILENAME with FLAGS in noclobber mode, hopefully avoiding most
+   race conditions and avoiding the problem where the file is replaced
+   between the stat(2) and open(2). */
+static int
+noclobber_open (filename, flags, mode, ri)
+     char *filename;
+     int flags, mode;
+     enum r_instruction ri;
+{
+  int r, fd;
+  struct stat finfo, finfo2;
+
+  /* If the file exists and is a regular file, return an error
+     immediately. */
+  r = stat (filename, &finfo);
+  if (r == 0 && (S_ISREG (finfo.st_mode)))
+    return (NOCLOBBER_REDIRECT);
+
+  /* If the file was not present (r != 0), make sure we open it
+     exclusively so that if it is created before we open it, our open
+     will fail.  Make sure that we do not truncate an existing file.
+     Note that we don't turn on O_EXCL unless the stat failed -- if
+     the file was not a regular file, we leave O_EXCL off. */
+  flags &= ~O_TRUNC;
+  if (r != 0)
+    {
+      fd = open (filename, flags|O_EXCL, mode);
+      return ((fd < 0 && errno == EEXIST) ? NOCLOBBER_REDIRECT : fd);
+    }
+  fd = open (filename, flags, mode);
+
+  /* If the open failed, return the file descriptor right away. */
+  if (fd < 0)
+    return (errno == EEXIST ? NOCLOBBER_REDIRECT : fd);
+
+  /* OK, the open succeeded, but the file may have been changed from a
+     non-regular file to a regular file between the stat and the open.
+     We are assuming that the O_EXCL open handles the case where FILENAME
+     did not exist and is symlinked to an existing file between the stat
+     and open. */
+
+  /* If we can open it and fstat the file descriptor, and neither check
+     revealed that it was a regular file, and the file has not been replaced,
+     return the file descriptor. */
+  if ((fstat (fd, &finfo2) == 0) && (S_ISREG (finfo2.st_mode) == 0) &&
+      r == 0 && (S_ISREG (finfo.st_mode) == 0) &&
+      same_file (filename, filename, &finfo, &finfo2))
+    return fd;
+
+  /* The file has been replaced.  badness. */
+  close (fd);  
+  errno = EEXIST;
+  return (NOCLOBBER_REDIRECT);
+}
+
+static int
+redir_open (filename, flags, mode, ri)
+     char *filename;
+     int flags, mode;
+     enum r_instruction ri;
+{
+  int fd, r;
+
+  r = find_string_in_alist (filename, _redir_special_filenames, 1);
+  if (r >= 0)
+    return (redir_special_open (r, filename, flags, mode, ri));
+
+  /* If we are in noclobber mode, you are not allowed to overwrite
+     existing files.  Check before opening. */
+  if (noclobber && CLOBBERING_REDIRECT (ri))
+    {
+      fd = noclobber_open (filename, flags, mode, ri);
+      if (fd == NOCLOBBER_REDIRECT)
+       return (NOCLOBBER_REDIRECT);
+    }
+  else
+    {
+      fd = open (filename, flags, mode);
+#if defined (AFS)
+      if ((fd < 0) && (errno == EACCES))
+       {
+         fd = open (filename, flags & ~O_CREAT, mode);
+         errno = EACCES;       /* restore errno */
+       }
+#endif /* AFS */
+    }
+
+  return fd;
+}
+
+static int
+undoablefd (fd)
+     int fd;
+{
+  int clexec;
+
+  clexec = fcntl (fd, F_GETFD, 0);
+  if (clexec == -1 || (fd >= SHELL_FD_BASE && clexec == 1))
+    return 0;
+  return 1;
+}
+
+/* Do the specific redirection requested.  Returns errno or one of the
+   special redirection errors (*_REDIRECT) in case of error, 0 on success.
+   If flags & RX_ACTIVE is zero, then just do whatever is neccessary to
+   produce the appropriate side effects.   flags & RX_UNDOABLE, if non-zero,
+   says to remember how to undo each redirection.  If flags & RX_CLEXEC is
+   non-zero, then we set all file descriptors > 2 that we open to be
+   close-on-exec.  */
+static int
+do_redirection_internal (redirect, flags)
+     REDIRECT *redirect;
+     int flags;
+{
+  WORD_DESC *redirectee;
+  int redir_fd, fd, redirector, r, oflags;
+  intmax_t lfd;
+  char *redirectee_word;
+  enum r_instruction ri;
+  REDIRECT *new_redirect;
+  REDIRECTEE sd;
+
+  redirectee = redirect->redirectee.filename;
+  redir_fd = redirect->redirectee.dest;
+  redirector = redirect->redirector.dest;
+  ri = redirect->instruction;
+
+  if (redirect->flags & RX_INTERNAL)
+    flags |= RX_INTERNAL;
+
+  if (TRANSLATE_REDIRECT (ri))
+    {
+      /* We have [N]>&WORD[-] or [N]<&WORD[-] (or {V}>&WORD[-] or {V}<&WORD-).
+         and WORD, then translate the redirection into a new one and 
+        continue. */
+      redirectee_word = redirection_expand (redirectee);
+
+      /* XXX - what to do with [N]<&$w- where w is unset or null?  ksh93
+              closes N. */
+      if (redirectee_word == 0)
+       return (AMBIGUOUS_REDIRECT);
+      else if (redirectee_word[0] == '-' && redirectee_word[1] == '\0')
+       {
+         sd = redirect->redirector;
+         rd.dest = 0;
+         new_redirect = make_redirection (sd, r_close_this, rd, 0);
+       }
+      else if (all_digits (redirectee_word))
+       {
+         sd = redirect->redirector;
+         if (legal_number (redirectee_word, &lfd) && (int)lfd == lfd)
+           rd.dest = lfd;
+         else
+           rd.dest = -1;       /* XXX */
+         switch (ri)
+           {
+           case r_duplicating_input_word:
+             new_redirect = make_redirection (sd, r_duplicating_input, rd, 0);
+             break;
+           case r_duplicating_output_word:
+             new_redirect = make_redirection (sd, r_duplicating_output, rd, 0);
+             break;
+           case r_move_input_word:
+             new_redirect = make_redirection (sd, r_move_input, rd, 0);
+             break;
+           case r_move_output_word:
+             new_redirect = make_redirection (sd, r_move_output, rd, 0);
+             break;
+           }
+       }
+      else if (ri == r_duplicating_output_word && (redirect->rflags & REDIR_VARASSIGN) == 0 && redirector == 1)
+       {
+         sd = redirect->redirector;
+         rd.filename = make_bare_word (redirectee_word);
+         new_redirect = make_redirection (sd, r_err_and_out, rd, 0);
+       }
+      else
+       {
+         free (redirectee_word);
+         return (AMBIGUOUS_REDIRECT);
+       }
+
+      free (redirectee_word);
+
+      /* Set up the variables needed by the rest of the function from the
+        new redirection. */
+      if (new_redirect->instruction == r_err_and_out)
+       {
+         char *alloca_hack;
+
+         /* Copy the word without allocating any memory that must be
+            explicitly freed. */
+         redirectee = (WORD_DESC *)alloca (sizeof (WORD_DESC));
+         xbcopy ((char *)new_redirect->redirectee.filename,
+                (char *)redirectee, sizeof (WORD_DESC));
+
+         alloca_hack = (char *)
+           alloca (1 + strlen (new_redirect->redirectee.filename->word));
+         redirectee->word = alloca_hack;
+         strcpy (redirectee->word, new_redirect->redirectee.filename->word);
+       }
+      else
+       /* It's guaranteed to be an integer, and shouldn't be freed. */
+       redirectee = new_redirect->redirectee.filename;
+
+      redir_fd = new_redirect->redirectee.dest;
+      redirector = new_redirect->redirector.dest;
+      ri = new_redirect->instruction;
+
+      /* Overwrite the flags element of the old redirect with the new value. */
+      redirect->flags = new_redirect->flags;
+      dispose_redirects (new_redirect);
+    }
+
+  switch (ri)
+    {
+    case r_output_direction:
+    case r_appending_to:
+    case r_input_direction:
+    case r_inputa_direction:
+    case r_err_and_out:                /* command &>filename */
+    case r_append_err_and_out: /* command &>> filename */
+    case r_input_output:
+    case r_output_force:
+      if (posixly_correct && interactive_shell == 0)
+       {
+         oflags = redirectee->flags;
+         redirectee->flags |= W_NOGLOB;
+       }
+      redirectee_word = redirection_expand (redirectee);
+      if (posixly_correct && interactive_shell == 0)
+       redirectee->flags = oflags;
+
+      if (redirectee_word == 0)
+       return (AMBIGUOUS_REDIRECT);
+
+#if defined (RESTRICTED_SHELL)
+      if (restricted && (WRITE_REDIRECT (ri)))
+       {
+         free (redirectee_word);
+         return (RESTRICTED_REDIRECT);
+       }
+#endif /* RESTRICTED_SHELL */
+
+      fd = redir_open (redirectee_word, redirect->flags, 0666, ri);
+      free (redirectee_word);
+
+      if (fd == NOCLOBBER_REDIRECT)
+       return (fd);
+
+      if (fd < 0)
+       return (errno);
+
+      if (flags & RX_ACTIVE)
+       {
+         if (redirect->rflags & REDIR_VARASSIGN)
+           {
+             redirector = fcntl (fd, F_DUPFD, SHELL_FD_BASE);          /* XXX try this for now */
+             r = errno;
+             if (redirector < 0)
+               sys_error (_("redirection error: cannot duplicate fd"));
+             REDIRECTION_ERROR (redirector, r, fd);
+           }
+
+         if (flags & RX_UNDOABLE)
+           {
+             /* Only setup to undo it if the thing to undo is active. */
+             if ((fd != redirector) && (fcntl (redirector, F_GETFD, 0) != -1))
+               r = add_undo_redirect (redirector, ri, -1);
+             else
+               r = add_undo_close_redirect (redirector);
+             if (r < 0 && (redirect->rflags & REDIR_VARASSIGN))
+               close (redirector);
+             REDIRECTION_ERROR (r, errno, fd);
+           }
+
+#if defined (BUFFERED_INPUT)
+         check_bash_input (redirector);
+#endif
+
+         /* Make sure there is no pending output before we change the state
+            of the underlying file descriptor, since the builtins use stdio
+            for output. */
+         if (redirector == 1 && fileno (stdout) == redirector)
+           {
+             fflush (stdout);
+             fpurge (stdout);
+           }
+         else if (redirector == 2 && fileno (stderr) == redirector)
+           {
+             fflush (stderr);
+             fpurge (stderr);
+           }
+
+         if (redirect->rflags & REDIR_VARASSIGN)
+           {
+             if ((r = redir_varassign (redirect, redirector)) < 0)
+               {
+                 close (redirector);
+                 close (fd);
+                 return (r);   /* XXX */
+               }
+           }
+         else if ((fd != redirector) && (dup2 (fd, redirector) < 0))
+           return (errno);
+
+#if defined (BUFFERED_INPUT)
+         /* Do not change the buffered stream for an implicit redirection
+            of /dev/null to fd 0 for asynchronous commands without job
+            control (r_inputa_direction). */
+         if (ri == r_input_direction || ri == r_input_output)
+           duplicate_buffered_stream (fd, redirector);
+#endif /* BUFFERED_INPUT */
+
+         /*
+          * If we're remembering, then this is the result of a while, for
+          * or until loop with a loop redirection, or a function/builtin
+          * executing in the parent shell with a redirection.  In the
+          * function/builtin case, we want to set all file descriptors > 2
+          * to be close-on-exec to duplicate the effect of the old
+          * for i = 3 to NOFILE close(i) loop.  In the case of the loops,
+          * both sh and ksh leave the file descriptors open across execs.
+          * The Posix standard mentions only the exec builtin.
+          */
+         if ((flags & RX_CLEXEC) && (redirector > 2))
+           SET_CLOSE_ON_EXEC (redirector);
+       }
+
+      if (fd != redirector)
+       {
+#if defined (BUFFERED_INPUT)
+         if (INPUT_REDIRECT (ri))
+           close_buffered_fd (fd);
+         else
+#endif /* !BUFFERED_INPUT */
+           close (fd);         /* Don't close what we just opened! */
+       }
+
+      /* If we are hacking both stdout and stderr, do the stderr
+        redirection here.  XXX - handle {var} here? */
+      if (ri == r_err_and_out || ri == r_append_err_and_out)
+       {
+         if (flags & RX_ACTIVE)
+           {
+             if (flags & RX_UNDOABLE)
+               add_undo_redirect (2, ri, -1);
+             if (dup2 (1, 2) < 0)
+               return (errno);
+           }
+       }
+      break;
+
+    case r_reading_until:
+    case r_deblank_reading_until:
+    case r_reading_string:
+      /* REDIRECTEE is a pointer to a WORD_DESC containing the text of
+        the new input.  Place it in a temporary file. */
+      if (redirectee)
+       {
+         fd = here_document_to_fd (redirectee, ri);
+
+         if (fd < 0)
+           {
+             heredoc_errno = errno;
+             return (HEREDOC_REDIRECT);
+           }
+
+         if (redirect->rflags & REDIR_VARASSIGN)
+           {
+             redirector = fcntl (fd, F_DUPFD, SHELL_FD_BASE);          /* XXX try this for now */
+             r = errno;
+             if (redirector < 0)
+               sys_error (_("redirection error: cannot duplicate fd"));
+             REDIRECTION_ERROR (redirector, r, fd);
+           }
+
+         if (flags & RX_ACTIVE)
+           {
+             if (flags & RX_UNDOABLE)
+               {
+                 /* Only setup to undo it if the thing to undo is active. */
+                 if ((fd != redirector) && (fcntl (redirector, F_GETFD, 0) != -1))
+                   r = add_undo_redirect (redirector, ri, -1);
+                 else
+                   r = add_undo_close_redirect (redirector);
+                 if (r < 0 && (redirect->rflags & REDIR_VARASSIGN))
+                   close (redirector);
+                 REDIRECTION_ERROR (r, errno, fd);
+               }
+
+#if defined (BUFFERED_INPUT)
+             check_bash_input (redirector);
+#endif
+             if (redirect->rflags & REDIR_VARASSIGN)
+               {
+                 if ((r = redir_varassign (redirect, redirector)) < 0)
+                   {
+                     close (redirector);
+                     close (fd);
+                     return (r);       /* XXX */
+                   }
+               }
+             else if (fd != redirector && dup2 (fd, redirector) < 0)
+               {
+                 r = errno;
+                 close (fd);
+                 return (r);
+               }
+
+#if defined (BUFFERED_INPUT)
+             duplicate_buffered_stream (fd, redirector);
+#endif
+
+             if ((flags & RX_CLEXEC) && (redirector > 2))
+               SET_CLOSE_ON_EXEC (redirector);
+           }
+
+         if (fd != redirector)
+#if defined (BUFFERED_INPUT)
+           close_buffered_fd (fd);
+#else
+           close (fd);
+#endif
+       }
+      break;
+
+    case r_duplicating_input:
+    case r_duplicating_output:
+    case r_move_input:
+    case r_move_output:
+      if ((flags & RX_ACTIVE) && (redirect->rflags & REDIR_VARASSIGN))
+        {
+         redirector = fcntl (redir_fd, F_DUPFD, SHELL_FD_BASE);                /* XXX try this for now */
+         r = errno;
+         if (redirector < 0)
+           sys_error (_("redirection error: cannot duplicate fd"));
+         REDIRECTION_ERROR (redirector, r, -1);
+        }
+
+      if ((flags & RX_ACTIVE) && (redir_fd != redirector))
+       {
+         if (flags & RX_UNDOABLE)
+           {
+             /* Only setup to undo it if the thing to undo is active. */
+             if (fcntl (redirector, F_GETFD, 0) != -1)
+               r = add_undo_redirect (redirector, ri, redir_fd);
+             else
+               r = add_undo_close_redirect (redirector);
+             if (r < 0 && (redirect->rflags & REDIR_VARASSIGN))
+               close (redirector);
+             REDIRECTION_ERROR (r, errno, -1);
+           }
+#if defined (BUFFERED_INPUT)
+         check_bash_input (redirector);
+#endif
+         if (redirect->rflags & REDIR_VARASSIGN)
+           {
+             if ((r = redir_varassign (redirect, redirector)) < 0)
+               {
+                 close (redirector);
+                 return (r);   /* XXX */
+               }
+           }
+         /* This is correct.  2>&1 means dup2 (1, 2); */
+         else if (dup2 (redir_fd, redirector) < 0)
+           return (errno);
+
+#if defined (BUFFERED_INPUT)
+         if (ri == r_duplicating_input || ri == r_move_input)
+           duplicate_buffered_stream (redir_fd, redirector);
+#endif /* BUFFERED_INPUT */
+
+         /* First duplicate the close-on-exec state of redirectee.  dup2
+            leaves the flag unset on the new descriptor, which means it
+            stays open.  Only set the close-on-exec bit for file descriptors
+            greater than 2 in any case, since 0-2 should always be open
+            unless closed by something like `exec 2<&-'.  It should always
+            be safe to set fds > 2 to close-on-exec if they're being used to
+            save file descriptors < 2, since we don't need to preserve the
+            state of the close-on-exec flag for those fds -- they should
+            always be open. */
+         /* if ((already_set || set_unconditionally) && (ok_to_set))
+               set_it () */
+#if 0
+         if (((fcntl (redir_fd, F_GETFD, 0) == 1) || redir_fd < 2 || (flags & RX_CLEXEC)) &&
+              (redirector > 2))
+#else
+         if (((fcntl (redir_fd, F_GETFD, 0) == 1) || (redir_fd < 2 && (flags & RX_INTERNAL)) || (flags & RX_CLEXEC)) &&
+              (redirector > 2))
+#endif
+           SET_CLOSE_ON_EXEC (redirector);
+
+         /* When undoing saving of non-standard file descriptors (>=3) using
+            file descriptors >= SHELL_FD_BASE, we set the saving fd to be
+            close-on-exec and use a flag to decide how to set close-on-exec
+            when the fd is restored. */
+         if ((redirect->flags & RX_INTERNAL) && (redirect->flags & RX_SAVCLEXEC) && redirector >= 3 && redir_fd >= SHELL_FD_BASE)
+           SET_OPEN_ON_EXEC (redirector);
+           
+         /* dup-and-close redirection */
+         if (ri == r_move_input || ri == r_move_output)
+           {
+             xtrace_fdchk (redir_fd);
+
+             close (redir_fd);
+#if defined (COPROCESS_SUPPORT)
+             coproc_fdchk (redir_fd);  /* XXX - loses coproc fds */
+#endif
+           }
+       }
+      break;
+
+    case r_close_this:
+      if (flags & RX_ACTIVE)
+       {
+         if (redirect->rflags & REDIR_VARASSIGN)
+           {
+             redirector = redir_varvalue (redirect);
+             if (redirector < 0)
+               return AMBIGUOUS_REDIRECT;
+           }
+
+         r = 0;
+         if ((flags & RX_UNDOABLE) && (fcntl (redirector, F_GETFD, 0) != -1))
+           {
+             r = add_undo_redirect (redirector, ri, -1);
+             REDIRECTION_ERROR (r, errno, redirector);
+           }
+
+#if defined (COPROCESS_SUPPORT)
+         coproc_fdchk (redirector);
+#endif
+         xtrace_fdchk (redirector);
+
+#if defined (BUFFERED_INPUT)
+         check_bash_input (redirector);
+         close_buffered_fd (redirector);
+#else /* !BUFFERED_INPUT */
+         close (redirector);
+#endif /* !BUFFERED_INPUT */
+       }
+      break;
+
+    case r_duplicating_input_word:
+    case r_duplicating_output_word:
+      break;
+    }
+  return (0);
+}
+
+/* Remember the file descriptor associated with the slot FD,
+   on REDIRECTION_UNDO_LIST.  Note that the list will be reversed
+   before it is executed.  Any redirections that need to be undone
+   even if REDIRECTION_UNDO_LIST is discarded by the exec builtin
+   are also saved on EXEC_REDIRECTION_UNDO_LIST.  FDBASE says where to
+   start the duplicating.  If it's less than SHELL_FD_BASE, we're ok,
+   and can use SHELL_FD_BASE (-1 == don't care).  If it's >= SHELL_FD_BASE,
+   we have to make sure we don't use fdbase to save a file descriptor,
+   since we're going to use it later (e.g., make sure we don't save fd 0
+   to fd 10 if we have a redirection like 0<&10).  If the value of fdbase
+   puts the process over its fd limit, causing fcntl to fail, we try
+   again with SHELL_FD_BASE.  Return 0 on success, -1 on error. */
+static int
+add_undo_redirect (fd, ri, fdbase)
+     int fd;
+     enum r_instruction ri;
+     int fdbase;
+{
+  int new_fd, clexec_flag;
+  REDIRECT *new_redirect, *closer, *dummy_redirect;
+  REDIRECTEE sd;
+
+  new_fd = fcntl (fd, F_DUPFD, (fdbase < SHELL_FD_BASE) ? SHELL_FD_BASE : fdbase+1);
+  if (new_fd < 0)
+    new_fd = fcntl (fd, F_DUPFD, SHELL_FD_BASE);
+
+  if (new_fd < 0)
+    {
+      sys_error (_("redirection error: cannot duplicate fd"));
+      return (-1);
+    }
+
+  clexec_flag = fcntl (fd, F_GETFD, 0);
+
+  sd.dest = new_fd;
+  rd.dest = 0;
+  closer = make_redirection (sd, r_close_this, rd, 0);
+  closer->flags |= RX_INTERNAL;
+  dummy_redirect = copy_redirects (closer);
+
+  sd.dest = fd;
+  rd.dest = new_fd;
+  if (fd == 0)
+    new_redirect = make_redirection (sd, r_duplicating_input, rd, 0);
+  else
+    new_redirect = make_redirection (sd, r_duplicating_output, rd, 0);
+  new_redirect->flags |= RX_INTERNAL;
+  if (clexec_flag == 0 && fd >= 3 && new_fd >= SHELL_FD_BASE)
+    new_redirect->flags |= RX_SAVCLEXEC;
+  new_redirect->next = closer;
+
+  closer->next = redirection_undo_list;
+  redirection_undo_list = new_redirect;
+
+  /* Save redirections that need to be undone even if the undo list
+     is thrown away by the `exec' builtin. */
+  add_exec_redirect (dummy_redirect);
+
+  /* experimental:  if we're saving a redirection to undo for a file descriptor
+     above SHELL_FD_BASE, add a redirection to be undone if the exec builtin
+     causes redirections to be discarded.  There needs to be a difference
+     between fds that are used to save other fds and then are the target of
+     user redirections and fds that are just the target of user redirections.
+     We use the close-on-exec flag to tell the difference; fds > SHELL_FD_BASE
+     that have the close-on-exec flag set are assumed to be fds used internally
+     to save others. */
+  if (fd >= SHELL_FD_BASE && ri != r_close_this && clexec_flag)
+    {
+      sd.dest = fd;
+      rd.dest = new_fd;
+      new_redirect = make_redirection (sd, r_duplicating_output, rd, 0);
+      new_redirect->flags |= RX_INTERNAL;
+
+      add_exec_redirect (new_redirect);
+    }
+
+  /* File descriptors used only for saving others should always be
+     marked close-on-exec.  Unfortunately, we have to preserve the
+     close-on-exec state of the file descriptor we are saving, since
+     fcntl (F_DUPFD) sets the new file descriptor to remain open
+     across execs.  If, however, the file descriptor whose state we
+     are saving is <= 2, we can just set the close-on-exec flag,
+     because file descriptors 0-2 should always be open-on-exec,
+     and the restore above in do_redirection() will take care of it. */
+  if (clexec_flag || fd < 3)
+    SET_CLOSE_ON_EXEC (new_fd);
+  else if (redirection_undo_list->flags & RX_SAVCLEXEC)
+    SET_CLOSE_ON_EXEC (new_fd);
+
+  return (0);
+}
+
+/* Set up to close FD when we are finished with the current command
+   and its redirections.  Return 0 on success, -1 on error. */
+static int
+add_undo_close_redirect (fd)
+     int fd;
+{
+  REDIRECT *closer;
+  REDIRECTEE sd;
+
+  sd.dest = fd;
+  rd.dest = 0;
+  closer = make_redirection (sd, r_close_this, rd, 0);
+  closer->flags |= RX_INTERNAL;
+  closer->next = redirection_undo_list;
+  redirection_undo_list = closer;
+
+  return 0;
+}
+
+static void
+add_exec_redirect (dummy_redirect)
+     REDIRECT *dummy_redirect;
+{
+  dummy_redirect->next = exec_redirection_undo_list;
+  exec_redirection_undo_list = dummy_redirect;
+}
+
+/* Return 1 if the redirection specified by RI and REDIRECTOR alters the
+   standard input. */
+static int
+stdin_redirection (ri, redirector)
+     enum r_instruction ri;
+     int redirector;
+{
+  switch (ri)
+    {
+    case r_input_direction:
+    case r_inputa_direction:
+    case r_input_output:
+    case r_reading_until:
+    case r_deblank_reading_until:
+    case r_reading_string:
+      return (1);
+    case r_duplicating_input:
+    case r_duplicating_input_word:
+    case r_close_this:
+      return (redirector == 0);
+    case r_output_direction:
+    case r_appending_to:
+    case r_duplicating_output:
+    case r_err_and_out:
+    case r_append_err_and_out:
+    case r_output_force:
+    case r_duplicating_output_word:
+      return (0);
+    }
+  return (0);
+}
+
+/* Return non-zero if any of the redirections in REDIRS alter the standard
+   input. */
+int
+stdin_redirects (redirs)
+     REDIRECT *redirs;
+{
+  REDIRECT *rp;
+  int n;
+
+  for (n = 0, rp = redirs; rp; rp = rp->next)
+    if ((rp->rflags & REDIR_VARASSIGN) == 0)
+      n += stdin_redirection (rp->instruction, rp->redirector.dest);
+  return n;
+}
+
+/* These don't yet handle array references */
+static int
+redir_varassign (redir, fd)
+     REDIRECT *redir;
+     int fd;
+{
+  WORD_DESC *w;
+  SHELL_VAR *v;
+
+  w = redir->redirector.filename;
+  v = bind_var_to_int (w->word, fd);
+  if (v == 0 || readonly_p (v) || noassign_p (v))
+    return BADVAR_REDIRECT;
+
+  return 0;
+}
+
+static int
+redir_varvalue (redir)
+     REDIRECT *redir;
+{
+  SHELL_VAR *v;
+  char *val;
+  intmax_t vmax;
+  int i;
+
+  /* XXX - handle set -u here? */
+  v = find_variable (redir->redirector.filename->word);
+  if (v == 0 || invisible_p (v))
+    return -1;
+
+  val = get_variable_value (v);
+  if (val == 0 || *val == 0)
+    return -1;
+
+  if (legal_number (val, &vmax) < 0)
+    return -1;
+
+  i = vmax;    /* integer truncation */
+  return i;
+}
index 0a612ca90a7e739c00eac6336b00cd52f169b0d9..59670a920a934d8fc6ffcd7fe1ac3db54372f573 100644 (file)
@@ -158,7 +158,7 @@ strsub (string, pat, rep, global)
          if (replen)
            RESIZE_MALLOCED_BUFFER (temp, templen, replen, tempsize, (replen * 2));
 
-         for (r = rep; *r; )
+         for (r = rep; *r; )   /* can rep == "" */
            temp[templen++] = *r++;
 
          i += patlen ? patlen : 1;     /* avoid infinite recursion */
diff --git a/stringlib.c~ b/stringlib.c~
new file mode 100644 (file)
index 0000000..0a612ca
--- /dev/null
@@ -0,0 +1,287 @@
+/* stringlib.c - Miscellaneous string functions. */
+
+/* Copyright (C) 1996-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)
+#  include <unistd.h>
+#endif
+
+#include "bashansi.h"
+#include <stdio.h>
+#include "chartypes.h"
+
+#include "shell.h"
+#include "pathexp.h"
+
+#include <glob/glob.h>
+
+#if defined (EXTENDED_GLOB)
+#  include <glob/strmatch.h>
+#endif
+
+/* **************************************************************** */
+/*                                                                 */
+/*             Functions to manage arrays of strings               */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Find STRING in ALIST, a list of string key/int value pairs.  If FLAGS
+   is 1, STRING is treated as a pattern and matched using strmatch. */
+int
+find_string_in_alist (string, alist, flags)
+     char *string;
+     STRING_INT_ALIST *alist;
+     int flags;
+{
+  register int i;
+  int r;
+
+  for (i = r = 0; alist[i].word; i++)
+    {
+#if defined (EXTENDED_GLOB)
+      if (flags)
+       r = strmatch (alist[i].word, string, FNM_EXTMATCH) != FNM_NOMATCH;
+      else
+#endif
+       r = STREQ (string, alist[i].word);
+
+      if (r)
+       return (alist[i].token);
+    }
+  return -1;
+}
+
+/* Find TOKEN in ALIST, a list of string/int value pairs.  Return the
+   corresponding string.  Allocates memory for the returned
+   string.  FLAGS is currently ignored, but reserved. */
+char *
+find_token_in_alist (token, alist, flags)
+     int token;
+     STRING_INT_ALIST *alist;
+     int flags;
+{
+  register int i;
+
+  for (i = 0; alist[i].word; i++)
+    {
+      if (alist[i].token == token)
+        return (savestring (alist[i].word));
+    }
+  return ((char *)NULL);
+}
+
+int
+find_index_in_alist (string, alist, flags)
+     char *string;
+     STRING_INT_ALIST *alist;
+     int flags;
+{
+  register int i;
+  int r;
+
+  for (i = r = 0; alist[i].word; i++)
+    {
+#if defined (EXTENDED_GLOB)
+      if (flags)
+       r = strmatch (alist[i].word, string, FNM_EXTMATCH) != FNM_NOMATCH;
+      else
+#endif
+       r = STREQ (string, alist[i].word);
+
+      if (r)
+       return (i);
+    }
+
+  return -1;
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                 String Management Functions                     */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Cons a new string from STRING starting at START and ending at END,
+   not including END. */
+char *
+substring (string, start, end)
+     const char *string;
+     int start, end;
+{
+  register int len;
+  register char *result;
+
+  len = end - start;
+  result = (char *)xmalloc (len + 1);
+  strncpy (result, string + start, len);
+  result[len] = '\0';
+  return (result);
+}
+
+/* Replace occurrences of PAT with REP in STRING.  If GLOBAL is non-zero,
+   replace all occurrences, otherwise replace only the first.
+   This returns a new string; the caller should free it. */
+char *
+strsub (string, pat, rep, global)
+     char *string, *pat, *rep;
+     int global;
+{
+  int patlen, replen, templen, tempsize, repl, i;
+  char *temp, *r;
+
+  patlen = strlen (pat);
+  replen = strlen (rep);
+  for (temp = (char *)NULL, i = templen = tempsize = 0, repl = 1; string[i]; )
+    {
+      if (repl && STREQN (string + i, pat, patlen))
+       {
+         if (replen)
+           RESIZE_MALLOCED_BUFFER (temp, templen, replen, tempsize, (replen * 2));
+
+         for (r = rep; *r; )
+           temp[templen++] = *r++;
+
+         i += patlen ? patlen : 1;     /* avoid infinite recursion */
+         repl = global != 0;
+       }
+      else
+       {
+         RESIZE_MALLOCED_BUFFER (temp, templen, 1, tempsize, 16);
+         temp[templen++] = string[i++];
+       }
+    }
+  if (temp)
+    temp[templen] = 0;
+  else
+    temp = savestring (string);
+  return (temp);
+}
+
+/* Replace all instances of C in STRING with TEXT.  TEXT may be empty or
+   NULL.  If DO_GLOB is non-zero, we quote the replacement text for
+   globbing.  Backslash may be used to quote C. */
+char *
+strcreplace (string, c, text, do_glob)
+     char *string;
+     int c;
+     char *text;
+     int do_glob;
+{
+  char *ret, *p, *r, *t;
+  int len, rlen, ind, tlen;
+
+  len = STRLEN (text);
+  rlen = len + strlen (string) + 2;
+  ret = (char *)xmalloc (rlen);
+
+  for (p = string, r = ret; p && *p; )
+    {
+      if (*p == c)
+       {
+         if (len)
+           {
+             ind = r - ret;
+             if (do_glob && (glob_pattern_p (text) || strchr (text, '\\')))
+               {
+                 t = quote_globbing_chars (text);
+                 tlen = strlen (t);
+                 RESIZE_MALLOCED_BUFFER (ret, ind, tlen, rlen, rlen);
+                 r = ret + ind;        /* in case reallocated */
+                 strcpy (r, t);
+                 r += tlen;
+                 free (t);
+               }
+             else
+               {
+                 RESIZE_MALLOCED_BUFFER (ret, ind, len, rlen, rlen);
+                 r = ret + ind;        /* in case reallocated */
+                 strcpy (r, text);
+                 r += len;
+               }
+           }
+         p++;
+         continue;
+       }
+
+      if (*p == '\\' && p[1] == c)
+       p++;
+
+      ind = r - ret;
+      RESIZE_MALLOCED_BUFFER (ret, ind, 2, rlen, rlen);
+      r = ret + ind;                   /* in case reallocated */
+      *r++ = *p++;
+    }
+  *r = '\0';
+
+  return ret;
+}
+
+#ifdef INCLUDE_UNUSED
+/* Remove all leading whitespace from STRING.  This includes
+   newlines.  STRING should be terminated with a zero. */
+void
+strip_leading (string)
+     char *string;
+{
+  char *start = string;
+
+  while (*string && (whitespace (*string) || *string == '\n'))
+    string++;
+
+  if (string != start)
+    {
+      int len = strlen (string);
+      FASTCOPY (string, start, len);
+      start[len] = '\0';
+    }
+}
+#endif
+
+/* Remove all trailing whitespace from STRING.  This includes
+   newlines.  If NEWLINES_ONLY is non-zero, only trailing newlines
+   are removed.  STRING should be terminated with a zero. */
+void
+strip_trailing (string, len, newlines_only)
+     char *string;
+     int len;
+     int newlines_only;
+{
+  while (len >= 0)
+    {
+      if ((newlines_only && string[len] == '\n') ||
+         (!newlines_only && whitespace (string[len])))
+       len--;
+      else
+       break;
+    }
+  string[len + 1] = '\0';
+}
+
+/* A wrapper for bcopy that can be prototyped in general.h */
+void
+xbcopy (s, d, n)
+     char *s, *d;
+     int n;
+{
+  FASTCOPY (s, d, n);
+}
diff --git a/subst.c b/subst.c
index 32e9cfc04fed3b32bfe1b93e9dc856838b8142ac..f53e8e5124180241f008133ce4b54415ca40b025 100644 (file)
--- a/subst.c
+++ b/subst.c
@@ -1772,6 +1772,7 @@ skip_to_delim (string, start, delims, flags)
          if (string[si] == '\0')
            CQ_RETURN(si);
          temp = extract_process_subst (string, (c == '<') ? "<(" : ">(", &si);
+         free (temp);          /* no SX_ALLOC here */
          i = si;
          if (string[i] == '\0')
            break;
@@ -2070,6 +2071,8 @@ split_at_delims (string, slen, delims, sentinel, flags, nwp, cwp)
   if (cwp)
     *cwp = cw;
 
+  FREE (d2);
+
   return (REVERSE_LIST (ret, WORD_LIST *));
 }
 #endif /* READLINE */
@@ -5524,9 +5527,11 @@ array_length_reference (s)
       if (akey == 0 || *akey == 0)
        {
          err_badarraysub (t);
+         FREE (akey);
          return (-1);
        }
       t = assoc_reference (assoc_cell (var), akey);
+      free (akey);
     }
   else
     {
@@ -5676,6 +5681,7 @@ parameter_brace_expand_word (name, var_is_special, quoted, pflags, indp)
 #if defined (ARRAY_VARS)
   else if (valid_array_reference (name))
     {
+      /* XXX - does this leak if name[@] or name[*]? */
       temp = array_value (name, quoted, 0, &atype, &ind);
       if (atype == 0 && temp)
        {
@@ -6327,7 +6333,11 @@ parameter_brace_substring (varname, value, ind, substr, quoted, flags)
   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);
+    {
+      if (vtype == VT_VARIABLE)
+       FREE (val);
+      return ((r == 0) ? &expand_param_error : (char *)NULL);
+    }
 
   switch (vtype)
     {
@@ -6956,9 +6966,8 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
       (sindex == t_index - 1 && string[sindex] == '!' && VALID_INDIR_PARAM (string[t_index])))
     {
       t_index++;
-      free (name);
       temp1 = string_extract (string, &t_index, "#%:-=?+/}", 0);
-      name = (char *)xmalloc (3 + (strlen (temp1)));
+      name = (char *)xrealloc (name, 3 + (strlen (temp1)));
       *name = string[sindex];
       if (string[sindex] == '!')
        {
@@ -7106,6 +7115,8 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
       free (temp1);
       *indexp = sindex;
 
+      free (name);
+
       ret = alloc_word_desc ();
       ret->word = temp;
       return ret;
@@ -7236,8 +7247,6 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
       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)))
diff --git a/subst.c~ b/subst.c~
new file mode 100644 (file)
index 0000000..c479757
--- /dev/null
+++ b/subst.c~
@@ -0,0 +1,9410 @@
+/* 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-2010 Free Software Foundation, Inc.
+
+   This file is part of GNU Bash, the Bourne Again SHell.
+
+   Bash is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   Bash is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#include "bashtypes.h"
+#include <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 "parser.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 "typemax.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 */
+#define PF_ASSIGNRHS   0x08    /* same as W_ASSIGNRHS */
+
+/* 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) \
+  ((posixly_correct == 0 && (c) == '#') || (posixly_correct == 0 && (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_upattern __P((char *, char *, int, char **, char **));
+#if defined (HANDLE_MULTIBYTE)
+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 *, int, char *, int, 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, arrayind_t *));
+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 *, arrayind_t, int, int, SHELL_VAR **, char **));
+static char *mb_substring __P((char *, int, int));
+static char *parameter_brace_substring __P((char *, char *, int, char *, int, int));
+
+static int shouldexp_replacement __P((char *));
+
+static char *pos_params_pat_subst __P((char *, char *, char *, int));
+
+static char *parameter_brace_patsub __P((char *, char *, int, char *, int, int));
+
+static char *pos_params_casemod __P((char *, char *, int, int));
+static char *parameter_brace_casemod __P((char *, char *, int, int, char *, int, 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)
+       {
+         /* XXX - take another look at this in light of Interp 221 */
+         /* 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, Q_DOUBLE_QUOTES, 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, Q_DOUBLE_QUOTES, 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, "(", ")", SX_COMMAND));
+}
+#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;
+       }
+
+      /* Process a nested command substitution, but only if we're parsing an
+        arithmetic substitution. */
+      if ((flags & SX_COMMAND) && string[i] == '$' && string[i+1] == LPAREN)
+        {
+          si = i + 2;
+          t = extract_command_subst (string, &si, flags|SX_NOALLOC);
+          i = si + 1;
+          continue;
+        }
+
+      /* 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, dolbrace_state;
+  char *result, *t;
+  DECLARE_MBSTATE;
+
+  pass_character = 0;
+  nesting_level = 1;
+  slen = strlen (string + *sindex) + *sindex;
+
+  /* The handling of dolbrace_state needs to agree with the code in parse.y:
+     parse_matched_pair().  The different initial value is to handle the
+     case where this function is called to parse the word in
+     ${param op word} (SX_WORD). */
+  dolbrace_state = (flags & SX_WORD) ? DOLBRACE_WORD : DOLBRACE_PARAM;
+  if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && (flags & SX_POSIXEXP))
+    dolbrace_state = DOLBRACE_QUOTE;
+
+  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;
+       }
+
+#if 0
+      /* 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;
+       }
+#else  /* XXX - bash-4.2 */
+      /* Pass the contents of double-quoted strings through verbatim. */
+      if (c == '"')
+       {
+         si = i + 1;
+         i = skip_double_quoted (string, slen, si);
+         /* skip_XXX_quoted leaves index one past close quote */
+         continue;
+       }
+
+      if (c == '\'')
+       {
+/*itrace("extract_dollar_brace_string: c == single quote flags = %d quoted = %d dolbrace_state = %d", flags, quoted, dolbrace_state);*/
+         if (posixly_correct && shell_compatibility_level > 41 && dolbrace_state != DOLBRACE_QUOTE && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+           ADVANCE_CHAR (string, slen, i);
+         else
+           {
+             si = i + 1;
+             i = skip_single_quoted (string, slen, si);
+           }
+
+          continue;
+       }
+#endif
+
+      /* move past this character, which was not special. */
+      ADVANCE_CHAR (string, slen, i);
+
+      /* This logic must agree with parse.y:parse_matched_pair, since they
+        share the same defines. */
+      if (dolbrace_state == DOLBRACE_PARAM && c == '%' && (i - *sindex) > 1)
+       dolbrace_state = DOLBRACE_QUOTE;
+      else if (dolbrace_state == DOLBRACE_PARAM && c == '#' && (i - *sindex) > 1)
+        dolbrace_state = DOLBRACE_QUOTE;
+      else if (dolbrace_state == DOLBRACE_PARAM && c == '/' && (i - *sindex) > 1)
+        dolbrace_state = DOLBRACE_QUOTE;
+      else if (dolbrace_state == DOLBRACE_PARAM && c == '^' && (i - *sindex) > 1)
+        dolbrace_state = DOLBRACE_QUOTE;
+      else if (dolbrace_state == DOLBRACE_PARAM && c == ',' && (i - *sindex) > 1)
+        dolbrace_state = DOLBRACE_QUOTE;
+      else if (dolbrace_state == DOLBRACE_PARAM && strchr ("#%^,~:-=?+/", c) != 0)
+       dolbrace_state = DOLBRACE_OP;
+      else if (dolbrace_state == DOLBRACE_OP && strchr ("#%^,~:-=?+/", c) == 0)
+       dolbrace_state = DOLBRACE_WORD;
+    }
+
+  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, open[3];
+  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);
+         free (temp);          /* no SX_ALLOC here */
+         i = si;
+         if (string[i] == '\0')
+           break;
+         i++;
+         continue;
+       }
+#endif /* PROCESS_SUBSTITUTION */
+#if defined (EXTENDED_GLOB)
+      else if ((flags & SD_EXTGLOB) && extended_glob && string[i+1] == LPAREN && member (c, "?*+!@"))
+       {
+         si = i + 2;
+         if (string[si] == '\0')
+           CQ_RETURN(si);
+
+         open[0] = c;
+         open[1] = LPAREN;
+         open[2] = '\0';
+         temp = extract_delimited_string (string, &si, open, "(", ")", SX_NOALLOC); /* ) */
+
+         i = si;
+         if (string[i] == '\0')        /* don't increment i past EOS in loop */
+           break;
+         i++;
+         continue;
+       }
+#endif
+      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 */
+  tlist = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES|Q_PATQUOTE))
+               ? 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, appendop, assign_list, aflags, retval;
+  char *name, *value, *temp;
+  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] == '=')
+    {
+      if (name[offset - 1] == '+')
+       {
+         appendop = 1;
+         name[offset - 1] = '\0';
+       }
+
+      name[offset] = 0;                /* might need this set later */
+      temp = name + offset + 1;
+
+#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, flags)
+     WORD_DESC *word;
+     int flags;
+{
+  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.
+   Since this does not perform word splitting, it leaves quoted nulls
+   in the result.  */
+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 = W_NOSPLIT2;               /* no splitting, remove "" and '' */
+  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++;
+         continue;
+       }
+
+      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
+
+/* Returns its first argument if nothing matched; new memory otherwise */
+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 (param);      /* no match, return original string */
+}
+
+#if defined (HANDLE_MULTIBYTE)
+/* Returns its first argument if nothing matched; new memory otherwise */
+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 (wparam);     /* no match, return original string */
+}
+#endif /* HANDLE_MULTIBYTE */
+
+static char *
+remove_pattern (param, pattern, op)
+     char *param, *pattern;
+     int op;
+{
+  char *xret;
+
+  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;
+
+      n = xdupmbstowcs (&wpattern, NULL, pattern);
+      if (n == (size_t)-1)
+       {
+         xret = remove_upattern (param, pattern, op);
+         return ((xret == param) ? savestring (param) : xret);
+       }
+      n = xdupmbstowcs (&wparam, NULL, param);
+      if (n == (size_t)-1)
+       {
+         free (wpattern);
+         xret = remove_upattern (param, pattern, op);
+         return ((xret == param) ? savestring (param) : xret);
+       }
+      oret = ret = remove_wpattern (wparam, n, wpattern, op);
+      /* Don't bother to convert wparam back to multibyte string if nothing
+        matched; just return copy of original string */
+      if (ret == wparam)
+        {
+          free (wparam);
+          free (wpattern);
+          return (savestring (param));
+        }
+
+      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
+    {
+      xret = remove_upattern (param, pattern, op);
+      return ((xret == param) ? savestring (param) : xret);
+    }
+}
+
+/* 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, mlen;
+  register char *p, *p1, *npat;
+  char *end;
+  int n1;
+
+  /* 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;
+
+  mlen = umatchlen (pat, len);
+
+  switch (mtype)
+    {
+    case MATCH_ANY:
+      for (p = string; p <= end; p++)
+       {
+         if (match_pattern_char (pat, p))
+           {
+#if 0
+             for (p1 = end; p1 >= p; p1--)
+#else
+             p1 = (mlen == -1) ? end : p + mlen;
+             /* p1 - p = length of portion of string to be considered
+                p = current position in string
+                mlen = number of characters consumed by match (-1 for entire string)
+                end = end of string
+                we want to break immediately if the potential match len
+                is greater than the number of characters remaining in the
+                string
+             */
+             if (p1 > end)
+               break;
+             for ( ; p1 >= p; p1--)
+#endif
+               {
+                 c = *p1; *p1 = '\0';
+                 if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0)
+                   {
+                     *p1 = c;
+                     *sp = p;
+                     *ep = p1;
+                     return 1;
+                   }
+                 *p1 = c;
+#if 1
+                 /* If MLEN != -1, we have a fixed length pattern. */
+                 if (mlen != -1)
+                   break;
+#endif
+               }
+           }
+       }
+
+      return (0);
+
+    case MATCH_BEG:
+      if (match_pattern_char (pat, string) == 0)
+       return (0);
+
+#if 0
+      for (p = end; p >= string; p--)
+#else
+      for (p = (mlen == -1) ? end : string + mlen; p >= string; p--)
+#endif
+       {
+         c = *p; *p = '\0';
+         if (strmatch (pat, string, FNMATCH_EXTFLAG) == 0)
+           {
+             *p = c;
+             *sp = string;
+             *ep = p;
+             return 1;
+           }
+         *p = c;
+#if 1
+         /* If MLEN != -1, we have a fixed length pattern. */
+         if (mlen != -1)
+           break;
+#endif
+       }
+
+      return (0);
+
+    case MATCH_END:
+#if 0
+      for (p = string; p <= end; p++)
+#else
+      for (p = end - ((mlen == -1) ? len : mlen); p <= end; p++)
+#endif
+       {
+         if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0)
+           {
+             *sp = p;
+             *ep = end;
+             return 1;
+           }
+#if 1
+         /* If MLEN != -1, we have a fixed length pattern. */
+         if (mlen != -1)
+           break;
+#endif
+       }
+
+      return (0);
+    }
+
+  return (0);
+}
+
+#if defined (HANDLE_MULTIBYTE)
+/* 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;
+  size_t len;
+  int mlen;
+  int n, n1, n2, simple;
+
+  simple = (wpat[0] != L'\\' && wpat[0] != L'*' && wpat[0] != L'?' && wpat[0] != L'[');
+#if defined (EXTENDED_GLOB)
+  if (extended_glob)
+    simple |= (wpat[1] != L'(' || (wpat[0] != L'*' && wpat[0] != L'?' && wpat[0] != L'+' && wpat[0] != L'!' && wpat[0] != L'@')); /*)*/
+#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. */
+  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);
+
+  mlen = wmatchlen (wpat, wstrlen);
+
+/* itrace("wmatchlen (%ls) -> %d", wpat, mlen); */
+  switch (mtype)
+    {
+    case MATCH_ANY:
+      for (n = 0; n <= wstrlen; n++)
+       {
+#if 1
+         n2 = simple ? (*wpat == wstring[n]) : match_pattern_wchar (wpat, wstring + n);
+#else
+         n2 = match_pattern_wchar (wpat, wstring + n);
+#endif
+         if (n2)
+           {
+#if 0
+             for (n1 = wstrlen; n1 >= n; n1--)
+#else
+             n1 = (mlen == -1) ? wstrlen : n + mlen;
+             if (n1 > wstrlen)
+               break;
+
+             for ( ; n1 >= n; n1--)
+#endif
+               {
+                 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;
+#if 1
+                 /* If MLEN != -1, we have a fixed length pattern. */
+                 if (mlen != -1)
+                   break;
+#endif
+               }
+           }
+       }
+
+      return (0);
+
+    case MATCH_BEG:
+      if (match_pattern_wchar (wpat, wstring) == 0)
+       return (0);
+
+#if 0
+      for (n = wstrlen; n >= 0; n--)
+#else
+      for (n = (mlen == -1) ? wstrlen : mlen; n >= 0; n--)
+#endif
+       {
+         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;
+#if 1
+         /* If MLEN != -1, we have a fixed length pattern. */
+         if (mlen != -1)
+           break;
+#endif
+       }
+
+      return (0);
+
+    case MATCH_END:
+#if 0
+      for (n = 0; n <= wstrlen; n++)
+#else
+      for (n = wstrlen - ((mlen == -1) ? wstrlen : mlen); n <= wstrlen; n++)
+#endif
+       {
+         if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG) == 0)
+           {
+             *sp = indices[n];
+             *ep = indices[wstrlen];
+             return 1;
+           }
+#if 1
+         /* If MLEN != -1, we have a fixed length pattern. */
+         if (mlen != -1)
+           break;
+#endif
+       }
+
+      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;
+  size_t slen, plen, mslen, mplen;
+#endif
+
+  if (string == 0 || *string == 0 || pat == 0 || *pat == 0)
+    return (0);
+
+#if defined (HANDLE_MULTIBYTE)
+  if (MB_CUR_MAX > 1)
+    {
+#if 0
+      slen = STRLEN (string);
+      mslen = MBSLEN (string);
+      plen = STRLEN (pat);
+      mplen = MBSLEN (pat);
+      if (slen == mslen && plen == mplen)
+#else
+      if (mbsmbchar (string) == 0 && mbsmbchar (pat) == 0)
+#endif
+        return (match_upattern (string, pat, mtype, sp, ep));
+
+      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, ind, patstr, rtype, quoted, flags)
+     char *varname, *value;
+     int ind;
+     char *patstr;
+     int rtype, quoted, flags;
+{
+  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, ind, quoted, flags, &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;
+  word->flags |= W_NOSPLIT2;
+  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;
+
+char *
+copy_fifo_list (sizep)
+     int *sizep;
+{
+  if (sizep)
+    *sizep = 0;
+  return (char *)NULL;
+}
+
+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 (i)
+     int 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;
+    }
+}
+
+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;
+}
+
+/* Take LIST, which is a bitmap denoting active FIFOs in fifo_list
+   from some point in the past, and close all open FIFOs in fifo_list
+   that are not marked as active in LIST.  If LIST is NULL, close
+   everything in fifo_list. LSIZE is the number of elements in LIST, in
+   case it's larger than fifo_list_size (size of fifo_list). */
+void
+close_new_fifos (list, lsize)
+     char *list;
+     int lsize;
+{
+  int i;
+
+  if (list == 0)
+    {
+      unlink_fifo_list ();
+      return;
+    }
+
+  for (i = 0; i < lsize; i++)
+    if (list[i] == 0 && i < fifo_list_size && fifo_list[i].proc != -1)
+      unlink_fifo (i);
+
+  for (i = lsize; i < fifo_list_size; i++)
+    unlink_fifo (i);  
+}
+
+int
+fifos_pending ()
+{
+  return nfifo;
+}
+
+int
+num_fifos ()
+{
+  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. */
+
+char *
+copy_fifo_list (sizep)
+     int *sizep;
+{
+  char *ret;
+
+  if (nfds == 0 || totfds == 0)
+    {
+      if (sizep)
+       *sizep = 0;
+      return (char *)NULL;
+    }
+
+  if (sizep)
+    *sizep = totfds;
+  ret = (char *)xmalloc (totfds);
+  return (memcpy (ret, dev_fd_list, totfds));
+}
+
+static void
+add_fifo_list (fd)
+     int fd;
+{
+  if (dev_fd_list == 0 || 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 */
+}
+
+int
+num_fifos ()
+{
+  return nfds;
+}
+
+void
+unlink_fifo (fd)
+     int fd;
+{
+  if (dev_fd_list[fd])
+    {
+      close (fd);
+      dev_fd_list[fd] = 0;
+      nfds--;
+    }
+}
+
+void
+unlink_fifo_list ()
+{
+  register int i;
+
+  if (nfds == 0)
+    return;
+
+  for (i = 0; nfds && i < totfds; i++)
+    unlink_fifo (i);
+
+  nfds = 0;
+}
+
+/* Take LIST, which is a snapshot copy of dev_fd_list from some point in
+   the past, and close all open fds in dev_fd_list that are not marked
+   as open in LIST.  If LIST is NULL, close everything in dev_fd_list.
+   LSIZE is the number of elements in LIST, in case it's larger than
+   totfds (size of dev_fd_list). */
+void
+close_new_fifos (list, lsize)
+     char *list;
+     int lsize;
+{
+  int i;
+
+  if (list == 0)
+    {
+      unlink_fifo_list ();
+      return;
+    }
+
+  for (i = 0; i < lsize; i++)
+    if (list[i] == 0 && i < totfds && dev_fd_list[i])
+      unlink_fifo (i);
+
+  for (i = lsize; i < totfds; i++)
+    unlink_fifo (i);  
+}
+
+#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 ();     /* XXX - what about special builtins? bash-4.2 */
+      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;
+
+  /* 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.  Set a flag noting that we have to free the
+        trap strings if we run trap to change a signal disposition. */
+      reset_signal_handlers ();
+      subshell_environment |= SUBSHELL_RESETTRAP;
+    }
+
+#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]);
+
+#ifdef __CYGWIN__
+      /* Let stdio know the fd may have changed from text to binary mode, and
+        make sure to preserve stdout line buffering. */
+      freopen (NULL, "w", stdout);
+      sh_setlinebuf (stdout);
+#endif /* __CYGWIN__ */
+
+      /* 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 (interactive && pipeline_pgrp != (pid_t)0 && (subshell_environment & SUBSHELL_ASYNC) == 0)
+       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 (var_isset (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);
+         FREE (akey);
+         return (-1);
+       }
+      t = assoc_reference (assoc_cell (var), akey);
+      free (akey);
+    }
+  else
+    {
+      ind = array_expand_index (var, 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, indp)
+     char *name;
+     int var_is_special, quoted, pflags;
+     arrayind_t *indp;
+{
+  WORD_DESC *ret;
+  char *temp, *tt;
+  intmax_t arg_index;
+  SHELL_VAR *var;
+  int atype, rflags;
+  arrayind_t ind;
+
+  ret = 0;
+  temp = 0;
+  rflags = 0;
+
+  if (indp)
+    *indp = INTMAX_MIN;
+
+  /* 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))
+    {
+      /* XXX - does this leak if name[@] or name[*]? */
+      temp = array_value (name, quoted, 0, &atype, &ind);
+      if (atype == 0 && temp)
+       {
+         temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+                   ? quote_string (temp)
+                   : quote_escapes (temp);
+         rflags |= W_ARRAYIND;
+         if (indp)
+           *indp = ind;
+       }                 
+      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, 0);
+  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, 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);
+
+  /* From Posix group discussion Feb-March 2010.  Issue 7 0000221 */
+  free (temp);
+
+  w->word = t1;
+  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} */
+}
+
+/* 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;   /* XXX - error if set -u set? */
+         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);
+         if (t == 0 && unbound_vars_is_error)
+           return INTMAX_MIN;
+         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);
+         if (t == 0 && unbound_vars_is_error)
+           return INTMAX_MIN;
+         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 = t ? MB_STRLEN (t) : 0;
+         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 ((vtype == VT_ARRAYVAR || vtype == VT_POSPARMS) && *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
+       {
+         if (*e2p < 0)
+           {
+             *e2p += len;
+             if (*e2p < 0 || *e2p < *e1p)
+               {
+                 internal_error (_("%s: substring expression < 0"), t);
+                 return (0);
+               }
+           }
+         else
+           *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).
+   QUOTED is the standard description of quoting state, using Q_* defines.
+   FLAGS is currently a set of flags to pass to array_value.  If IND is
+   non-null and not INTMAX_MIN, and FLAGS includes AV_USEIND, IND is
+   passed to array_value so the array index is not computed again.
+   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, ind, quoted, flags, varp, valp)
+     char *varname, *value;
+     arrayind_t ind;
+     int quoted, flags;
+     SHELL_VAR **varp;
+     char **valp;
+{
+  int vtype;
+  char *temp;
+#if defined (ARRAY_VARS)
+  SHELL_VAR *v;
+#endif
+  arrayind_t lind;
+
+  /* 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 we want to signal array_value to use an already-computed index,
+        set LIND to that index */
+      lind = (ind != INTMAX_MIN && (flags & AV_USEIND)) ? ind : 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, Q_DOUBLE_QUOTES, flags, (int *)NULL, &lind);
+           }
+         *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, Q_DOUBLE_QUOTES, flags, (int *)NULL, &lind);
+       }
+    }
+  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, ind, substr, quoted, flags)
+     char *varname, *value;
+     int ind;
+     char *substr;
+     int quoted, flags;
+{
+  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, ind, quoted, flags, &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)
+    {
+      if (vtype == VT_VARIABLE)
+       FREE (val);
+      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 */
+/*                                                             */
+/****************************************************************/
+
+static int
+shouldexp_replacement (s)
+     char *s;
+{
+  register char *p;
+
+  for (p = s; p && *p; p++)
+    {
+      if (*p == '\\')
+       p++;
+      else if (*p == '&')
+       return 1;
+    }
+  return 0;
+}
+
+char *
+pat_subst (string, pat, rep, mflags)
+     char *string, *pat, *rep;
+     int mflags;
+{
+  char *ret, *s, *e, *str, *rstr, *mstr;
+  int rsize, rptr, l, replen, mtype, rxpand, rslen, mlen;
+
+  if (string  == 0)
+    return (savestring (""));
+
+  mtype = mflags & MATCH_TYPEMASK;
+
+#if 0  /* bash-4.2 ? */
+  rxpand = (rep && *rep) ? shouldexp_replacement (rep) : 0;
+#else
+  rxpand = 0;
+#endif
+
+  /* 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.
+   * These don't understand or process `&' in the replacement string.
+   */
+  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;
+
+      if (rxpand)
+        {
+          int x;
+          mlen = e - s;
+          mstr = xmalloc (mlen + 1);
+         for (x = 0; x < mlen; x++)
+           mstr[x] = s[x];
+          mstr[mlen] = '\0';
+          rstr = strcreplace (rep, '&', mstr, 0);
+          rslen = strlen (rstr);
+        }
+      else
+        {
+          rstr = rep;
+          rslen = replen;
+        }
+        
+      RESIZE_MALLOCED_BUFFER (ret, rptr, (l + rslen), 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, rstr, rslen);
+         rptr += rslen;
+       }
+      str = e;         /* e == end of match */
+
+      if (rstr != rep)
+       free (rstr);
+
+      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 && *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, ind, patsub, quoted, flags)
+     char *varname, *value;
+     int ind;
+     char *patsub;
+     int quoted, flags;
+{
+  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, ind, quoted, flags, &v, &val);
+  if (vtype == -1)
+    return ((char *)NULL);
+
+  starsub = vtype & VT_STARSUB;
+  vtype &= ~VT_STARSUB;
+
+  mflags = 0;
+  /* PATSUB is never NULL when this is called. */
+  if (*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. */
+  delim = skip_to_delim (lpatsub, ((*patsub == '/') ? 1 : 0), "/", 0);
+  if (lpatsub[delim] == '/')
+    {
+      lpatsub[delim] = 0;
+      rep = lpatsub + delim + 1;
+    }
+  else
+    rep = (char *)NULL;
+
+  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, ind, modspec, patspec, quoted, flags)
+     char *varname, *value;
+     int ind, modspec;
+     char *patspec;
+     int quoted, flags;
+{
+  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, ind, quoted, flags, &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;
+  arrayind_t ind;
+
+  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;
+
+  ind = INTMAX_MIN;
+
+  /* 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 && VALID_SPECIAL_LENGTH_PARAM (string[t_index])) ||
+      (sindex == t_index - 1 && string[sindex] == '!' && VALID_INDIR_PARAM (string[t_index])))
+    {
+      t_index++;
+      temp1 = string_extract (string, &t_index, "#%:-=?+/}", 0);
+      name = (char *)xrealloc (name, 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 */) /* XXX */
+    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);
+      if (number == INTMAX_MIN && unbound_vars_is_error)
+       {
+         last_command_exit_value = EXECUTION_FAILURE;
+         err_unboundvar (name+1);
+         free (name);
+         return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+       }
+      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;
+
+      free (name);
+
+      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), &ind);
+
+  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, (c == '%' || c == '#' || c =='/' || c == '^' || c == ',' || c ==':') ? SX_POSIXEXP|SX_WORD : SX_WORD);
+      if (string[sindex] == RBRACE)
+       sindex++;
+      else
+       goto bad_substitution;
+    }
+  else
+    value = (char *)NULL;
+
+  *indexp = sindex;
+
+  /* All the cases where an expansion can possibly generate an unbound
+     variable error. */
+  if (want_substring || want_patsub || want_casemod || c == '#' || c == '%' || c == 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);
+       }
+    }
+    
+  /* If this is a substring spec, process it and add the result. */
+  if (want_substring)
+    {
+      temp1 = parameter_brace_substring (name, temp, ind, value, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
+      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, ind, value, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
+      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;
+    }
+#if defined (CASEMOD_EXPANSIONS)
+  else if (want_casemod)
+    {
+      temp1 = parameter_brace_casemod (name, temp, ind, modspec, value, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
+      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:
+      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, ind, value, c, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
+      free (temp);
+      free (value);
+      free (name);
+
+      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)
+               {
+                 /* From Posix discussion on austin-group list.  Issue 221
+                    requires that backslashes escaping `}' inside
+                    double-quoted ${...} be removed. */
+                 if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+                   quoted |= Q_DOLBRACE;
+                 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;
+
+             /* From Posix discussion on austin-group list.  Issue 221 requires
+                that backslashes escaping `}' inside double-quoted ${...} be
+                removed. */
+             if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+               quoted |= Q_DOLBRACE;
+             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);
+         if (temp)
+           {
+             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;
+
+      /* 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. */
+#if 0
+      temp = string_list_dollar_at (list, quoted);
+#else
+      temp = string_list_dollar_at (list, (pflags & PF_ASSIGNRHS) ? (quoted|Q_DOUBLE_QUOTES) : quoted);
+#endif
+
+      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;
+         if (word->flags & W_ASSIGNRHS)
+           pflags |= PF_ASSIGNRHS;
+         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 ? tword->word : (char *)NULL;
+         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;
+
+         /* From Posix discussion on austin-group list:  Backslash escaping
+            a } in ${...} is removed.  Issue 0000221 */
+         if ((quoted & Q_DOLBRACE) && c == RBRACE)
+           {
+             SCOPY_CHAR_I (twochars, CTLESC, c, string, sindex, string_size);
+           }
+         else 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.  The execption to
+            this is when we are going to be performing word splitting,
+            since we have to preserve a null argument if the next character
+            will cause word splitting. */
+         if (temp == 0 && quoted_state == PARTIALLY_QUOTED && (word->flags & (W_NOSPLIT|W_NOSPLIT2)))
+           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 = alloc_word_desc ();
+             w->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;
+             else
+               w = make_word_flags (w, temp_string);
+
+             output_list = make_word_list (w, output_list);
+           }
+         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, 0);
+         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, 0);
+                 /* 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;
+      int is_special_builtin, is_builtin_or_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;
+
+      is_builtin_or_func = (new_list && new_list->word && (find_shell_builtin (new_list->word->word) || find_function (new_list->word->word)));
+      /* Posix says that special builtins exit if a variable assignment error
+        occurs in an assignment preceding it. */
+      is_special_builtin = (posixly_correct && new_list && new_list->word && find_special_builtin (new_list->word->word));
+      
+      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, is_builtin_or_func);
+         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 && is_special_builtin)
+                   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);
+}
diff --git a/trap.c b/trap.c
index 2b02e3e680e52c67f34ec1b2645a60747ee12c06..c85a5614996fdb23033c43d0c06042bbba94893b 100644 (file)
--- a/trap.c
+++ b/trap.c
@@ -1058,7 +1058,7 @@ restore_original_signals ()
 }
 
 /* If a trap handler exists for signal SIG, then call it; otherwise just
-   return failure. */
+   return failure.  Returns 1 if it called the trap handler. */
 int
 maybe_call_trap_handler (sig)
      int sig;
diff --git a/trap.c~ b/trap.c~
new file mode 100644 (file)
index 0000000..2b02e3e
--- /dev/null
+++ b/trap.c~
@@ -0,0 +1,1134 @@
+/* trap.c -- Not the trap command, but useful functions for manipulating
+   those objects.  The trap command is in builtins/trap.def. */
+
+/* Copyright (C) 1987-2011 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 (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#include "bashtypes.h"
+#include "bashansi.h"
+
+#include <stdio.h>
+#include <errno.h>
+
+#include "bashintl.h"
+
+#include "trap.h"
+
+#include "shell.h"
+#include "flags.h"
+#include "input.h"     /* for save_token_state, restore_token_state */
+#include "jobs.h"
+#include "signames.h"
+#include "builtins.h"
+#include "builtins/common.h"
+#include "builtins/builtext.h"
+
+#ifndef errno
+extern int errno;
+#endif
+
+/* Flags which describe the current handling state of a signal. */
+#define SIG_INHERITED   0x0    /* Value inherited from parent. */
+#define SIG_TRAPPED     0x1    /* Currently trapped. */
+#define SIG_HARD_IGNORE 0x2    /* Signal was ignored on shell entry. */
+#define SIG_SPECIAL     0x4    /* Treat this signal specially. */
+#define SIG_NO_TRAP     0x8    /* Signal cannot be trapped. */
+#define SIG_INPROGRESS 0x10    /* Signal handler currently executing. */
+#define SIG_CHANGED    0x20    /* Trap value changed in trap handler. */
+#define SIG_IGNORED    0x40    /* The signal is currently being ignored. */
+
+#define SPECIAL_TRAP(s)        ((s) == EXIT_TRAP || (s) == DEBUG_TRAP || (s) == ERROR_TRAP || (s) == RETURN_TRAP)
+
+/* An array of such flags, one for each signal, describing what the
+   shell will do with a signal.  DEBUG_TRAP == NSIG; some code below
+   assumes this. */
+static int sigmodes[BASH_NSIG];
+
+static void free_trap_command __P((int));
+static void change_signal __P((int, char *));
+
+static void get_original_signal __P((int));
+
+static int _run_trap_internal __P((int, char *));
+
+static void free_trap_string __P((int));
+static void reset_signal __P((int));
+static void restore_signal __P((int));
+static void reset_or_restore_signal_handlers __P((sh_resetsig_func_t *));
+
+/* Variables used here but defined in other files. */
+extern int last_command_exit_value;
+extern int line_number;
+
+extern char *this_command_name;
+extern sh_builtin_func_t *this_shell_builtin;
+extern procenv_t wait_intr_buf;
+extern int return_catch_flag, return_catch_value;
+extern int subshell_level;
+extern WORD_LIST *subst_assign_varlist;
+
+/* The list of things to do originally, before we started trapping. */
+SigHandler *original_signals[NSIG];
+
+/* For each signal, a slot for a string, which is a command to be
+   executed when that signal is recieved.  The slot can also contain
+   DEFAULT_SIG, which means do whatever you were going to do before
+   you were so rudely interrupted, or IGNORE_SIG, which says ignore
+   this signal. */
+char *trap_list[BASH_NSIG];
+
+/* A bitmap of signals received for which we have trap handlers. */
+int pending_traps[NSIG];
+
+/* Set to the number of the signal we're running the trap for + 1.
+   Used in execute_cmd.c and builtins/common.c to clean up when
+   parse_and_execute does not return normally after executing the
+   trap command (e.g., when `return' is executed in the trap command). */
+int running_trap;
+
+/* Set to last_command_exit_value before running a trap. */
+int trap_saved_exit_value;
+
+/* The (trapped) signal received while executing in the `wait' builtin */
+int wait_signal_received;
+
+#define GETORIGSIG(sig) \
+  do { \
+    original_signals[sig] = (SigHandler *)set_signal_handler (sig, SIG_DFL); \
+    set_signal_handler (sig, original_signals[sig]); \
+    if (original_signals[sig] == SIG_IGN) \
+      sigmodes[sig] |= SIG_HARD_IGNORE; \
+  } while (0)
+
+#define SETORIGSIG(sig,handler) \
+  do { \
+    original_signals[sig] = handler; \
+    if (original_signals[sig] == SIG_IGN) \
+      sigmodes[sig] |= SIG_HARD_IGNORE; \
+  } while (0)
+
+#define GET_ORIGINAL_SIGNAL(sig) \
+  if (sig && sig < NSIG && original_signals[sig] == IMPOSSIBLE_TRAP_HANDLER) \
+    GETORIGSIG(sig)
+
+void
+initialize_traps ()
+{
+  register int i;
+
+  initialize_signames();
+
+  trap_list[EXIT_TRAP] = trap_list[DEBUG_TRAP] = trap_list[ERROR_TRAP] = trap_list[RETURN_TRAP] = (char *)NULL;
+  sigmodes[EXIT_TRAP] = sigmodes[DEBUG_TRAP] = sigmodes[ERROR_TRAP] = sigmodes[RETURN_TRAP] = SIG_INHERITED;
+  original_signals[EXIT_TRAP] = IMPOSSIBLE_TRAP_HANDLER;
+
+  for (i = 1; i < NSIG; i++)
+    {
+      pending_traps[i] = 0;
+      trap_list[i] = (char *)DEFAULT_SIG;
+      sigmodes[i] = SIG_INHERITED;     /* XXX - only set, not used */
+      original_signals[i] = IMPOSSIBLE_TRAP_HANDLER;
+    }
+
+  /* Show which signals are treated specially by the shell. */
+#if defined (SIGCHLD)
+  GETORIGSIG (SIGCHLD);
+  sigmodes[SIGCHLD] |= (SIG_SPECIAL | SIG_NO_TRAP);
+#endif /* SIGCHLD */
+
+  GETORIGSIG (SIGINT);
+  sigmodes[SIGINT] |= SIG_SPECIAL;
+
+#if defined (__BEOS__)
+  /* BeOS sets SIGINT to SIG_IGN! */
+  original_signals[SIGINT] = SIG_DFL;
+  sigmodes[SIGINT] &= ~SIG_HARD_IGNORE;
+#endif
+
+  GETORIGSIG (SIGQUIT);
+  sigmodes[SIGQUIT] |= SIG_SPECIAL;
+
+  if (interactive)
+    {
+      GETORIGSIG (SIGTERM);
+      sigmodes[SIGTERM] |= SIG_SPECIAL;
+    }
+}
+
+#ifdef DEBUG
+/* Return a printable representation of the trap handler for SIG. */
+static char *
+trap_handler_string (sig)
+     int sig;
+{
+  if (trap_list[sig] == (char *)DEFAULT_SIG)
+    return "DEFAULT_SIG";
+  else if (trap_list[sig] == (char *)IGNORE_SIG)
+    return "IGNORE_SIG";
+  else if (trap_list[sig] == (char *)IMPOSSIBLE_TRAP_HANDLER)
+    return "IMPOSSIBLE_TRAP_HANDLER";
+  else if (trap_list[sig])
+    return trap_list[sig];
+  else
+    return "NULL";
+}
+#endif
+
+/* Return the print name of this signal. */
+char *
+signal_name (sig)
+     int sig;
+{
+  char *ret;
+
+  /* on cygwin32, signal_names[sig] could be null */
+  ret = (sig >= BASH_NSIG || sig < 0 || signal_names[sig] == NULL)
+       ? _("invalid signal number")
+       : signal_names[sig];
+
+  return ret;
+}
+
+/* Turn a string into a signal number, or a number into
+   a signal number.  If STRING is "2", "SIGINT", or "INT",
+   then (int)2 is returned.  Return NO_SIG if STRING doesn't
+   contain a valid signal descriptor. */
+int
+decode_signal (string, flags)
+     char *string;
+     int flags;
+{
+  intmax_t sig;
+  char *name;
+
+  if (legal_number (string, &sig))
+    return ((sig >= 0 && sig < NSIG) ? (int)sig : NO_SIG);
+
+  /* A leading `SIG' may be omitted. */
+  for (sig = 0; sig < BASH_NSIG; sig++)
+    {
+      name = signal_names[sig];
+      if (name == 0 || name[0] == '\0')
+       continue;
+
+      /* Check name without the SIG prefix first case sensitivly or
+        insensitively depending on whether flags includes DSIG_NOCASE */
+      if (STREQN (name, "SIG", 3))
+       {
+         name += 3;
+
+         if ((flags & DSIG_NOCASE) && strcasecmp (string, name) == 0)
+           return ((int)sig);
+         else if ((flags & DSIG_NOCASE) == 0 && strcmp (string, name) == 0)
+           return ((int)sig);
+         /* If we can't use the `SIG' prefix to match, punt on this
+            name now. */
+         else if ((flags & DSIG_SIGPREFIX) == 0)
+           continue;
+       }
+
+      /* Check name with SIG prefix case sensitively or insensitively
+        depending on whether flags includes DSIG_NOCASE */
+      name = signal_names[sig];
+      if ((flags & DSIG_NOCASE) && strcasecmp (string, name) == 0)
+       return ((int)sig);
+      else if ((flags & DSIG_NOCASE) == 0 && strcmp (string, name) == 0)
+       return ((int)sig);
+    }
+
+  return (NO_SIG);
+}
+
+/* Non-zero when we catch a trapped signal. */
+static int catch_flag;
+
+void
+run_pending_traps ()
+{
+  register int sig;
+  int old_exit_value, *token_state;
+  WORD_LIST *save_subst_varlist;
+#if defined (ARRAY_VARS)
+  ARRAY *ps;
+#endif
+
+  if (catch_flag == 0)         /* simple optimization */
+    return;
+
+itrace("run_pending_traps: ");
+
+  catch_flag = 0;
+
+  /* Preserve $? when running trap. */
+  old_exit_value = last_command_exit_value;
+#if defined (ARRAY_VARS)
+  ps = save_pipestatus_array ();
+#endif
+
+  for (sig = 1; sig < NSIG; sig++)
+    {
+      /* XXX this could be made into a counter by using
+        while (pending_traps[sig]--) instead of the if statement. */
+      if (pending_traps[sig])
+       {
+#if defined (HAVE_POSIX_SIGNALS)
+         sigset_t set, oset;
+
+         sigemptyset (&set);
+         sigemptyset (&oset);
+
+         sigaddset (&set, sig);
+         sigprocmask (SIG_BLOCK, &set, &oset);
+#else
+#  if defined (HAVE_BSD_SIGNALS)
+         int oldmask = sigblock (sigmask (sig));
+#  endif
+#endif /* HAVE_POSIX_SIGNALS */
+
+         if (sig == SIGINT)
+           {
+             run_interrupt_trap ();
+             CLRINTERRUPT;
+           }
+#if defined (JOB_CONTROL) && defined (SIGCHLD)
+         else if (sig == SIGCHLD &&
+                  trap_list[SIGCHLD] != (char *)IMPOSSIBLE_TRAP_HANDLER &&
+                  (sigmodes[SIGCHLD] & SIG_INPROGRESS) == 0)
+           {
+             run_sigchld_trap (pending_traps[sig]);    /* use as counter */
+           }
+#endif
+         else if (trap_list[sig] == (char *)DEFAULT_SIG ||
+                  trap_list[sig] == (char *)IGNORE_SIG ||
+                  trap_list[sig] == (char *)IMPOSSIBLE_TRAP_HANDLER)
+           {
+             /* This is possible due to a race condition.  Say a bash
+                process has SIGTERM trapped.  A subshell is spawned
+                using { list; } & and the parent does something and kills
+                the subshell with SIGTERM.  It's possible for the subshell
+                to set pending_traps[SIGTERM] to 1 before the code in
+                execute_cmd.c eventually calls restore_original_signals
+                to reset the SIGTERM signal handler in the subshell.  The
+                next time run_pending_traps is called, pending_traps[SIGTERM]
+                will be 1, but the trap handler in trap_list[SIGTERM] will
+                be invalid (probably DEFAULT_SIG, but it could be IGNORE_SIG).
+                Unless we catch this, the subshell will dump core when
+                trap_list[SIGTERM] == DEFAULT_SIG, because DEFAULT_SIG is
+                usually 0x0. */
+             internal_warning (_("run_pending_traps: bad value in trap_list[%d]: %p"),
+                               sig, trap_list[sig]);
+             if (trap_list[sig] == (char *)DEFAULT_SIG)
+               {
+                 internal_warning (_("run_pending_traps: signal handler is SIG_DFL, resending %d (%s) to myself"), sig, signal_name (sig));
+                 kill (getpid (), sig);
+               }
+           }
+         else
+           {
+             token_state = save_token_state ();
+             save_subst_varlist = subst_assign_varlist;
+             subst_assign_varlist = 0;
+
+             parse_and_execute (savestring (trap_list[sig]), "trap", SEVAL_NONINT|SEVAL_NOHIST|SEVAL_RESETLINE);
+             restore_token_state (token_state);
+             free (token_state);
+
+             subst_assign_varlist = save_subst_varlist;
+           }
+
+         pending_traps[sig] = 0;
+
+#if defined (HAVE_POSIX_SIGNALS)
+         sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL);
+#else
+#  if defined (HAVE_BSD_SIGNALS)
+         sigsetmask (oldmask);
+#  endif
+#endif /* POSIX_VERSION */
+       }
+    }
+
+#if defined (ARRAY_VARS)
+  restore_pipestatus_array (ps);
+#endif
+  last_command_exit_value = old_exit_value;
+}
+
+sighandler
+trap_handler (sig)
+     int sig;
+{
+  int oerrno;
+
+  if ((sigmodes[sig] & SIG_TRAPPED) == 0)
+    {
+#if defined (DEBUG)
+      internal_warning ("trap_handler: signal %d: signal not trapped", sig);
+#endif
+      SIGRETURN (0);
+    }
+
+itrace("trap_handler: sig = %d", sig);
+
+  if ((sig >= NSIG) ||
+      (trap_list[sig] == (char *)DEFAULT_SIG) ||
+      (trap_list[sig] == (char *)IGNORE_SIG))
+    programming_error (_("trap_handler: bad signal %d"), sig);
+  else
+    {
+      oerrno = errno;
+#if defined (MUST_REINSTALL_SIGHANDLERS)
+#  if defined (JOB_CONTROL) && defined (SIGCHLD)
+      if (sig != SIGCHLD)
+#  endif /* JOB_CONTROL && SIGCHLD */
+      set_signal_handler (sig, trap_handler);
+#endif /* MUST_REINSTALL_SIGHANDLERS */
+
+      catch_flag = 1;
+      pending_traps[sig]++;
+
+      if (interrupt_immediately && this_shell_builtin && (this_shell_builtin == wait_builtin))
+       {
+         wait_signal_received = sig;
+         longjmp (wait_intr_buf, 1);
+       }
+
+      if (interrupt_immediately)
+       run_pending_traps ();
+
+      errno = oerrno;
+    }
+
+  SIGRETURN (0);
+}
+
+#if defined (JOB_CONTROL) && defined (SIGCHLD)
+
+#ifdef INCLUDE_UNUSED
+/* Make COMMAND_STRING be executed when SIGCHLD is caught. */
+void
+set_sigchld_trap (command_string)
+     char *command_string;
+{
+  set_signal (SIGCHLD, command_string);
+}
+#endif
+
+/* Make COMMAND_STRING be executed when SIGCHLD is caught iff SIGCHLD
+   is not already trapped.  IMPOSSIBLE_TRAP_HANDLER is used as a sentinel
+   to make sure that a SIGCHLD trap handler run via run_sigchld_trap can
+   reset the disposition to the default and not have the original signal
+   accidentally restored, undoing the user's command. */
+void
+maybe_set_sigchld_trap (command_string)
+     char *command_string;
+{
+  if ((sigmodes[SIGCHLD] & SIG_TRAPPED) == 0 && trap_list[SIGCHLD] == (char *)IMPOSSIBLE_TRAP_HANDLER)
+    set_signal (SIGCHLD, command_string);
+}
+
+/* Temporarily set the SIGCHLD trap string to IMPOSSIBLE_TRAP_HANDLER.  Used
+   as a sentinel in run_sigchld_trap and maybe_set_sigchld_trap to see whether
+   or not a SIGCHLD trap handler reset SIGCHLD disposition to the default. */
+void
+set_impossible_sigchld_trap ()
+{
+  restore_default_signal (SIGCHLD);
+  change_signal (SIGCHLD, (char *)IMPOSSIBLE_TRAP_HANDLER);
+  sigmodes[SIGCHLD] &= ~SIG_TRAPPED;   /* maybe_set_sigchld_trap checks this */
+}
+#endif /* JOB_CONTROL && SIGCHLD */
+
+void
+set_debug_trap (command)
+     char *command;
+{
+  set_signal (DEBUG_TRAP, command);
+}
+
+void
+set_error_trap (command)
+     char *command;
+{
+  set_signal (ERROR_TRAP, command);
+}
+
+void
+set_return_trap (command)
+     char *command;
+{
+  set_signal (RETURN_TRAP, command);
+}
+
+#ifdef INCLUDE_UNUSED
+void
+set_sigint_trap (command)
+     char *command;
+{
+  set_signal (SIGINT, command);
+}
+#endif
+
+/* Reset the SIGINT handler so that subshells that are doing `shellsy'
+   things, like waiting for command substitution or executing commands
+   in explicit subshells ( ( cmd ) ), can catch interrupts properly. */
+SigHandler *
+set_sigint_handler ()
+{
+  if (sigmodes[SIGINT] & SIG_HARD_IGNORE)
+    return ((SigHandler *)SIG_IGN);
+
+  else if (sigmodes[SIGINT] & SIG_IGNORED)
+    return ((SigHandler *)set_signal_handler (SIGINT, SIG_IGN)); /* XXX */
+
+  else if (sigmodes[SIGINT] & SIG_TRAPPED)
+    return ((SigHandler *)set_signal_handler (SIGINT, trap_handler));
+
+  /* The signal is not trapped, so set the handler to the shell's special
+     interrupt handler. */
+  else if (interactive)        /* XXX - was interactive_shell */
+    return (set_signal_handler (SIGINT, sigint_sighandler));
+  else
+    return (set_signal_handler (SIGINT, termsig_sighandler));
+}
+
+/* Return the correct handler for signal SIG according to the values in
+   sigmodes[SIG]. */
+SigHandler *
+trap_to_sighandler (sig)
+     int sig;
+{
+  if (sigmodes[sig] & (SIG_IGNORED|SIG_HARD_IGNORE))
+    return (SIG_IGN);
+  else if (sigmodes[sig] & SIG_TRAPPED)
+    return (trap_handler);
+  else
+    return (SIG_DFL);
+}
+
+/* Set SIG to call STRING as a command. */
+void
+set_signal (sig, string)
+     int sig;
+     char *string;
+{
+  sigset_t set, oset;
+
+  if (SPECIAL_TRAP (sig))
+    {
+      change_signal (sig, savestring (string));
+      if (sig == EXIT_TRAP && interactive == 0)
+       initialize_terminating_signals ();
+      return;
+    }
+
+  /* A signal ignored on entry to the shell cannot be trapped or reset, but
+     no error is reported when attempting to do so.  -- Posix.2 */
+  if (sigmodes[sig] & SIG_HARD_IGNORE)
+    return;
+
+  /* Make sure we have original_signals[sig] if the signal has not yet
+     been trapped. */
+  if ((sigmodes[sig] & SIG_TRAPPED) == 0)
+    {
+      /* If we aren't sure of the original value, check it. */
+      if (original_signals[sig] == IMPOSSIBLE_TRAP_HANDLER)
+        GETORIGSIG (sig);
+      if (original_signals[sig] == SIG_IGN)
+       return;
+    }
+
+  /* Only change the system signal handler if SIG_NO_TRAP is not set.
+     The trap command string is changed in either case.  The shell signal
+     handlers for SIGINT and SIGCHLD run the user specified traps in an
+     environment in which it is safe to do so. */
+  if ((sigmodes[sig] & SIG_NO_TRAP) == 0)
+    {
+      BLOCK_SIGNAL (sig, set, oset);
+      change_signal (sig, savestring (string));
+      set_signal_handler (sig, trap_handler);
+      UNBLOCK_SIGNAL (oset);
+    }
+  else
+    change_signal (sig, savestring (string));
+}
+
+static void
+free_trap_command (sig)
+     int sig;
+{
+  if ((sigmodes[sig] & SIG_TRAPPED) && trap_list[sig] &&
+      (trap_list[sig] != (char *)IGNORE_SIG) &&
+      (trap_list[sig] != (char *)DEFAULT_SIG) &&
+      (trap_list[sig] != (char *)IMPOSSIBLE_TRAP_HANDLER))
+    free (trap_list[sig]);
+}
+
+/* If SIG has a string assigned to it, get rid of it.  Then give it
+   VALUE. */
+static void
+change_signal (sig, value)
+     int sig;
+     char *value;
+{
+  if ((sigmodes[sig] & SIG_INPROGRESS) == 0)
+    free_trap_command (sig);
+  trap_list[sig] = value;
+
+  sigmodes[sig] |= SIG_TRAPPED;
+  if (value == (char *)IGNORE_SIG)
+    sigmodes[sig] |= SIG_IGNORED;
+  else
+    sigmodes[sig] &= ~SIG_IGNORED;
+  if (sigmodes[sig] & SIG_INPROGRESS)
+    sigmodes[sig] |= SIG_CHANGED;
+}
+
+static void
+get_original_signal (sig)
+     int sig;
+{
+  /* If we aren't sure the of the original value, then get it. */
+  if (sig > 0 && sig < NSIG && original_signals[sig] == (SigHandler *)IMPOSSIBLE_TRAP_HANDLER)
+    GETORIGSIG (sig);
+}
+
+void
+get_all_original_signals ()
+{
+  register int i;
+
+  for (i = 1; i < NSIG; i++)
+    GET_ORIGINAL_SIGNAL (i);
+}
+
+void
+set_original_signal (sig, handler)
+     int sig;
+     SigHandler *handler;
+{
+  if (sig > 0 && sig < NSIG && original_signals[sig] == (SigHandler *)IMPOSSIBLE_TRAP_HANDLER)
+    SETORIGSIG (sig, handler);
+}
+
+/* Restore the default action for SIG; i.e., the action the shell
+   would have taken before you used the trap command.  This is called
+   from trap_builtin (), which takes care to restore the handlers for
+   the signals the shell treats specially. */
+void
+restore_default_signal (sig)
+     int sig;
+{
+  if (SPECIAL_TRAP (sig))
+    {
+      if ((sig != DEBUG_TRAP && sig != ERROR_TRAP && sig != RETURN_TRAP) ||
+         (sigmodes[sig] & SIG_INPROGRESS) == 0)
+       free_trap_command (sig);
+      trap_list[sig] = (char *)NULL;
+      sigmodes[sig] &= ~SIG_TRAPPED;
+      if (sigmodes[sig] & SIG_INPROGRESS)
+       sigmodes[sig] |= SIG_CHANGED;
+      return;
+    }
+
+  GET_ORIGINAL_SIGNAL (sig);
+
+  /* A signal ignored on entry to the shell cannot be trapped or reset, but
+     no error is reported when attempting to do so.  Thanks Posix.2. */
+  if (sigmodes[sig] & SIG_HARD_IGNORE)
+    return;
+
+  /* If we aren't trapping this signal, don't bother doing anything else. */
+  if ((sigmodes[sig] & SIG_TRAPPED) == 0)
+    return;
+
+  /* Only change the signal handler for SIG if it allows it. */
+  if ((sigmodes[sig] & SIG_NO_TRAP) == 0)
+    set_signal_handler (sig, original_signals[sig]);
+
+  /* Change the trap command in either case. */
+  change_signal (sig, (char *)DEFAULT_SIG);
+
+  /* Mark the signal as no longer trapped. */
+  sigmodes[sig] &= ~SIG_TRAPPED;
+}
+
+/* Make this signal be ignored. */
+void
+ignore_signal (sig)
+     int sig;
+{
+  if (SPECIAL_TRAP (sig) && ((sigmodes[sig] & SIG_IGNORED) == 0))
+    {
+      change_signal (sig, (char *)IGNORE_SIG);
+      return;
+    }
+
+  GET_ORIGINAL_SIGNAL (sig);
+
+  /* A signal ignored on entry to the shell cannot be trapped or reset.
+     No error is reported when the user attempts to do so. */
+  if (sigmodes[sig] & SIG_HARD_IGNORE)
+    return;
+
+  /* If already trapped and ignored, no change necessary. */
+  if (sigmodes[sig] & SIG_IGNORED)
+    return;
+
+  /* Only change the signal handler for SIG if it allows it. */
+  if ((sigmodes[sig] & SIG_NO_TRAP) == 0)
+    set_signal_handler (sig, SIG_IGN);
+
+  /* Change the trap command in either case. */
+  change_signal (sig, (char *)IGNORE_SIG);
+}
+
+/* Handle the calling of "trap 0".  The only sticky situation is when
+   the command to be executed includes an "exit".  This is why we have
+   to provide our own place for top_level to jump to. */
+int
+run_exit_trap ()
+{
+  char *trap_command;
+  int code, function_code, retval;
+#if defined (ARRAY_VARS)
+  ARRAY *ps;
+#endif
+
+  trap_saved_exit_value = last_command_exit_value;
+#if defined (ARRAY_VARS)
+  ps = save_pipestatus_array ();
+#endif
+  function_code = 0;
+
+  /* Run the trap only if signal 0 is trapped and not ignored, and we are not
+     currently running in the trap handler (call to exit in the list of
+     commands given to trap 0). */
+  if ((sigmodes[EXIT_TRAP] & SIG_TRAPPED) &&
+      (sigmodes[EXIT_TRAP] & (SIG_IGNORED|SIG_INPROGRESS)) == 0)
+    {
+      trap_command = savestring (trap_list[EXIT_TRAP]);
+      sigmodes[EXIT_TRAP] &= ~SIG_TRAPPED;
+      sigmodes[EXIT_TRAP] |= SIG_INPROGRESS;
+
+      retval = trap_saved_exit_value;
+      running_trap = 1;
+
+      code = setjmp (top_level);
+
+      /* If we're in a function, make sure return longjmps come here, too. */
+      if (return_catch_flag)
+       function_code = setjmp (return_catch);
+
+      if (code == 0 && function_code == 0)
+       {
+         reset_parser ();
+         parse_and_execute (trap_command, "exit trap", SEVAL_NONINT|SEVAL_NOHIST|SEVAL_RESETLINE);
+       }
+      else if (code == ERREXIT)
+       retval = last_command_exit_value;
+      else if (code == EXITPROG)
+       retval = last_command_exit_value;
+      else if (function_code != 0)
+        retval = return_catch_value;
+      else
+       retval = trap_saved_exit_value;
+
+      running_trap = 0;
+      return retval;
+    }
+
+#if defined (ARRAY_VARS)
+  restore_pipestatus_array (ps);
+#endif
+  return (trap_saved_exit_value);
+}
+
+void
+run_trap_cleanup (sig)
+     int sig;
+{
+  sigmodes[sig] &= ~(SIG_INPROGRESS|SIG_CHANGED);
+}
+
+/* Run a trap command for SIG.  SIG is one of the signals the shell treats
+   specially.  Returns the exit status of the executed trap command list. */
+static int
+_run_trap_internal (sig, tag)
+     int sig;
+     char *tag;
+{
+  char *trap_command, *old_trap;
+  int trap_exit_value, *token_state;
+  int save_return_catch_flag, function_code, flags;
+  procenv_t save_return_catch;
+  WORD_LIST *save_subst_varlist;
+#if defined (ARRAY_VARS)
+  ARRAY *ps;
+#endif
+
+  trap_exit_value = function_code = 0;
+  /* Run the trap only if SIG is trapped and not ignored, and we are not
+     currently executing in the trap handler. */
+  if ((sigmodes[sig] & SIG_TRAPPED) && ((sigmodes[sig] & SIG_IGNORED) == 0) &&
+      (trap_list[sig] != (char *)IMPOSSIBLE_TRAP_HANDLER) &&
+      ((sigmodes[sig] & SIG_INPROGRESS) == 0))
+    {
+      old_trap = trap_list[sig];
+      sigmodes[sig] |= SIG_INPROGRESS;
+      sigmodes[sig] &= ~SIG_CHANGED;           /* just to be sure */
+      trap_command =  savestring (old_trap);
+
+      running_trap = sig + 1;
+      trap_saved_exit_value = last_command_exit_value;
+#if defined (ARRAY_VARS)
+      ps = save_pipestatus_array ();
+#endif
+
+      token_state = save_token_state ();
+      save_subst_varlist = subst_assign_varlist;
+      subst_assign_varlist = 0;
+
+      /* If we're in a function, make sure return longjmps come here, too. */
+      save_return_catch_flag = return_catch_flag;
+      if (return_catch_flag)
+       {
+         COPY_PROCENV (return_catch, save_return_catch);
+         function_code = setjmp (return_catch);
+       }
+
+      flags = SEVAL_NONINT|SEVAL_NOHIST;
+      if (sig != DEBUG_TRAP && sig != RETURN_TRAP && sig != ERROR_TRAP)
+       flags |= SEVAL_RESETLINE;
+      if (function_code == 0)
+       parse_and_execute (trap_command, tag, flags);
+
+      restore_token_state (token_state);
+      free (token_state);
+
+      subst_assign_varlist = save_subst_varlist;
+
+      trap_exit_value = last_command_exit_value;
+      last_command_exit_value = trap_saved_exit_value;
+#if defined (ARRAY_VARS)
+      restore_pipestatus_array (ps);
+#endif
+      running_trap = 0;
+
+      sigmodes[sig] &= ~SIG_INPROGRESS;
+
+      if (sigmodes[sig] & SIG_CHANGED)
+       {
+#if 0
+         /* Special traps like EXIT, DEBUG, RETURN are handled explicitly in
+            the places where they can be changed using unwind-protects.  For
+            example, look at execute_cmd.c:execute_function(). */
+         if (SPECIAL_TRAP (sig) == 0)
+#endif
+           free (old_trap);
+         sigmodes[sig] &= ~SIG_CHANGED;
+       }
+
+      if (save_return_catch_flag)
+       {
+         return_catch_flag = save_return_catch_flag;
+         return_catch_value = trap_exit_value;
+         COPY_PROCENV (save_return_catch, return_catch);
+         if (function_code)
+           longjmp (return_catch, 1);
+       }
+    }
+
+  return trap_exit_value;
+}
+
+int
+run_debug_trap ()
+{
+  int trap_exit_value;
+  pid_t save_pgrp;
+  int save_pipe[2];
+
+  /* XXX - question:  should the DEBUG trap inherit the RETURN trap? */
+  trap_exit_value = 0;
+  if ((sigmodes[DEBUG_TRAP] & SIG_TRAPPED) && ((sigmodes[DEBUG_TRAP] & SIG_IGNORED) == 0) && ((sigmodes[DEBUG_TRAP] & SIG_INPROGRESS) == 0))
+    {
+#if defined (JOB_CONTROL)
+      save_pgrp = pipeline_pgrp;
+      pipeline_pgrp = 0;
+      save_pipeline (1);
+#  if defined (PGRP_PIPE)
+      save_pgrp_pipe (save_pipe, 1);
+#  endif
+      stop_making_children ();
+#endif
+
+      trap_exit_value = _run_trap_internal (DEBUG_TRAP, "debug trap");
+
+#if defined (JOB_CONTROL)
+      pipeline_pgrp = save_pgrp;
+      restore_pipeline (1);
+#  if defined (PGRP_PIPE)
+      close_pgrp_pipe ();
+      restore_pgrp_pipe (save_pipe);
+#  endif
+      if (pipeline_pgrp > 0)
+       give_terminal_to (pipeline_pgrp, 1);
+      notify_and_cleanup ();
+#endif
+      
+#if defined (DEBUGGER)
+      /* If we're in the debugger and the DEBUG trap returns 2 while we're in
+        a function or sourced script, we force a `return'. */
+      if (debugging_mode && trap_exit_value == 2 && return_catch_flag)
+       {
+         return_catch_value = trap_exit_value;
+         longjmp (return_catch, 1);
+       }
+#endif
+    }
+  return trap_exit_value;
+}
+
+void
+run_error_trap ()
+{
+  if ((sigmodes[ERROR_TRAP] & SIG_TRAPPED) && ((sigmodes[ERROR_TRAP] & SIG_IGNORED) == 0) && (sigmodes[ERROR_TRAP] & SIG_INPROGRESS) == 0)
+    _run_trap_internal (ERROR_TRAP, "error trap");
+}
+
+void
+run_return_trap ()
+{
+  int old_exit_value;
+
+#if 0
+  if ((sigmodes[DEBUG_TRAP] & SIG_TRAPPED) && (sigmodes[DEBUG_TRAP] & SIG_INPROGRESS))
+    return;
+#endif
+
+  if ((sigmodes[RETURN_TRAP] & SIG_TRAPPED) && ((sigmodes[RETURN_TRAP] & SIG_IGNORED) == 0) && (sigmodes[RETURN_TRAP] & SIG_INPROGRESS) == 0)
+    {
+      old_exit_value = last_command_exit_value;
+      _run_trap_internal (RETURN_TRAP, "return trap");
+      last_command_exit_value = old_exit_value;
+    }
+}
+
+/* Run a trap set on SIGINT.  This is called from throw_to_top_level (), and
+   declared here to localize the trap functions. */
+void
+run_interrupt_trap ()
+{
+  _run_trap_internal (SIGINT, "interrupt trap");
+}
+
+/* Free all the allocated strings in the list of traps and reset the trap
+   values to the default.  Intended to be called from subshells that want
+   to complete work done by reset_signal_handlers upon execution of a
+   subsequent `trap' command that changes a signal's disposition.  We need
+   to make sure that we duplicate the behavior of
+   reset_or_restore_signal_handlers and not change the disposition of signals
+   that are set to be ignored. */
+void
+free_trap_strings ()
+{
+  register int i;
+
+  for (i = 0; i < BASH_NSIG; i++)
+    {
+      if (trap_list[i] != (char *)IGNORE_SIG)
+       free_trap_string (i);
+    }
+  trap_list[DEBUG_TRAP] = trap_list[EXIT_TRAP] = trap_list[ERROR_TRAP] = trap_list[RETURN_TRAP] = (char *)NULL;
+}
+
+/* Free a trap command string associated with SIG without changing signal
+   disposition.  Intended to be called from free_trap_strings()  */
+static void
+free_trap_string (sig)
+     int sig;
+{
+  change_signal (sig, (char *)DEFAULT_SIG);
+  sigmodes[sig] &= ~SIG_TRAPPED;
+}
+
+/* Reset the handler for SIG to the original value but leave the trap string
+   in place. */
+static void
+reset_signal (sig)
+     int sig;
+{
+  set_signal_handler (sig, original_signals[sig]);
+  sigmodes[sig] &= ~SIG_TRAPPED;
+}
+
+/* Set the handler signal SIG to the original and free any trap
+   command associated with it. */
+static void
+restore_signal (sig)
+     int sig;
+{
+  set_signal_handler (sig, original_signals[sig]);
+  change_signal (sig, (char *)DEFAULT_SIG);
+  sigmodes[sig] &= ~SIG_TRAPPED;
+}
+
+static void
+reset_or_restore_signal_handlers (reset)
+     sh_resetsig_func_t *reset;
+{
+  register int i;
+
+  /* Take care of the exit trap first */
+  if (sigmodes[EXIT_TRAP] & SIG_TRAPPED)
+    {
+      sigmodes[EXIT_TRAP] &= ~SIG_TRAPPED;
+      if (reset != reset_signal)
+       {
+         free_trap_command (EXIT_TRAP);
+         trap_list[EXIT_TRAP] = (char *)NULL;
+       }
+    }
+
+  for (i = 1; i < NSIG; i++)
+    {
+      if (sigmodes[i] & SIG_TRAPPED)
+       {
+         if (trap_list[i] == (char *)IGNORE_SIG)
+           set_signal_handler (i, SIG_IGN);
+         else
+           (*reset) (i);
+       }
+      else if (sigmodes[i] & SIG_SPECIAL)
+       (*reset) (i);
+    }
+
+  /* Command substitution and other child processes don't inherit the
+     debug, error, or return traps.  If we're in the debugger, and the
+     `functrace' or `errtrace' options have been set, then let command
+     substitutions inherit them.  Let command substitution inherit the
+     RETURN trap if we're in the debugger and tracing functions. */
+  if (function_trace_mode == 0)
+    {
+      sigmodes[DEBUG_TRAP] &= ~SIG_TRAPPED;
+      sigmodes[RETURN_TRAP] &= ~SIG_TRAPPED;
+    }
+  if (error_trace_mode == 0)
+    sigmodes[ERROR_TRAP] &= ~SIG_TRAPPED;
+}
+
+/* Reset trapped signals to their original values, but don't free the
+   trap strings.  Called by the command substitution code and other places
+   that create a "subshell environment". */
+void
+reset_signal_handlers ()
+{
+  reset_or_restore_signal_handlers (reset_signal);
+}
+
+/* Reset all trapped signals to their original values.  Signals set to be
+   ignored with trap '' SIGNAL should be ignored, so we make sure that they
+   are.  Called by child processes after they are forked. */
+void
+restore_original_signals ()
+{
+  reset_or_restore_signal_handlers (restore_signal);
+}
+
+/* If a trap handler exists for signal SIG, then call it; otherwise just
+   return failure. */
+int
+maybe_call_trap_handler (sig)
+     int sig;
+{
+  /* Call the trap handler for SIG if the signal is trapped and not ignored. */
+  if ((sigmodes[sig] & SIG_TRAPPED) && ((sigmodes[sig] & SIG_IGNORED) == 0))
+    {
+      switch (sig)
+       {
+       case SIGINT:
+         run_interrupt_trap ();
+         break;
+       case EXIT_TRAP:
+         run_exit_trap ();
+         break;
+       case DEBUG_TRAP:
+         run_debug_trap ();
+         break;
+       case ERROR_TRAP:
+         run_error_trap ();
+         break;
+       default:
+         trap_handler (sig);
+         break;
+       }
+      return (1);
+    }
+  else
+    return (0);
+}
+
+int
+signal_is_trapped (sig)
+     int sig;
+{
+  return (sigmodes[sig] & SIG_TRAPPED);
+}
+
+int
+signal_is_special (sig)
+     int sig;
+{
+  return (sigmodes[sig] & SIG_SPECIAL);
+}
+
+int
+signal_is_ignored (sig)
+     int sig;
+{
+  return (sigmodes[sig] & SIG_IGNORED);
+}
+
+int
+signal_is_hard_ignored (sig)
+     int sig;
+{
+  return (sigmodes[sig] & SIG_HARD_IGNORE);
+}
+
+void
+set_signal_ignored (sig)
+     int sig;
+{
+  sigmodes[sig] |= SIG_HARD_IGNORE;
+  original_signals[sig] = SIG_IGN;
+}
+
+int
+signal_in_progress (sig)
+     int sig;
+{
+  return (sigmodes[sig] & SIG_INPROGRESS);
+}
index 85f7bb4f2061343bc0789a9489310d1c6362fc4f..652d6ce47c35a452d7f41d26069b18f01f403719 100644 (file)
@@ -4071,7 +4071,7 @@ push_dollar_vars ()
     {
       dollar_arg_stack = (WORD_LIST **)
        xrealloc (dollar_arg_stack, (dollar_arg_stack_slots += 10)
-                 * sizeof (WORD_LIST **));
+                 * sizeof (WORD_LIST *));
     }
   dollar_arg_stack[dollar_arg_stack_index++] = list_rest_of_args ();
   dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL;
index 98d751645864636e8d23c09d4baeec134e75e794..85f7bb4f2061343bc0789a9489310d1c6362fc4f 100644 (file)
@@ -4650,7 +4650,6 @@ void
 sv_strict_posix (name)
      char *name;
 {
-itrace("sv_strict_posix: entry");
   SET_INT_VAR (name, posixly_correct);
   posix_initialize (posixly_correct);
 #if defined (READLINE)
@@ -4658,7 +4657,6 @@ itrace("sv_strict_posix: entry");
     posix_readline_initialize (posixly_correct);
 #endif /* READLINE */
   set_shellopts ();    /* make sure `posix' is/is not in $SHELLOPTS */
-itrace("sv_strict_posix: return");
 }
 
 void