commit bash-20040408 snapshot
authorChet Ramey <chet.ramey@case.edu>
Sat, 3 Dec 2011 17:56:32 +0000 (12:56 -0500)
committerChet Ramey <chet.ramey@case.edu>
Sat, 3 Dec 2011 17:56:32 +0000 (12:56 -0500)
28 files changed:
CWRU/CWRU.chlog
CWRU/CWRU.chlog~
builtins/alias.def
builtins/alias.def~ [new file with mode: 0644]
builtins/cd.def
execute_cmd.c~ [new file with mode: 0644]
externs.h
externs.h~ [new file with mode: 0644]
general.c
general.c~ [new file with mode: 0644]
general.h
general.h~ [new file with mode: 0644]
include/shmbutil.h
include/shmbutil.h.save1 [new file with mode: 0644]
include/shmbutil.h~
jobs.c
jobs.c~ [new file with mode: 0644]
print_cmd.c
print_cmd.c~ [new file with mode: 0644]
subst.c
subst.c~
syntax.h
syntax.h~ [new file with mode: 0644]
tests/RUN-ONE-TEST
tests/errors.right
tests/errors.right~ [new file with mode: 0644]
variables.c
variables.c~ [new file with mode: 0644]

index 9130bf0..7b16815 100644 (file)
@@ -9317,3 +9317,53 @@ configure.in
 
 include/posixdir.h
        - use new and renamed HAVE_STRUCT_DIRENT_D_xxx defines
+
+                                   4/7
+                                   ---
+builtins/cd.def
+       - ensure that we print out a non-null pathname after getting a
+         directory from CDPATH and canonicalizing it (e.g., if the result
+         exceeds PATH_MAX in length and the_current_working_directory is
+         set to NULL)
+
+                                  4/12
+                                  ----
+print_cmd.c
+       - new functionto print out assignment statements when `set -x' has
+         been enabled: xtrace_print_assignment
+
+externs.h
+       - extern declaration for xtrace_print_assignment
+
+                                  4/13
+                                  ----
+{subst,variables}.c
+       - call xtrace_print_assignment instead of using inline code
+
+jobs.c
+       - if turning on job control when it was previously off, set
+         pipeline_pgrp to 0 in set_job_control so make_child puts
+         subsequent children in their own process group
+
+                                  4/14
+                                  ----
+general.c
+       - new function, legal_alias_name, called to decide whether an
+         argument to add_alias is a valid alias name -- essentially any
+         character except one which must be quoted to the shell parser
+         and `/'
+
+general.h
+       - new extern declaration for legal_alias_name
+
+builtins/alias.def
+       - `unalias' now returns failure status if no NAME arguments are
+         supplied and -a is not given
+       - call legal_alias_name to make sure alias name is valid before
+         calling add_alias from alias_builtin
+
+                                  4/19
+                                  ----
+include/shmbutil.h
+       - include <config.h> for definition of HANDLE_MULTIBYTE rather than
+         duplicating logic
index a35dff8..126cd0c 100644 (file)
@@ -9264,6 +9264,13 @@ expr.c
          pre-decrement work when separated from their accompanying identifier
          by whitespace
 
+                                  3/18
+                                  ----
+lib/readline/misc.c
+       - in rl_maybe_unsave_line, don't force rl_replace_line to clear
+         the undo_list, since it might point directly at an undo list
+         from a history entry (to which we have no handle)
+
                                   3/19
                                   ----
 lib/readline/display.c
@@ -9295,3 +9302,63 @@ bashhist.c
          that returned `print only' to the history list, since history_expand
          no longer does it (and, when using readline, do it only when
          rl_dispatching is zero)
+
+                                  3/22
+                                  ----
+config.h.in,aclocal.m4
+       - change bash-specific functions that look in struct dirent to define
+         HAVE_STRUCT_DIRENT_xxx, like AC_CHECK_MEMBERS does (though the
+         functions are otherwise the same)
+       - new function, BASH_STRUCT_DIRENT_D_NAMLEN, define
+         HAVE_STRUCT_DIRENT_D_NAMLEN if struct dirent has a `d_namlen' member
+
+configure.in
+       - call BASH_STRUCT_DIRENT_D_NAMLEN
+
+include/posixdir.h
+       - use new and renamed HAVE_STRUCT_DIRENT_D_xxx defines
+
+                                   4/7
+                                   ---
+builtins/cd.def
+       - ensure that we print out a non-null pathname after getting a
+         directory from CDPATH and canonicalizing it (e.g., if the result
+         exceeds PATH_MAX in length and the_current_working_directory is
+         set to NULL)
+
+                                  4/12
+                                  ----
+print_cmd.c
+       - new functionto print out assignment statements when `set -x' has
+         been enabled: xtrace_print_assignment
+
+externs.h
+       - extern declaration for xtrace_print_assignment
+
+                                  4/13
+                                  ----
+{subst,variables}.c
+       - call xtrace_print_assignment instead of using inline code
+
+jobs.c
+       - if turning on job control when it was previously off, set
+         pipeline_pgrp to 0 in set_job_control so make_child puts
+         subsequent children in their own process group
+
+                                  4/14
+                                  ----
+general.c
+       - new function, legal_alias_name, called to decide whether an
+         argument to add_alias is a valid alias name -- essentially any
+         character except one which must be quoted to the shell parser
+         and `/'
+
+general.h
+       - new extern declaration for legal_alias_name
+
+builtins/alias.def
+       - `unalias' now returns failure status if no NAME arguments are
+         supplied and -a is not given
+       - call legal_alias_name to make sure alias name is valid before
+         calling add_alias from alias_builtin
+
index 5c7ed3d..ec83d0b 100644 (file)
@@ -118,7 +118,13 @@ alias_builtin (list)
          name[offset] = '\0';
          value = name + offset + 1;
 
-         add_alias (name, value);
+         if (legal_alias_name (name, 0) == 0)
+           {
+             builtin_error ("%s: invalid alias name", name);
+             any_failed++;
+           }
+         else
+           add_alias (name, value);
        }
       else
        {
@@ -141,7 +147,7 @@ alias_builtin (list)
 $BUILTIN unalias
 $FUNCTION unalias_builtin
 $DEPENDS_ON ALIAS
-$SHORT_DOC unalias [-a] [name ...]
+$SHORT_DOC unalias [-a] name [name ...]
 Remove NAMEs from the list of defined aliases.  If the -a option is given,
 then remove all alias definitions.
 $END
@@ -178,6 +184,12 @@ unalias_builtin (list)
       return (EXECUTION_SUCCESS);
     }
 
+  if (list == 0)
+    {
+      builtin_usage ();
+      return (EX_USAGE);
+    }
+
   aflag = 0;
   while (list)
     {
diff --git a/builtins/alias.def~ b/builtins/alias.def~
new file mode 100644 (file)
index 0000000..2ffae28
--- /dev/null
@@ -0,0 +1,229 @@
+This file is alias.def, from which is created alias.c
+It implements the builtins "alias" and "unalias" in Bash.
+
+Copyright (C) 1987-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 2, or (at your option) any later
+version.
+
+Bash is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License along
+with Bash; see the file COPYING.  If not, write to the Free Software
+Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA.
+
+$BUILTIN alias
+$FUNCTION alias_builtin
+$DEPENDS_ON ALIAS
+$PRODUCES alias.c
+$SHORT_DOC alias [-p] [name[=value] ... ]
+`alias' with no arguments or with the -p option prints the list
+of aliases in the form alias NAME=VALUE on standard output.
+Otherwise, an alias is defined for each NAME whose VALUE is given.
+A trailing space in VALUE causes the next word to be checked for
+alias substitution when the alias is expanded.  Alias returns
+true unless a NAME is given for which no alias has been defined.
+$END
+
+#include <config.h>
+
+#if defined (ALIAS)
+
+#if defined (HAVE_UNISTD_H)
+#  ifdef _MINIX
+#    include <sys/types.h>
+#  endif
+#  include <unistd.h>
+#endif
+
+#  include "../bashansi.h"
+
+#  include <stdio.h>
+#  include "../shell.h"
+#  include "../alias.h"
+#  include "common.h"
+#  include "bashgetopt.h"
+
+/* Flags for print_alias */
+#define AL_REUSABLE    0x01
+
+static void print_alias __P((alias_t *, int));
+
+extern int posixly_correct;
+
+/* Hack the alias command in a Korn shell way. */
+int
+alias_builtin (list)
+     WORD_LIST *list;
+{
+  int any_failed, offset, pflag, dflags;
+  alias_t **alias_list, *t;
+  char *name, *value;
+
+  dflags = posixly_correct ? 0 : AL_REUSABLE;
+  pflag = 0;
+  reset_internal_getopt ();
+  while ((offset = internal_getopt (list, "p")) != -1)
+    {
+      switch (offset)
+       {
+       case 'p':
+         pflag = 1;
+         dflags |= AL_REUSABLE;
+         break;
+       default:
+         builtin_usage ();
+         return (EX_USAGE);
+       }
+    }
+
+  list = loptend;
+
+  if (list == 0 || pflag)
+    {
+      if (aliases == 0)
+       return (EXECUTION_SUCCESS);
+
+      alias_list = all_aliases ();
+
+      if (alias_list == 0)
+       return (EXECUTION_SUCCESS);
+
+      for (offset = 0; alias_list[offset]; offset++)
+       print_alias (alias_list[offset], dflags);
+
+      free (alias_list);       /* XXX - Do not free the strings. */
+
+      if (list == 0)
+       return (EXECUTION_SUCCESS);
+    }
+
+  any_failed = 0;
+  while (list)
+    {
+      name = list->word->word;
+
+      for (offset = 0; name[offset] && name[offset] != '='; offset++)
+       ;
+
+      if (offset && name[offset] == '=')
+       {
+         name[offset] = '\0';
+         value = name + offset + 1;
+
+         if (legal_alias_name (name, 0) == 0)
+           {
+             builtin_error ("%s: invalid alias name", name);
+             any_failed++;
+             continue;
+           }
+
+         add_alias (name, value);
+       }
+      else
+       {
+         t = find_alias (name);
+         if (t)
+           print_alias (t, dflags);
+         else
+           {
+             sh_notfound (name);
+             any_failed++;
+           }
+       }
+      list = list->next;
+    }
+
+  return (any_failed ? EXECUTION_FAILURE : EXECUTION_SUCCESS);
+}
+#endif /* ALIAS */
+
+$BUILTIN unalias
+$FUNCTION unalias_builtin
+$DEPENDS_ON ALIAS
+$SHORT_DOC unalias [-a] name [name ...]
+Remove NAMEs from the list of defined aliases.  If the -a option is given,
+then remove all alias definitions.
+$END
+
+#if defined (ALIAS)
+/* Remove aliases named in LIST from the aliases database. */
+int
+unalias_builtin (list)
+     register WORD_LIST *list;
+{
+  register alias_t *alias;
+  int opt, aflag;
+
+  aflag = 0;
+  reset_internal_getopt ();
+  while ((opt = internal_getopt (list, "a")) != -1)
+    {
+      switch (opt)
+       {
+       case 'a':
+         aflag = 1;
+         break;
+       default:
+         builtin_usage ();
+         return (EX_USAGE);
+       }
+    }
+
+  list = loptend;
+
+  if (aflag)
+    {
+      delete_all_aliases ();
+      return (EXECUTION_SUCCESS);
+    }
+
+  if (list == 0)
+    {
+      builtin_usage ();
+      return (EX_USAGE);
+    }
+
+  aflag = 0;
+  while (list)
+    {
+      alias = find_alias (list->word->word);
+
+      if (alias)
+       remove_alias (alias->name);
+      else
+       {
+         sh_notfound (list->word->word);
+         aflag++;
+       }
+
+      list = list->next;
+    }
+
+  return (aflag ? EXECUTION_FAILURE : EXECUTION_SUCCESS);
+}
+
+/* Output ALIAS in such a way as to allow it to be read back in. */
+static void
+print_alias (alias, flags)
+     alias_t *alias;
+     int flags;
+{
+  char *value;
+
+  value = sh_single_quote (alias->value);
+  if (flags & AL_REUSABLE)
+    printf ("alias ");
+  printf ("%s=%s\n", alias->name, value);
+  free (value);
+
+  fflush (stdout);
+}
+#endif /* ALIAS */
index 10cbe98..a902476 100644 (file)
@@ -228,8 +228,8 @@ cd_builtin (list)
                 is used to find the directory to change to, the new
                 directory name is echoed to stdout, whether or not
                 the shell is interactive. */
-             if (opt)
-               printf ("%s\n", no_symlinks ? temp : the_current_working_directory);
+             if (opt && (path = no_symlinks ? temp : the_current_working_directory))
+               printf ("%s\n", path);
 
              free (temp);
              /* Posix.2 says that after using CDPATH, the resultant
diff --git a/execute_cmd.c~ b/execute_cmd.c~
new file mode 100644 (file)
index 0000000..3aefd2f
--- /dev/null
@@ -0,0 +1,4023 @@
+/* execute_command.c -- Execute a COMMAND structure. */
+
+/* Copyright (C) 1987-2003 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 2, or (at your option)
+   any later version.
+
+   Bash is distributed in the hope that it will be useful, but WITHOUT
+   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
+   License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with Bash; see the file COPYING.  If not, write to the Free
+   Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+#include "config.h"
+
+#if !defined (__GNUC__) && !defined (HAVE_ALLOCA_H) && defined (_AIX)
+  #pragma alloca
+#endif /* _AIX && RISC6000 && !__GNUC__ */
+
+#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"
+#include <signal.h>
+#ifndef _MINIX
+#  include <sys/param.h>
+#endif
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#include "posixtime.h"
+
+#if defined (HAVE_SYS_RESOURCE_H) && !defined (RLIMTYPE)
+#  include <sys/resource.h>
+#endif
+
+#if defined (HAVE_SYS_TIMES_H) && defined (HAVE_TIMES)
+#  include <sys/times.h>
+#endif
+
+#include <errno.h>
+
+#if !defined (errno)
+extern int errno;
+#endif
+
+#include "bashansi.h"
+#include "bashintl.h"
+
+#include "memalloc.h"
+#include "shell.h"
+#include <y.tab.h>     /* use <...> so we pick it up from the build directory */
+#include "flags.h"
+#include "builtins.h"
+#include "hashlib.h"
+#include "jobs.h"
+#include "execute_cmd.h"
+#include "findcmd.h"
+#include "redir.h"
+#include "trap.h"
+#include "pathexp.h"
+#include "hashcmd.h"
+
+#if defined (COND_COMMAND)
+#  include "test.h"
+#endif
+
+#include "builtins/common.h"
+#include "builtins/builtext.h" /* list of builtins */
+
+#include <glob/strmatch.h>
+#include <tilde/tilde.h>
+
+#if defined (BUFFERED_INPUT)
+#  include "input.h"
+#endif
+
+#if defined (ALIAS)
+#  include "alias.h"
+#endif
+
+#if defined (HISTORY)
+#  include "bashhist.h"
+#endif
+
+extern int posixly_correct;
+extern int breaking, continuing, loop_level;
+extern int expand_aliases;
+extern int parse_and_execute_level, running_trap;
+extern int command_string_index, line_number;
+extern int dot_found_in_search;
+extern int already_making_children;
+extern char *the_printed_command, *shell_name;
+extern pid_t last_command_subst_pid;
+extern sh_builtin_func_t *last_shell_builtin, *this_shell_builtin;
+extern char **subshell_argv, **subshell_envp;
+extern int subshell_argc;
+#if 0
+extern char *glob_argv_flags;
+#endif
+
+extern int close __P((int));
+
+/* Static functions defined and used in this file. */
+static void close_pipes __P((int, int));
+static void do_piping __P((int, int));
+static void bind_lastarg __P((char *));
+static int shell_control_structure __P((enum command_type));
+static void cleanup_redirects __P((REDIRECT *));
+
+#if defined (JOB_CONTROL)
+static int restore_signal_mask __P((sigset_t *));
+#endif
+
+static void async_redirect_stdin __P((void));
+
+static int builtin_status __P((int));
+
+static int execute_for_command __P((FOR_COM *));
+#if defined (SELECT_COMMAND)
+static int print_index_and_element __P((int, int, WORD_LIST *));
+static void indent __P((int, int));
+static void print_select_list __P((WORD_LIST *, int, int, int));
+static char *select_query __P((WORD_LIST *, int, char *, int));
+static int execute_select_command __P((SELECT_COM *));
+#endif
+#if defined (DPAREN_ARITHMETIC)
+static int execute_arith_command __P((ARITH_COM *));
+#endif
+#if defined (COND_COMMAND)
+static int execute_cond_node __P((COND_COM *));
+static int execute_cond_command __P((COND_COM *));
+#endif
+#if defined (COMMAND_TIMING)
+static int mkfmt __P((char *, int, int, time_t, int));
+static void print_formatted_time __P((FILE *, char *,
+                                     time_t, int, time_t, int,
+                                     time_t, int, int));
+static int time_command __P((COMMAND *, int, int, int, struct fd_bitmap *));
+#endif
+#if defined (ARITH_FOR_COMMAND)
+static intmax_t eval_arith_for_expr __P((WORD_LIST *, int *));
+static int execute_arith_for_command __P((ARITH_FOR_COM *));
+#endif
+static int execute_case_command __P((CASE_COM *));
+static int execute_while_command __P((WHILE_COM *));
+static int execute_until_command __P((WHILE_COM *));
+static int execute_while_or_until __P((WHILE_COM *, int));
+static int execute_if_command __P((IF_COM *));
+static int execute_null_command __P((REDIRECT *, int, int, int, pid_t));
+static void fix_assignment_words __P((WORD_LIST *));
+static int execute_simple_command __P((SIMPLE_COM *, int, int, int, struct fd_bitmap *));
+static int execute_builtin __P((sh_builtin_func_t *, WORD_LIST *, int, int));
+static int execute_function __P((SHELL_VAR *, WORD_LIST *, int, struct fd_bitmap *, int, int));
+static int execute_builtin_or_function __P((WORD_LIST *, sh_builtin_func_t *,
+                                           SHELL_VAR *,
+                                           REDIRECT *, struct fd_bitmap *, int));
+static void execute_subshell_builtin_or_function __P((WORD_LIST *, REDIRECT *,
+                                                     sh_builtin_func_t *,
+                                                     SHELL_VAR *,
+                                                     int, int, int,
+                                                     struct fd_bitmap *,
+                                                     int));
+static void execute_disk_command __P((WORD_LIST *, REDIRECT *, char *,
+                                     int, int, int, struct fd_bitmap *, int));
+
+static char *getinterp __P((char *, int, int *));
+static void initialize_subshell __P((void));
+static int execute_in_subshell __P((COMMAND *, int, int, int, struct fd_bitmap *));
+
+static int execute_pipeline __P((COMMAND *, int, int, int, struct fd_bitmap *));
+
+static int execute_connection __P((COMMAND *, int, int, int, struct fd_bitmap *));
+
+static int execute_intern_function __P((WORD_DESC *, COMMAND *));
+
+/* The line number that the currently executing function starts on. */
+static int function_line_number;
+
+/* Set to 1 if fd 0 was the subject of redirection to a subshell.  Global
+   so that reader_loop can set it to zero before executing a command. */
+int stdin_redir;
+
+/* The name of the command that is currently being executed.
+   `test' needs this, for example. */
+char *this_command_name;
+
+/* The printed representation of the currently-executing command (same as
+   the_printed_command), except when a trap is being executed.  Useful for
+   a debugger to know where exactly the program is currently executing. */
+char *the_printed_command_except_trap;
+
+static COMMAND *currently_executing_command;
+
+struct stat SB;                /* used for debugging */
+
+static int special_builtin_failed;
+
+/* XXX - set to 1 if we're running the DEBUG trap and we want to show the line
+   number containing the function name.  Used by executing_line_number to
+   report the correct line number.  Kind of a hack. */
+static int showing_function_line;
+
+/* For catching RETURN in a function. */
+int return_catch_flag;
+int return_catch_value;
+procenv_t return_catch;
+
+/* The value returned by the last synchronous command. */
+int last_command_exit_value;
+
+/* Whether or not the last command (corresponding to last_command_exit_value)
+   was terminated by a signal, and, if so, which one. */
+int last_command_exit_signal;
+
+/* The list of redirections to perform which will undo the redirections
+   that I made in the shell. */
+REDIRECT *redirection_undo_list = (REDIRECT *)NULL;
+
+/* The list of redirections to perform which will undo the internal
+   redirections performed by the `exec' builtin.  These are redirections
+   that must be undone even when exec discards redirection_undo_list. */
+REDIRECT *exec_redirection_undo_list = (REDIRECT *)NULL;
+
+/* Non-zero if we have just forked and are currently running in a subshell
+   environment. */
+int subshell_environment;
+
+/* Count of nested subshells, like SHLVL.  Available via $BASH_SUBSHELL */
+int subshell_level = 0;
+
+/* Currently-executing shell function. */
+SHELL_VAR *this_shell_function;
+
+struct fd_bitmap *current_fds_to_close = (struct fd_bitmap *)NULL;
+
+#define FD_BITMAP_DEFAULT_SIZE 32
+
+/* Functions to allocate and deallocate the structures used to pass
+   information from the shell to its children about file descriptors
+   to close. */
+struct fd_bitmap *
+new_fd_bitmap (size)
+     int size;
+{
+  struct fd_bitmap *ret;
+
+  ret = (struct fd_bitmap *)xmalloc (sizeof (struct fd_bitmap));
+
+  ret->size = size;
+
+  if (size)
+    {
+      ret->bitmap = (char *)xmalloc (size);
+      memset (ret->bitmap, '\0', size);
+    }
+  else
+    ret->bitmap = (char *)NULL;
+  return (ret);
+}
+
+void
+dispose_fd_bitmap (fdbp)
+     struct fd_bitmap *fdbp;
+{
+  FREE (fdbp->bitmap);
+  free (fdbp);
+}
+
+void
+close_fd_bitmap (fdbp)
+     struct fd_bitmap *fdbp;
+{
+  register int i;
+
+  if (fdbp)
+    {
+      for (i = 0; i < fdbp->size; i++)
+       if (fdbp->bitmap[i])
+         {
+           close (i);
+           fdbp->bitmap[i] = 0;
+         }
+    }
+}
+
+/* Return the line number of the currently executing command. */
+int
+executing_line_number ()
+{
+  if (executing && showing_function_line == 0 &&
+      (variable_context == 0 || interactive_shell == 0) &&
+      currently_executing_command)
+    {
+#if defined (COND_COMMAND)
+      if (currently_executing_command->type == cm_cond)
+       return currently_executing_command->value.Cond->line;
+#endif
+#if defined (DPAREN_ARITHMETIC)
+      else if (currently_executing_command->type == cm_arith)
+       return currently_executing_command->value.Arith->line;
+#endif
+#if defined (ARITH_FOR_COMMAND)
+      else if (currently_executing_command->type == cm_arith_for)
+       return currently_executing_command->value.ArithFor->line;
+#endif
+
+       return line_number;
+    }
+  else
+    return line_number;
+}
+
+/* Execute the command passed in COMMAND.  COMMAND is exactly what
+   read_command () places into GLOBAL_COMMAND.  See "command.h" for the
+   details of the command structure.
+
+   EXECUTION_SUCCESS or EXECUTION_FAILURE are the only possible
+   return values.  Executing a command with nothing in it returns
+   EXECUTION_SUCCESS. */
+int
+execute_command (command)
+     COMMAND *command;
+{
+  struct fd_bitmap *bitmap;
+  int result;
+
+  current_fds_to_close = (struct fd_bitmap *)NULL;
+  bitmap = new_fd_bitmap (FD_BITMAP_DEFAULT_SIZE);
+  begin_unwind_frame ("execute-command");
+  add_unwind_protect (dispose_fd_bitmap, (char *)bitmap);
+
+  /* Just do the command, but not asynchronously. */
+  result = execute_command_internal (command, 0, NO_PIPE, NO_PIPE, bitmap);
+
+  dispose_fd_bitmap (bitmap);
+  discard_unwind_frame ("execute-command");
+
+#if defined (PROCESS_SUBSTITUTION)
+  /* don't unlink fifos if we're in a shell function; wait until the function
+     returns. */
+  if (variable_context == 0)
+    unlink_fifo_list ();
+#endif /* PROCESS_SUBSTITUTION */
+
+  return (result);
+}
+
+/* Return 1 if TYPE is a shell control structure type. */
+static int
+shell_control_structure (type)
+     enum command_type type;
+{
+  switch (type)
+    {
+    case cm_for:
+#if defined (ARITH_FOR_COMMAND)
+    case cm_arith_for:
+#endif
+#if defined (SELECT_COMMAND)
+    case cm_select:
+#endif
+#if defined (DPAREN_ARITHMETIC)
+    case cm_arith:
+#endif
+#if defined (COND_COMMAND)
+    case cm_cond:
+#endif
+    case cm_case:
+    case cm_while:
+    case cm_until:
+    case cm_if:
+    case cm_group:
+      return (1);
+
+    default:
+      return (0);
+    }
+}
+
+/* A function to use to unwind_protect the redirection undo list
+   for loops. */
+static void
+cleanup_redirects (list)
+     REDIRECT *list;
+{
+  do_redirections (list, RX_ACTIVE);
+  dispose_redirects (list);
+}
+
+#if 0
+/* Function to unwind_protect the redirections for functions and builtins. */
+static void
+cleanup_func_redirects (list)
+     REDIRECT *list;
+{
+  do_redirections (list, RX_ACTIVE);
+}
+#endif
+
+void
+dispose_exec_redirects ()
+{
+  if (exec_redirection_undo_list)
+    {
+      dispose_redirects (exec_redirection_undo_list);
+      exec_redirection_undo_list = (REDIRECT *)NULL;
+    }
+}
+
+#if defined (JOB_CONTROL)
+/* A function to restore the signal mask to its proper value when the shell
+   is interrupted or errors occur while creating a pipeline. */
+static int
+restore_signal_mask (set)
+     sigset_t *set;
+{
+  return (sigprocmask (SIG_SETMASK, set, (sigset_t *)NULL));
+}
+#endif /* JOB_CONTROL */
+
+#ifdef DEBUG
+/* A debugging function that can be called from gdb, for instance. */
+void
+open_files ()
+{
+  register int i;
+  int f, fd_table_size;
+
+  fd_table_size = getdtablesize ();
+
+  fprintf (stderr, "pid %ld open files:", (long)getpid ());
+  for (i = 3; i < fd_table_size; i++)
+    {
+      if ((f = fcntl (i, F_GETFD, 0)) != -1)
+       fprintf (stderr, " %d (%s)", i, f ? "close" : "open");
+    }
+  fprintf (stderr, "\n");
+}
+#endif
+
+static void
+async_redirect_stdin ()
+{
+  int fd;
+
+  fd = open ("/dev/null", O_RDONLY);
+  if (fd > 0)
+    {
+      dup2 (fd, 0);
+      close (fd);
+    }
+  else if (fd < 0)
+    internal_error (_("cannot redirect standard input from /dev/null: %s"), strerror (errno));
+}
+
+#define DESCRIBE_PID(pid) do { if (interactive) describe_pid (pid); } while (0)
+
+/* Execute the command passed in COMMAND, perhaps doing it asynchrounously.
+   COMMAND is exactly what read_command () places into GLOBAL_COMMAND.
+   ASYNCHROUNOUS, if non-zero, says to do this command in the background.
+   PIPE_IN and PIPE_OUT are file descriptors saying where input comes
+   from and where it goes.  They can have the value of NO_PIPE, which means
+   I/O is stdin/stdout.
+   FDS_TO_CLOSE is a list of file descriptors to close once the child has
+   been forked.  This list often contains the unusable sides of pipes, etc.
+
+   EXECUTION_SUCCESS or EXECUTION_FAILURE are the only possible
+   return values.  Executing a command with nothing in it returns
+   EXECUTION_SUCCESS. */
+int
+execute_command_internal (command, asynchronous, pipe_in, pipe_out,
+                         fds_to_close)
+     COMMAND *command;
+     int asynchronous;
+     int pipe_in, pipe_out;
+     struct fd_bitmap *fds_to_close;
+{
+  int exec_result, invert, ignore_return, was_error_trap;
+  REDIRECT *my_undo_list, *exec_undo_list;
+  volatile pid_t last_pid;
+  volatile int save_line_number;
+
+  if (command == 0 || breaking || continuing || read_but_dont_execute)
+    return (EXECUTION_SUCCESS);
+
+  run_pending_traps ();
+
+#if 0
+  if (running_trap == 0)
+#endif
+    currently_executing_command = command;
+
+  invert = (command->flags & CMD_INVERT_RETURN) != 0;
+
+  /* If we're inverting the return value and `set -e' has been executed,
+     we don't want a failing command to inadvertently cause the shell
+     to exit. */
+  if (exit_immediately_on_error && invert)     /* XXX */
+    command->flags |= CMD_IGNORE_RETURN;       /* XXX */
+
+  exec_result = EXECUTION_SUCCESS;
+
+  /* If a command was being explicitly run in a subshell, or if it is
+     a shell control-structure, and it has a pipe, then we do the command
+     in a subshell. */
+  if (command->type == cm_subshell && (command->flags & CMD_NO_FORK))
+    return (execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close));
+
+  if (command->type == cm_subshell ||
+      (command->flags & (CMD_WANT_SUBSHELL|CMD_FORCE_SUBSHELL)) ||
+      (shell_control_structure (command->type) &&
+       (pipe_out != NO_PIPE || pipe_in != NO_PIPE || asynchronous)))
+    {
+      pid_t paren_pid;
+
+if (asynchronous)
+itrace("execute_command_internal: making child: asynchronous = 1 job_control = %d", job_control);
+      /* Fork a subshell, turn off the subshell bit, turn off job
+        control and call execute_command () on the command again. */
+      paren_pid = make_child (savestring (make_command_string (command)),
+                             asynchronous);
+      if (paren_pid == 0)
+       exit (execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close));
+       /* NOTREACHED */
+      else
+       {
+         close_pipes (pipe_in, pipe_out);
+
+#if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD)
+         unlink_fifo_list ();
+#endif
+         /* If we are part of a pipeline, and not the end of the pipeline,
+            then we should simply return and let the last command in the
+            pipe be waited for.  If we are not in a pipeline, or are the
+            last command in the pipeline, then we wait for the subshell
+            and return its exit status as usual. */
+         if (pipe_out != NO_PIPE)
+           return (EXECUTION_SUCCESS);
+
+         stop_pipeline (asynchronous, (COMMAND *)NULL);
+
+         if (asynchronous == 0)
+           {
+             last_command_exit_value = wait_for (paren_pid);
+
+             /* If we have to, invert the return value. */
+             if (invert)
+               exec_result = ((last_command_exit_value == EXECUTION_SUCCESS)
+                               ? EXECUTION_FAILURE
+                               : EXECUTION_SUCCESS);
+             else
+               exec_result = last_command_exit_value;
+
+             return (last_command_exit_value = exec_result);
+           }
+         else
+           {
+             DESCRIBE_PID (paren_pid);
+
+             run_pending_traps ();
+
+             return (EXECUTION_SUCCESS);
+           }
+       }
+    }
+
+#if defined (COMMAND_TIMING)
+  if (command->flags & CMD_TIME_PIPELINE)
+    {
+      if (asynchronous)
+       {
+         command->flags |= CMD_FORCE_SUBSHELL;
+         exec_result = execute_command_internal (command, 1, pipe_in, pipe_out, fds_to_close);
+       }
+      else
+       {
+         exec_result = time_command (command, asynchronous, pipe_in, pipe_out, fds_to_close);
+#if 0
+         if (running_trap == 0)
+#endif
+           currently_executing_command = (COMMAND *)NULL;
+       }
+      return (exec_result);
+    }
+#endif /* COMMAND_TIMING */
+
+  if (shell_control_structure (command->type) && command->redirects)
+    stdin_redir = stdin_redirects (command->redirects);
+
+  /* Handle WHILE FOR CASE etc. with redirections.  (Also '&' input
+     redirection.)  */
+  if (do_redirections (command->redirects, RX_ACTIVE|RX_UNDOABLE) != 0)
+    {
+      cleanup_redirects (redirection_undo_list);
+      redirection_undo_list = (REDIRECT *)NULL;
+      dispose_exec_redirects ();
+      return (EXECUTION_FAILURE);
+    }
+
+  if (redirection_undo_list)
+    {
+      my_undo_list = (REDIRECT *)copy_redirects (redirection_undo_list);
+      dispose_redirects (redirection_undo_list);
+      redirection_undo_list = (REDIRECT *)NULL;
+    }
+  else
+    my_undo_list = (REDIRECT *)NULL;
+
+  if (exec_redirection_undo_list)
+    {
+      exec_undo_list = (REDIRECT *)copy_redirects (exec_redirection_undo_list);
+      dispose_redirects (exec_redirection_undo_list);
+      exec_redirection_undo_list = (REDIRECT *)NULL;
+    }
+  else
+    exec_undo_list = (REDIRECT *)NULL;
+
+  if (my_undo_list || exec_undo_list)
+    begin_unwind_frame ("loop_redirections");
+
+  if (my_undo_list)
+    add_unwind_protect ((Function *)cleanup_redirects, my_undo_list);
+
+  if (exec_undo_list)
+    add_unwind_protect ((Function *)dispose_redirects, exec_undo_list);
+
+  ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0;
+
+  QUIT;
+
+  switch (command->type)
+    {
+    case cm_simple:
+      {
+       save_line_number = line_number;
+       /* We can't rely on variables retaining their values across a
+          call to execute_simple_command if a longjmp occurs as the
+          result of a `return' builtin.  This is true for sure with gcc. */
+       last_pid = last_made_pid;
+       was_error_trap = signal_is_trapped (ERROR_TRAP) && signal_is_ignored (ERROR_TRAP) == 0;
+
+       if (ignore_return && command->value.Simple)
+         command->value.Simple->flags |= CMD_IGNORE_RETURN;
+       if (command->flags & CMD_STDIN_REDIR)
+         command->value.Simple->flags |= CMD_STDIN_REDIR;
+
+       line_number = command->value.Simple->line;
+       exec_result =
+         execute_simple_command (command->value.Simple, pipe_in, pipe_out,
+                                 asynchronous, fds_to_close);
+       line_number = save_line_number;
+
+       /* The temporary environment should be used for only the simple
+          command immediately following its definition. */
+       dispose_used_env_vars ();
+
+#if (defined (ultrix) && defined (mips)) || defined (C_ALLOCA)
+       /* Reclaim memory allocated with alloca () on machines which
+          may be using the alloca emulation code. */
+       (void) alloca (0);
+#endif /* (ultrix && mips) || C_ALLOCA */
+
+       /* If we forked to do the command, then we must wait_for ()
+          the child. */
+
+       /* XXX - this is something to watch out for if there are problems
+          when the shell is compiled without job control. */
+       if (already_making_children && pipe_out == NO_PIPE &&
+           last_pid != last_made_pid)
+         {
+           stop_pipeline (asynchronous, (COMMAND *)NULL);
+
+           if (asynchronous)
+             {
+               DESCRIBE_PID (last_made_pid);
+             }
+           else
+#if !defined (JOB_CONTROL)
+             /* Do not wait for asynchronous processes started from
+                startup files. */
+           if (last_made_pid != last_asynchronous_pid)
+#endif
+           /* When executing a shell function that executes other
+              commands, this causes the last simple command in
+              the function to be waited for twice.  This also causes
+              subshells forked to execute builtin commands (e.g., in
+              pipelines) to be waited for twice. */
+             exec_result = wait_for (last_made_pid);
+#if defined (RECYCLES_PIDS)
+             /* LynxOS, for one, recycles pids very quickly -- so quickly
+                that a new process may have the same pid as the last one
+                created.  This has been reported to fix the problem on that
+                OS, and a similar problem on Cygwin. */
+             if (exec_result == 0)
+               last_made_pid = NO_PID;
+#endif
+         }
+      }
+
+      if (was_error_trap && ignore_return == 0 && invert == 0 && exec_result != EXECUTION_SUCCESS)
+       {
+         last_command_exit_value = exec_result;
+         run_error_trap ();
+       }
+
+      if (ignore_return == 0 && invert == 0 &&
+         ((posixly_correct && interactive == 0 && special_builtin_failed) ||
+          (exit_immediately_on_error && (exec_result != EXECUTION_SUCCESS))))
+       {
+         last_command_exit_value = exec_result;
+         run_pending_traps ();
+         jump_to_top_level (ERREXIT);
+       }
+
+      break;
+
+    case cm_for:
+      if (ignore_return)
+       command->value.For->flags |= CMD_IGNORE_RETURN;
+      exec_result = execute_for_command (command->value.For);
+      break;
+
+#if defined (ARITH_FOR_COMMAND)
+    case cm_arith_for:
+      if (ignore_return)
+       command->value.ArithFor->flags |= CMD_IGNORE_RETURN;
+      exec_result = execute_arith_for_command (command->value.ArithFor);
+      break;
+#endif
+
+#if defined (SELECT_COMMAND)
+    case cm_select:
+      if (ignore_return)
+       command->value.Select->flags |= CMD_IGNORE_RETURN;
+      exec_result = execute_select_command (command->value.Select);
+      break;
+#endif
+
+    case cm_case:
+      if (ignore_return)
+       command->value.Case->flags |= CMD_IGNORE_RETURN;
+      exec_result = execute_case_command (command->value.Case);
+      break;
+
+    case cm_while:
+      if (ignore_return)
+       command->value.While->flags |= CMD_IGNORE_RETURN;
+      exec_result = execute_while_command (command->value.While);
+      break;
+
+    case cm_until:
+      if (ignore_return)
+       command->value.While->flags |= CMD_IGNORE_RETURN;
+      exec_result = execute_until_command (command->value.While);
+      break;
+
+    case cm_if:
+      if (ignore_return)
+       command->value.If->flags |= CMD_IGNORE_RETURN;
+      exec_result = execute_if_command (command->value.If);
+      break;
+
+    case cm_group:
+
+      /* This code can be executed from either of two paths: an explicit
+        '{}' command, or via a function call.  If we are executed via a
+        function call, we have already taken care of the function being
+        executed in the background (down there in execute_simple_command ()),
+        and this command should *not* be marked as asynchronous.  If we
+        are executing a regular '{}' group command, and asynchronous == 1,
+        we must want to execute the whole command in the background, so we
+        need a subshell, and we want the stuff executed in that subshell
+        (this group command) to be executed in the foreground of that
+        subshell (i.e. there will not be *another* subshell forked).
+
+        What we do is to force a subshell if asynchronous, and then call
+        execute_command_internal again with asynchronous still set to 1,
+        but with the original group command, so the printed command will
+        look right.
+
+        The code above that handles forking off subshells will note that
+        both subshell and async are on, and turn off async in the child
+        after forking the subshell (but leave async set in the parent, so
+        the normal call to describe_pid is made).  This turning off
+        async is *crucial*; if it is not done, this will fall into an
+        infinite loop of executions through this spot in subshell after
+        subshell until the process limit is exhausted. */
+
+      if (asynchronous)
+       {
+         command->flags |= CMD_FORCE_SUBSHELL;
+         exec_result =
+           execute_command_internal (command, 1, pipe_in, pipe_out,
+                                     fds_to_close);
+       }
+      else
+       {
+         if (ignore_return && command->value.Group->command)
+           command->value.Group->command->flags |= CMD_IGNORE_RETURN;
+         exec_result =
+           execute_command_internal (command->value.Group->command,
+                                     asynchronous, pipe_in, pipe_out,
+                                     fds_to_close);
+       }
+      break;
+
+    case cm_connection:
+      exec_result = execute_connection (command, asynchronous,
+                                       pipe_in, pipe_out, fds_to_close);
+      break;
+
+#if defined (DPAREN_ARITHMETIC)
+    case cm_arith:
+      if (ignore_return)
+       command->value.Arith->flags |= CMD_IGNORE_RETURN;
+      exec_result = execute_arith_command (command->value.Arith);
+      break;
+#endif
+
+#if defined (COND_COMMAND)
+    case cm_cond:
+      if (ignore_return)
+       command->value.Cond->flags |= CMD_IGNORE_RETURN;
+      save_line_number = line_number;
+      exec_result = execute_cond_command (command->value.Cond);
+      line_number = save_line_number;
+      break;
+#endif
+    
+    case cm_function_def:
+      exec_result = execute_intern_function (command->value.Function_def->name,
+                                            command->value.Function_def->command);
+      break;
+
+    default:
+      command_error ("execute_command", CMDERR_BADTYPE, command->type, 0);
+    }
+
+  if (my_undo_list)
+    {
+      do_redirections (my_undo_list, RX_ACTIVE);
+      dispose_redirects (my_undo_list);
+    }
+
+  if (exec_undo_list)
+    dispose_redirects (exec_undo_list);
+
+  if (my_undo_list || exec_undo_list)
+    discard_unwind_frame ("loop_redirections");
+
+  /* Invert the return value if we have to */
+  if (invert)
+    exec_result = (exec_result == EXECUTION_SUCCESS)
+                   ? EXECUTION_FAILURE
+                   : EXECUTION_SUCCESS;
+
+  last_command_exit_value = exec_result;
+  run_pending_traps ();
+#if 0
+  if (running_trap == 0)
+#endif
+    currently_executing_command = (COMMAND *)NULL;
+  return (last_command_exit_value);
+}
+
+#if defined (COMMAND_TIMING)
+
+#if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY)
+extern struct timeval *difftimeval __P((struct timeval *, struct timeval *, struct timeval *));
+extern struct timeval *addtimeval __P((struct timeval *, struct timeval *, struct timeval *));
+extern int timeval_to_cpu __P((struct timeval *, struct timeval *, struct timeval *));
+#endif
+
+#define POSIX_TIMEFORMAT "real %2R\nuser %2U\nsys %2S"
+#define BASH_TIMEFORMAT  "\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS"
+
+static int precs[] = { 0, 100, 10, 1 };
+
+/* Expand one `%'-prefixed escape sequence from a time format string. */
+static int
+mkfmt (buf, prec, lng, sec, sec_fraction)
+     char *buf;
+     int prec, lng;
+     time_t sec;
+     int sec_fraction;
+{
+  time_t min;
+  char abuf[INT_STRLEN_BOUND(time_t) + 1];
+  int ind, aind;
+
+  ind = 0;
+  abuf[sizeof(abuf) - 1] = '\0';
+
+  /* If LNG is non-zero, we want to decompose SEC into minutes and seconds. */
+  if (lng)
+    {
+      min = sec / 60;
+      sec %= 60;
+      aind = sizeof(abuf) - 2;
+      do
+       abuf[aind--] = (min % 10) + '0';
+      while (min /= 10);
+      aind++;
+      while (abuf[aind])
+       buf[ind++] = abuf[aind++];
+      buf[ind++] = 'm';
+    }
+
+  /* Now add the seconds. */
+  aind = sizeof (abuf) - 2;
+  do
+    abuf[aind--] = (sec % 10) + '0';
+  while (sec /= 10);
+  aind++;
+  while (abuf[aind])
+    buf[ind++] = abuf[aind++];
+
+  /* We want to add a decimal point and PREC places after it if PREC is
+     nonzero.  PREC is not greater than 3.  SEC_FRACTION is between 0
+     and 999. */
+  if (prec != 0)
+    {
+      buf[ind++] = '.';
+      for (aind = 1; aind <= prec; aind++)
+       {
+         buf[ind++] = (sec_fraction / precs[aind]) + '0';
+         sec_fraction %= precs[aind];
+       }
+    }
+
+  if (lng)
+    buf[ind++] = 's';
+  buf[ind] = '\0';
+
+  return (ind);
+}
+
+/* Interpret the format string FORMAT, interpolating the following escape
+   sequences:
+               %[prec][l][RUS]
+
+   where the optional `prec' is a precision, meaning the number of
+   characters after the decimal point, the optional `l' means to format
+   using minutes and seconds (MMmNN[.FF]s), like the `times' builtin',
+   and the last character is one of
+   
+               R       number of seconds of `real' time
+               U       number of seconds of `user' time
+               S       number of seconds of `system' time
+
+   An occurrence of `%%' in the format string is translated to a `%'.  The
+   result is printed to FP, a pointer to a FILE.  The other variables are
+   the seconds and thousandths of a second of real, user, and system time,
+   resectively. */
+static void
+print_formatted_time (fp, format, rs, rsf, us, usf, ss, ssf, cpu)
+     FILE *fp;
+     char *format;
+     time_t rs;
+     int rsf;
+     time_t us;
+     int usf;
+     time_t ss;
+     int ssf, cpu;
+{
+  int prec, lng, len;
+  char *str, *s, ts[INT_STRLEN_BOUND (time_t) + sizeof ("mSS.FFFF")];
+  time_t sum;
+  int sum_frac;
+  int sindex, ssize;
+
+  len = strlen (format);
+  ssize = (len + 64) - (len % 64);
+  str = (char *)xmalloc (ssize);
+  sindex = 0;
+
+  for (s = format; *s; s++)
+    {
+      if (*s != '%' || s[1] == '\0')
+       {
+         RESIZE_MALLOCED_BUFFER (str, sindex, 1, ssize, 64);
+         str[sindex++] = *s;
+       }
+      else if (s[1] == '%')
+       {
+         s++;
+         RESIZE_MALLOCED_BUFFER (str, sindex, 1, ssize, 64);
+         str[sindex++] = *s;
+       }
+      else if (s[1] == 'P')
+       {
+         s++;
+         if (cpu > 10000)
+           cpu = 10000;
+         sum = cpu / 100;
+         sum_frac = (cpu % 100) * 10;
+         len = mkfmt (ts, 2, 0, sum, sum_frac);
+         RESIZE_MALLOCED_BUFFER (str, sindex, len, ssize, 64);
+         strcpy (str + sindex, ts);
+         sindex += len;
+       }
+      else
+       {
+         prec = 3;     /* default is three places past the decimal point. */
+         lng = 0;      /* default is to not use minutes or append `s' */
+         s++;
+         if (DIGIT (*s))               /* `precision' */
+           {
+             prec = *s++ - '0';
+             if (prec > 3) prec = 3;
+           }
+         if (*s == 'l')                /* `length extender' */
+           {
+             lng = 1;
+             s++;
+           }
+         if (*s == 'R' || *s == 'E')
+           len = mkfmt (ts, prec, lng, rs, rsf);
+         else if (*s == 'U')
+           len = mkfmt (ts, prec, lng, us, usf);
+         else if (*s == 'S')
+           len = mkfmt (ts, prec, lng, ss, ssf);
+         else
+           {
+             internal_error (_("TIMEFORMAT: `%c': invalid format character"), *s);
+             free (str);
+             return;
+           }
+         RESIZE_MALLOCED_BUFFER (str, sindex, len, ssize, 64);
+         strcpy (str + sindex, ts);
+         sindex += len;
+       }
+    }
+
+  str[sindex] = '\0';
+  fprintf (fp, "%s\n", str);
+  fflush (fp);
+
+  free (str);
+}
+
+static int
+time_command (command, asynchronous, pipe_in, pipe_out, fds_to_close)
+     COMMAND *command;
+     int asynchronous, pipe_in, pipe_out;
+     struct fd_bitmap *fds_to_close;
+{
+  int rv, posix_time, old_flags;
+  time_t rs, us, ss;
+  int rsf, usf, ssf;
+  int cpu;
+  char *time_format;
+
+#if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY)
+  struct timeval real, user, sys;
+  struct timeval before, after;
+#  if defined (HAVE_STRUCT_TIMEZONE)
+  struct timezone dtz;                         /* posix doesn't define this */
+#  endif
+  struct rusage selfb, selfa, kidsb, kidsa;    /* a = after, b = before */
+#else
+#  if defined (HAVE_TIMES)
+  clock_t tbefore, tafter, real, user, sys;
+  struct tms before, after;
+#  endif
+#endif
+
+#if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY)
+#  if defined (HAVE_STRUCT_TIMEZONE)
+  gettimeofday (&before, &dtz);
+#  else
+  gettimeofday (&before, (void *)NULL);
+#  endif /* !HAVE_STRUCT_TIMEZONE */
+  getrusage (RUSAGE_SELF, &selfb);
+  getrusage (RUSAGE_CHILDREN, &kidsb);
+#else
+#  if defined (HAVE_TIMES)
+  tbefore = times (&before);
+#  endif
+#endif
+
+  posix_time = (command->flags & CMD_TIME_POSIX);
+
+  old_flags = command->flags;
+  command->flags &= ~(CMD_TIME_PIPELINE|CMD_TIME_POSIX);
+  rv = execute_command_internal (command, asynchronous, pipe_in, pipe_out, fds_to_close);
+  command->flags = old_flags;
+
+  rs = us = ss = 0;
+  rsf = usf = ssf = cpu = 0;
+
+#if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY)
+#  if defined (HAVE_STRUCT_TIMEZONE)
+  gettimeofday (&after, &dtz);
+#  else
+  gettimeofday (&after, (void *)NULL);
+#  endif /* !HAVE_STRUCT_TIMEZONE */
+  getrusage (RUSAGE_SELF, &selfa);
+  getrusage (RUSAGE_CHILDREN, &kidsa);
+
+  difftimeval (&real, &before, &after);
+  timeval_to_secs (&real, &rs, &rsf);
+
+  addtimeval (&user, difftimeval(&after, &selfb.ru_utime, &selfa.ru_utime),
+                    difftimeval(&before, &kidsb.ru_utime, &kidsa.ru_utime));
+  timeval_to_secs (&user, &us, &usf);
+
+  addtimeval (&sys, difftimeval(&after, &selfb.ru_stime, &selfa.ru_stime),
+                   difftimeval(&before, &kidsb.ru_stime, &kidsa.ru_stime));
+  timeval_to_secs (&sys, &ss, &ssf);
+
+  cpu = timeval_to_cpu (&real, &user, &sys);
+#else
+#  if defined (HAVE_TIMES)
+  tafter = times (&after);
+
+  real = tafter - tbefore;
+  clock_t_to_secs (real, &rs, &rsf);
+
+  user = (after.tms_utime - before.tms_utime) + (after.tms_cutime - before.tms_cutime);
+  clock_t_to_secs (user, &us, &usf);
+
+  sys = (after.tms_stime - before.tms_stime) + (after.tms_cstime - before.tms_cstime);
+  clock_t_to_secs (sys, &ss, &ssf);
+
+  cpu = (real == 0) ? 0 : ((user + sys) * 10000) / real;
+
+#  else
+  rs = us = ss = 0;
+  rsf = usf = ssf = cpu = 0;
+#  endif
+#endif
+
+  if (posix_time)
+    time_format = POSIX_TIMEFORMAT;
+  else if ((time_format = get_string_value ("TIMEFORMAT")) == 0)
+    time_format = BASH_TIMEFORMAT;
+
+  if (time_format && *time_format)
+    print_formatted_time (stderr, time_format, rs, rsf, us, usf, ss, ssf, cpu);
+
+  return rv;
+}
+#endif /* COMMAND_TIMING */
+
+/* Execute a command that's supposed to be in a subshell.  This must be
+   called after make_child and we must be running in the child process.
+   The caller will return or exit() immediately with the value this returns. */
+static int
+execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close)
+     COMMAND *command;
+     int asynchronous;
+     int pipe_in, pipe_out;
+     struct fd_bitmap *fds_to_close;
+{
+  int user_subshell, return_code, function_value, should_redir_stdin, invert;
+  int ois;
+  COMMAND *tcom;
+
+  USE_VAR(user_subshell);
+  USE_VAR(invert);
+  USE_VAR(tcom);
+  USE_VAR(asynchronous);
+
+  subshell_level++;
+  should_redir_stdin = (asynchronous && (command->flags & CMD_STDIN_REDIR) &&
+                         pipe_in == NO_PIPE &&
+                         stdin_redirects (command->redirects) == 0);
+
+  invert = (command->flags & CMD_INVERT_RETURN) != 0;
+  user_subshell = command->type == cm_subshell || ((command->flags & CMD_WANT_SUBSHELL) != 0);
+
+  command->flags &= ~(CMD_FORCE_SUBSHELL | CMD_WANT_SUBSHELL | CMD_INVERT_RETURN);
+
+itrace("execute_in_subshell: job_control = %d user_subshell = %d", job_control, user_subshell);
+
+  /* If a command is asynchronous in a subshell (like ( foo ) & or
+     the special case of an asynchronous GROUP command where the
+     the subshell bit is turned on down in case cm_group: below),
+     turn off `asynchronous', so that two subshells aren't spawned.
+
+     This seems semantically correct to me.  For example,
+     ( foo ) & seems to say ``do the command `foo' in a subshell
+     environment, but don't wait for that subshell to finish'',
+     and "{ foo ; bar ; } &" seems to me to be like functions or
+     builtins in the background, which executed in a subshell
+     environment.  I just don't see the need to fork two subshells. */
+
+  /* Don't fork again, we are already in a subshell.  A `doubly
+     async' shell is not interactive, however. */
+  if (asynchronous)
+    {
+#if defined (JOB_CONTROL)
+      /* If a construct like ( exec xxx yyy ) & is given while job
+        control is active, we want to prevent exec from putting the
+        subshell back into the original process group, carefully
+        undoing all the work we just did in make_child. */
+      original_pgrp = -1;
+#endif /* JOB_CONTROL */
+      ois = interactive_shell;
+      interactive_shell = 0;
+      /* This test is to prevent alias expansion by interactive shells that
+        run `(command) &' but to allow scripts that have enabled alias
+        expansion with `shopt -s expand_alias' to continue to expand
+        aliases. */
+      if (ois != interactive_shell)
+       expand_aliases = 0;
+      asynchronous = 0;
+    }
+
+  /* Subshells are neither login nor interactive. */
+  login_shell = interactive = 0;
+
+  subshell_environment = user_subshell ? SUBSHELL_PAREN : SUBSHELL_ASYNC;
+
+  reset_terminating_signals ();                /* in sig.c */
+  /* Cancel traps, in trap.c. */
+  restore_original_signals ();
+  if (asynchronous)
+    setup_async_signals ();
+
+#if defined (JOB_CONTROL)
+  set_sigchld_handler ();
+#endif /* JOB_CONTROL */
+
+  set_sigint_handler ();
+
+#if defined (JOB_CONTROL)
+  /* Delete all traces that there were any jobs running.  This is
+     only for subshells. */
+  without_job_control ();
+#endif /* JOB_CONTROL */
+
+  if (fds_to_close)
+    close_fd_bitmap (fds_to_close);
+
+  do_piping (pipe_in, pipe_out);
+
+  /* If this is a user subshell, set a flag if stdin was redirected.
+     This is used later to decide whether to redirect fd 0 to
+     /dev/null for async commands in the subshell.  This adds more
+     sh compatibility, but I'm not sure it's the right thing to do. */
+  if (user_subshell)
+    {
+      stdin_redir = stdin_redirects (command->redirects);
+      restore_default_signal (0);
+    }
+
+  /* If this is an asynchronous command (command &), we want to
+     redirect the standard input from /dev/null in the absence of
+     any specific redirection involving stdin. */
+  if (should_redir_stdin && stdin_redir == 0)
+    async_redirect_stdin ();
+
+  /* Do redirections, then dispose of them before recursive call. */
+  if (command->redirects)
+    {
+      if (do_redirections (command->redirects, RX_ACTIVE) != 0)
+       exit (invert ? EXECUTION_SUCCESS : EXECUTION_FAILURE);
+
+      dispose_redirects (command->redirects);
+      command->redirects = (REDIRECT *)NULL;
+    }
+
+  tcom = (command->type == cm_subshell) ? command->value.Subshell->command : command;
+
+  /* Make sure the subshell inherits any CMD_IGNORE_RETURN flag. */
+  if ((command->flags & CMD_IGNORE_RETURN) && tcom != command)
+    tcom->flags |= CMD_IGNORE_RETURN;
+
+  /* If this is a simple command, tell execute_disk_command that it
+     might be able to get away without forking and simply exec.
+     This means things like ( sleep 10 ) will only cause one fork.
+     If we're timing the command or inverting its return value, however,
+     we cannot do this optimization. */
+  if (user_subshell && (tcom->type == cm_simple || tcom->type == cm_subshell) &&
+      ((tcom->flags & CMD_TIME_PIPELINE) == 0) &&
+      ((tcom->flags & CMD_INVERT_RETURN) == 0))
+    {
+      tcom->flags |= CMD_NO_FORK;
+      if (tcom->type == cm_simple)
+       tcom->value.Simple->flags |= CMD_NO_FORK;
+    }
+
+  invert = (tcom->flags & CMD_INVERT_RETURN) != 0;
+  tcom->flags &= ~CMD_INVERT_RETURN;
+
+  /* If we're inside a function while executing this subshell, we
+     need to handle a possible `return'. */
+  function_value = 0;
+  if (return_catch_flag)
+    function_value = setjmp (return_catch);
+
+  if (function_value)
+    return_code = return_catch_value;
+  else
+    return_code = execute_command_internal
+      (tcom, asynchronous, NO_PIPE, NO_PIPE, fds_to_close);
+
+  /* If we are asked to, invert the return value. */
+  if (invert)
+    return_code = (return_code == EXECUTION_SUCCESS) ? EXECUTION_FAILURE
+                                                    : EXECUTION_SUCCESS;
+
+  /* If we were explicitly placed in a subshell with (), we need
+     to do the `shell cleanup' things, such as running traps[0]. */
+  if (user_subshell && signal_is_trapped (0))
+    {
+      last_command_exit_value = return_code;
+      return_code = run_exit_trap ();
+    }
+
+  subshell_level--;
+  return (return_code);
+  /* NOTREACHED */
+}
+
+static int
+execute_pipeline (command, asynchronous, pipe_in, pipe_out, fds_to_close)
+     COMMAND *command;
+     int asynchronous, pipe_in, pipe_out;
+     struct fd_bitmap *fds_to_close;
+{
+  int prev, fildes[2], new_bitmap_size, dummyfd, ignore_return, exec_result;
+  COMMAND *cmd;
+  struct fd_bitmap *fd_bitmap;
+
+#if defined (JOB_CONTROL)
+  sigset_t set, oset;
+  BLOCK_CHILD (set, oset);
+#endif /* JOB_CONTROL */
+
+  ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0;
+
+  prev = pipe_in;
+  cmd = command;
+
+  while (cmd && cmd->type == cm_connection &&
+        cmd->value.Connection && cmd->value.Connection->connector == '|')
+    {
+      /* Make a pipeline between the two commands. */
+      if (pipe (fildes) < 0)
+       {
+         sys_error ("pipe error");
+#if defined (JOB_CONTROL)
+         terminate_current_pipeline ();
+         kill_current_pipeline ();
+#endif /* JOB_CONTROL */
+         last_command_exit_value = EXECUTION_FAILURE;
+         /* The unwind-protects installed below will take care
+            of closing all of the open file descriptors. */
+         throw_to_top_level ();
+         return (EXECUTION_FAILURE);   /* XXX */
+       }
+
+      /* Here is a problem: with the new file close-on-exec
+        code, the read end of the pipe (fildes[0]) stays open
+        in the first process, so that process will never get a
+        SIGPIPE.  There is no way to signal the first process
+        that it should close fildes[0] after forking, so it
+        remains open.  No SIGPIPE is ever sent because there
+        is still a file descriptor open for reading connected
+        to the pipe.  We take care of that here.  This passes
+        around a bitmap of file descriptors that must be
+        closed after making a child process in execute_simple_command. */
+
+      /* We need fd_bitmap to be at least as big as fildes[0].
+        If fildes[0] is less than fds_to_close->size, then
+        use fds_to_close->size. */
+      new_bitmap_size = (fildes[0] < fds_to_close->size)
+                               ? fds_to_close->size
+                               : fildes[0] + 8;
+
+      fd_bitmap = new_fd_bitmap (new_bitmap_size);
+
+      /* Now copy the old information into the new bitmap. */
+      xbcopy ((char *)fds_to_close->bitmap, (char *)fd_bitmap->bitmap, fds_to_close->size);
+
+      /* And mark the pipe file descriptors to be closed. */
+      fd_bitmap->bitmap[fildes[0]] = 1;
+
+      /* In case there are pipe or out-of-processes errors, we
+        want all these file descriptors to be closed when
+        unwind-protects are run, and the storage used for the
+        bitmaps freed up. */
+      begin_unwind_frame ("pipe-file-descriptors");
+      add_unwind_protect (dispose_fd_bitmap, fd_bitmap);
+      add_unwind_protect (close_fd_bitmap, fd_bitmap);
+      if (prev >= 0)
+       add_unwind_protect (close, prev);
+      dummyfd = fildes[1];
+      add_unwind_protect (close, dummyfd);
+
+#if defined (JOB_CONTROL)
+      add_unwind_protect (restore_signal_mask, &oset);
+#endif /* JOB_CONTROL */
+
+      if (ignore_return && cmd->value.Connection->first)
+       cmd->value.Connection->first->flags |= CMD_IGNORE_RETURN;
+      execute_command_internal (cmd->value.Connection->first, asynchronous,
+                               prev, fildes[1], fd_bitmap);
+
+      if (prev >= 0)
+       close (prev);
+
+      prev = fildes[0];
+      close (fildes[1]);
+
+      dispose_fd_bitmap (fd_bitmap);
+      discard_unwind_frame ("pipe-file-descriptors");
+
+      cmd = cmd->value.Connection->second;
+    }
+
+  /* Now execute the rightmost command in the pipeline.  */
+  if (ignore_return && cmd)
+    cmd->flags |= CMD_IGNORE_RETURN;
+  exec_result = execute_command_internal (cmd, asynchronous, prev, pipe_out, fds_to_close);
+
+  if (prev >= 0)
+    close (prev);
+
+#if defined (JOB_CONTROL)
+  UNBLOCK_CHILD (oset);
+#endif
+
+  return (exec_result);
+}
+
+static int
+execute_connection (command, asynchronous, pipe_in, pipe_out, fds_to_close)
+     COMMAND *command;
+     int asynchronous, pipe_in, pipe_out;
+     struct fd_bitmap *fds_to_close;
+{
+  REDIRECT *rp;
+  COMMAND *tc, *second;
+  int ignore_return, exec_result;
+
+  ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0;
+
+  switch (command->value.Connection->connector)
+    {
+    /* Do the first command asynchronously. */
+    case '&':
+      tc = command->value.Connection->first;
+      if (tc == 0)
+       return (EXECUTION_SUCCESS);
+
+      rp = tc->redirects;
+
+      if (ignore_return)
+       tc->flags |= CMD_IGNORE_RETURN;
+      tc->flags |= CMD_AMPERSAND;
+
+      /* If this shell was compiled without job control support,
+        if we are currently in a subshell via `( xxx )', or if job
+        control is not active then the standard input for an
+        asynchronous command is forced to /dev/null. */
+#if defined (JOB_CONTROL)
+      if ((subshell_environment || !job_control) && !stdin_redir)
+#else
+      if (!stdin_redir)
+#endif /* JOB_CONTROL */
+       tc->flags |= CMD_STDIN_REDIR;
+
+      exec_result = execute_command_internal (tc, 1, pipe_in, pipe_out, fds_to_close);
+
+      if (tc->flags & CMD_STDIN_REDIR)
+       tc->flags &= ~CMD_STDIN_REDIR;
+
+      second = command->value.Connection->second;
+      if (second)
+       {
+         if (ignore_return)
+           second->flags |= CMD_IGNORE_RETURN;
+
+         exec_result = execute_command_internal (second, asynchronous, pipe_in, pipe_out, fds_to_close);
+       }
+
+      break;
+
+    /* Just call execute command on both sides. */
+    case ';':
+      if (ignore_return)
+       {
+         if (command->value.Connection->first)
+           command->value.Connection->first->flags |= CMD_IGNORE_RETURN;
+         if (command->value.Connection->second)
+           command->value.Connection->second->flags |= CMD_IGNORE_RETURN;
+       }
+      QUIT;
+      execute_command (command->value.Connection->first);
+      QUIT;
+      exec_result = execute_command_internal (command->value.Connection->second,
+                                     asynchronous, pipe_in, pipe_out,
+                                     fds_to_close);
+      break;
+
+    case '|':
+      exec_result = execute_pipeline (command, asynchronous, pipe_in, pipe_out, fds_to_close);
+      break;
+
+    case AND_AND:
+    case OR_OR:
+      if (asynchronous)
+       {
+         /* If we have something like `a && b &' or `a || b &', run the
+            && or || stuff in a subshell.  Force a subshell and just call
+            execute_command_internal again.  Leave asynchronous on
+            so that we get a report from the parent shell about the
+            background job. */
+         command->flags |= CMD_FORCE_SUBSHELL;
+         exec_result = execute_command_internal (command, 1, pipe_in, pipe_out, fds_to_close);
+         break;
+       }
+
+      /* Execute the first command.  If the result of that is successful
+        and the connector is AND_AND, or the result is not successful
+        and the connector is OR_OR, then execute the second command,
+        otherwise return. */
+
+      if (command->value.Connection->first)
+       command->value.Connection->first->flags |= CMD_IGNORE_RETURN;
+
+      exec_result = execute_command (command->value.Connection->first);
+      QUIT;
+      if (((command->value.Connection->connector == AND_AND) &&
+          (exec_result == EXECUTION_SUCCESS)) ||
+         ((command->value.Connection->connector == OR_OR) &&
+          (exec_result != EXECUTION_SUCCESS)))
+       {
+         if (ignore_return && command->value.Connection->second)
+           command->value.Connection->second->flags |= CMD_IGNORE_RETURN;
+
+         exec_result = execute_command (command->value.Connection->second);
+       }
+      break;
+
+    default:
+      command_error ("execute_connection", CMDERR_BADCONN, command->value.Connection->connector, 0);
+      jump_to_top_level (DISCARD);
+      exec_result = EXECUTION_FAILURE;
+    }
+
+  return exec_result;
+}
+
+#define REAP() \
+  do \
+    { \
+      if (!interactive_shell) \
+       reap_dead_jobs (); \
+    } \
+  while (0)
+
+/* Execute a FOR command.  The syntax is: FOR word_desc IN word_list;
+   DO command; DONE */
+static int
+execute_for_command (for_command)
+     FOR_COM *for_command;
+{
+  register WORD_LIST *releaser, *list;
+  SHELL_VAR *v;
+  char *identifier;
+  int retval, save_line_number;
+#if 0
+  SHELL_VAR *old_value = (SHELL_VAR *)NULL; /* Remember the old value of x. */
+#endif
+
+  save_line_number = line_number;
+  if (check_identifier (for_command->name, 1) == 0)
+    {
+      if (posixly_correct && interactive_shell == 0)
+       {
+         last_command_exit_value = EX_USAGE;
+         jump_to_top_level (ERREXIT);
+       }
+      return (EXECUTION_FAILURE);
+    }
+
+  loop_level++;
+  identifier = for_command->name->word;
+
+  list = releaser = expand_words_no_vars (for_command->map_list);
+
+  begin_unwind_frame ("for");
+  add_unwind_protect (dispose_words, releaser);
+
+#if 0
+  if (lexical_scoping)
+    {
+      old_value = copy_variable (find_variable (identifier));
+      if (old_value)
+       add_unwind_protect (dispose_variable, old_value);
+    }
+#endif
+
+  if (for_command->flags & CMD_IGNORE_RETURN)
+    for_command->action->flags |= CMD_IGNORE_RETURN;
+
+  for (retval = EXECUTION_SUCCESS; list; list = list->next)
+    {
+      QUIT;
+
+      line_number = for_command->line;
+
+      /* Remember what this command looks like, for debugger. */
+      command_string_index = 0;
+      print_for_command_head (for_command);
+
+      if (echo_command_at_execute)
+       xtrace_print_for_command_head (for_command);
+
+      /* Save this command unless it's a trap command. */
+      if (this_command_name == 0 || (STREQ (this_command_name, "trap") == 0))
+       {
+         FREE (the_printed_command_except_trap);
+         the_printed_command_except_trap = savestring (the_printed_command);
+       }
+
+      retval = run_debug_trap ();
+#if defined (DEBUGGER)
+      /* In debugging mode, if the DEBUG trap returns a non-zero status, we
+        skip the command. */
+      if (debugging_mode && retval != EXECUTION_SUCCESS)
+        continue;
+#endif
+
+      this_command_name = (char *)NULL;
+      v = bind_variable (identifier, list->word->word);
+      if (readonly_p (v) || noassign_p (v))
+       {
+         line_number = save_line_number;
+         if (readonly_p (v) && interactive_shell == 0 && posixly_correct)
+           {
+             last_command_exit_value = EXECUTION_FAILURE;
+             jump_to_top_level (FORCE_EOF);
+           }
+         else
+           {
+             dispose_words (releaser);
+             discard_unwind_frame ("for");
+             loop_level--;
+             return (EXECUTION_FAILURE);
+           }
+       }
+      retval = execute_command (for_command->action);
+      REAP ();
+      QUIT;
+
+      if (breaking)
+       {
+         breaking--;
+         break;
+       }
+
+      if (continuing)
+       {
+         continuing--;
+         if (continuing)
+           break;
+       }
+    }
+
+  loop_level--;
+  line_number = save_line_number;
+
+#if 0
+  if (lexical_scoping)
+    {
+      if (!old_value)
+        unbind_variable (identifier);
+      else
+       {
+         SHELL_VAR *new_value;
+
+         new_value = bind_variable (identifier, value_cell(old_value));
+         new_value->attributes = old_value->attributes;
+         dispose_variable (old_value);
+       }
+    }
+#endif
+
+  dispose_words (releaser);
+  discard_unwind_frame ("for");
+  return (retval);
+}
+
+#if defined (ARITH_FOR_COMMAND)
+/* Execute an arithmetic for command.  The syntax is
+
+       for (( init ; step ; test ))
+       do
+               body
+       done
+
+   The execution should be exactly equivalent to
+
+       eval \(\( init \)\)
+       while eval \(\( test \)\) ; do
+               body;
+               eval \(\( step \)\)
+       done
+*/
+static intmax_t
+eval_arith_for_expr (l, okp)
+     WORD_LIST *l;
+     int *okp;
+{
+  WORD_LIST *new;
+  intmax_t expresult;
+  int r;
+
+  new = expand_words_no_vars (l);
+  if (new)
+    {
+      if (echo_command_at_execute)
+       xtrace_print_arith_cmd (new);
+      this_command_name = "((";                /* )) for expression error messages */
+
+      command_string_index = 0;
+      print_arith_command (new);
+      FREE (the_printed_command_except_trap);
+      the_printed_command_except_trap = savestring (the_printed_command);
+
+      r = run_debug_trap ();
+      /* In debugging mode, if the DEBUG trap returns a non-zero status, we
+        skip the command. */
+#if defined (DEBUGGER)
+      if (debugging_mode == 0 || r == EXECUTION_SUCCESS)
+       expresult = evalexp (new->word->word, okp);
+      else
+       {
+         expresult = 0;
+         if (okp)
+           *okp = 1;
+       }
+#else
+      expresult = evalexp (new->word->word, okp);
+#endif
+      dispose_words (new);
+    }
+  else
+    {
+      expresult = 0;
+      if (okp)
+       *okp = 1;
+    }
+  return (expresult);
+}
+
+static int
+execute_arith_for_command (arith_for_command)
+     ARITH_FOR_COM *arith_for_command;
+{
+  intmax_t expresult;
+  int expok, body_status, arith_lineno, save_lineno;
+
+  body_status = EXECUTION_SUCCESS;
+  loop_level++;
+  save_lineno = line_number;
+
+  if (arith_for_command->flags & CMD_IGNORE_RETURN)
+    arith_for_command->action->flags |= CMD_IGNORE_RETURN;
+
+  this_command_name = "((";    /* )) for expression error messages */
+
+  /* save the starting line number of the command so we can reset
+     line_number before executing each expression -- for $LINENO
+     and the DEBUG trap. */
+  line_number = arith_lineno = arith_for_command->line;
+  if (variable_context && interactive_shell)
+    line_number -= function_line_number;
+
+  /* Evaluate the initialization expression. */
+  expresult = eval_arith_for_expr (arith_for_command->init, &expok);
+  if (expok == 0)
+    {
+      line_number = save_lineno;
+      return (EXECUTION_FAILURE);
+    }
+
+  while (1)
+    {
+      /* Evaluate the test expression. */
+      line_number = arith_lineno;
+      expresult = eval_arith_for_expr (arith_for_command->test, &expok);
+      line_number = save_lineno;
+
+      if (expok == 0)
+       {
+         body_status = EXECUTION_FAILURE;
+         break;
+       }
+      REAP ();
+      if (expresult == 0)
+       break;
+
+      /* Execute the body of the arithmetic for command. */
+      QUIT;
+      body_status = execute_command (arith_for_command->action);
+      QUIT;
+
+      /* Handle any `break' or `continue' commands executed by the body. */
+      if (breaking)
+       {
+         breaking--;
+         break;
+       }
+
+      if (continuing)
+       {
+         continuing--;
+         if (continuing)
+           break;
+       }
+
+      /* Evaluate the step expression. */
+      line_number = arith_lineno;
+      expresult = eval_arith_for_expr (arith_for_command->step, &expok);
+      line_number = save_lineno;
+
+      if (expok == 0)
+       {
+         body_status = EXECUTION_FAILURE;
+         break;
+       }
+    }
+
+  loop_level--;
+  line_number = save_lineno;
+
+  return (body_status);
+}
+#endif
+
+#if defined (SELECT_COMMAND)
+static int LINES, COLS, tabsize;
+
+#define RP_SPACE ") "
+#define RP_SPACE_LEN 2
+
+/* XXX - does not handle numbers > 1000000 at all. */
+#define NUMBER_LEN(s) \
+((s < 10) ? 1 \
+         : ((s < 100) ? 2 \
+                     : ((s < 1000) ? 3 \
+                                  : ((s < 10000) ? 4 \
+                                                : ((s < 100000) ? 5 \
+                                                               : 6)))))
+
+static int
+print_index_and_element (len, ind, list)
+      int len, ind;
+      WORD_LIST *list;
+{
+  register WORD_LIST *l;
+  register int i;
+
+  if (list == 0)
+    return (0);
+  for (i = ind, l = list; l && --i; l = l->next)
+    ;
+  fprintf (stderr, "%*d%s%s", len, ind, RP_SPACE, l->word->word);
+  return (STRLEN (l->word->word));
+}
+
+static void
+indent (from, to)
+     int from, to;
+{
+  while (from < to)
+    {
+      if ((to / tabsize) > (from / tabsize))
+       {
+         putc ('\t', stderr);
+         from += tabsize - from % tabsize;
+       }
+      else
+       {
+         putc (' ', stderr);
+         from++;
+       }
+    }
+}
+
+static void
+print_select_list (list, list_len, max_elem_len, indices_len)
+     WORD_LIST *list;
+     int list_len, max_elem_len, indices_len;
+{
+  int ind, row, elem_len, pos, cols, rows;
+  int first_column_indices_len, other_indices_len;
+
+  if (list == 0)
+    {
+      putc ('\n', stderr);
+      return;
+    }
+
+  cols = max_elem_len ? COLS / max_elem_len : 1;
+  if (cols == 0)
+    cols = 1;
+  rows = list_len ? list_len / cols + (list_len % cols != 0) : 1;
+  cols = list_len ? list_len / rows + (list_len % rows != 0) : 1;
+
+  if (rows == 1)
+    {
+      rows = cols;
+      cols = 1;
+    }
+
+  first_column_indices_len = NUMBER_LEN (rows);
+  other_indices_len = indices_len;
+
+  for (row = 0; row < rows; row++)
+    {
+      ind = row;
+      pos = 0;
+      while (1)
+       {
+         indices_len = (pos == 0) ? first_column_indices_len : other_indices_len;
+         elem_len = print_index_and_element (indices_len, ind + 1, list);
+         elem_len += indices_len + RP_SPACE_LEN;
+         ind += rows;
+         if (ind >= list_len)
+           break;
+         indent (pos + elem_len, pos + max_elem_len);
+         pos += max_elem_len;
+       }
+      putc ('\n', stderr);
+    }
+}
+
+/* Print the elements of LIST, one per line, preceded by an index from 1 to
+   LIST_LEN.  Then display PROMPT and wait for the user to enter a number.
+   If the number is between 1 and LIST_LEN, return that selection.  If EOF
+   is read, return a null string.  If a blank line is entered, or an invalid
+   number is entered, the loop is executed again. */
+static char *
+select_query (list, list_len, prompt, print_menu)
+     WORD_LIST *list;
+     int list_len;
+     char *prompt;
+     int print_menu;
+{
+  int max_elem_len, indices_len, len;
+  intmax_t reply;
+  WORD_LIST *l;
+  char *repl_string, *t;
+
+  t = get_string_value ("LINES");
+  LINES = (t && *t) ? atoi (t) : 24;
+  t = get_string_value ("COLUMNS");
+  COLS =  (t && *t) ? atoi (t) : 80;
+
+#if 0
+  t = get_string_value ("TABSIZE");
+  tabsize = (t && *t) ? atoi (t) : 8;
+  if (tabsize <= 0)
+    tabsize = 8;
+#else
+  tabsize = 8;
+#endif
+
+  max_elem_len = 0;
+  for (l = list; l; l = l->next)
+    {
+      len = STRLEN (l->word->word);
+      if (len > max_elem_len)
+       max_elem_len = len;
+    }
+  indices_len = NUMBER_LEN (list_len);
+  max_elem_len += indices_len + RP_SPACE_LEN + 2;
+
+  while (1)
+    {
+      if (print_menu)
+       print_select_list (list, list_len, max_elem_len, indices_len);
+      fprintf (stderr, "%s", prompt);
+      fflush (stderr);
+      QUIT;
+
+      if (read_builtin ((WORD_LIST *)NULL) == EXECUTION_FAILURE)
+       {
+         putchar ('\n');
+         return ((char *)NULL);
+       }
+      repl_string = get_string_value ("REPLY");
+      if (*repl_string == 0)
+       {
+         print_menu = 1;
+         continue;
+       }
+      if (legal_number (repl_string, &reply) == 0)
+       return "";
+      if (reply < 1 || reply > list_len)
+       return "";
+
+      for (l = list; l && --reply; l = l->next)
+       ;
+      return (l->word->word);
+    }
+}
+
+/* Execute a SELECT command.  The syntax is:
+   SELECT word IN list DO command_list DONE
+   Only `break' or `return' in command_list will terminate
+   the command. */
+static int
+execute_select_command (select_command)
+     SELECT_COM *select_command;
+{
+  WORD_LIST *releaser, *list;
+  SHELL_VAR *v;
+  char *identifier, *ps3_prompt, *selection;
+  int retval, list_len, show_menu, save_line_number;
+
+  if (check_identifier (select_command->name, 1) == 0)
+    return (EXECUTION_FAILURE);
+
+  save_line_number = line_number;
+  line_number = select_command->line;
+
+  command_string_index = 0;
+  print_select_command_head (select_command);
+
+  if (echo_command_at_execute)
+    xtrace_print_select_command_head (select_command);
+
+  FREE (the_printed_command_except_trap);
+  the_printed_command_except_trap = savestring (the_printed_command);
+
+  retval = run_debug_trap ();
+#if defined (DEBUGGER)
+  /* In debugging mode, if the DEBUG trap returns a non-zero status, we
+     skip the command. */
+  if (debugging_mode && retval != EXECUTION_SUCCESS)
+    return (EXECUTION_SUCCESS);
+#endif
+
+  loop_level++;
+  identifier = select_command->name->word;
+
+  /* command and arithmetic substitution, parameter and variable expansion,
+     word splitting, pathname expansion, and quote removal. */
+  list = releaser = expand_words_no_vars (select_command->map_list);
+  list_len = list_length (list);
+  if (list == 0 || list_len == 0)
+    {
+      if (list)
+       dispose_words (list);
+      line_number = save_line_number;
+      return (EXECUTION_SUCCESS);
+    }
+
+  begin_unwind_frame ("select");
+  add_unwind_protect (dispose_words, releaser);
+
+  if (select_command->flags & CMD_IGNORE_RETURN)
+    select_command->action->flags |= CMD_IGNORE_RETURN;
+
+  retval = EXECUTION_SUCCESS;
+  show_menu = 1;
+
+  while (1)
+    {
+      line_number = select_command->line;
+      ps3_prompt = get_string_value ("PS3");
+      if (ps3_prompt == 0)
+       ps3_prompt = "#? ";
+
+      QUIT;
+      selection = select_query (list, list_len, ps3_prompt, show_menu);
+      QUIT;
+      if (selection == 0)
+       {
+         /* select_query returns EXECUTION_FAILURE if the read builtin
+            fails, so we want to return failure in this case. */
+         retval = EXECUTION_FAILURE;
+         break;
+       }
+
+      v = bind_variable (identifier, selection);
+      if (readonly_p (v) || noassign_p (v))
+       {
+         if (readonly_p (v) && interactive_shell == 0 && posixly_correct)
+           {
+             last_command_exit_value = EXECUTION_FAILURE;
+             jump_to_top_level (FORCE_EOF);
+           }
+         else
+           {
+             dispose_words (releaser);
+             discard_unwind_frame ("select");
+             loop_level--;
+             line_number = save_line_number;
+             return (EXECUTION_FAILURE);
+           }
+       }
+
+      retval = execute_command (select_command->action);
+
+      REAP ();
+      QUIT;
+
+      if (breaking)
+       {
+         breaking--;
+         break;
+       }
+
+      if (continuing)
+       {
+         continuing--;
+         if (continuing)
+           break;
+       }
+
+#if defined (KSH_COMPATIBLE_SELECT)
+      show_menu = 0;
+      selection = get_string_value ("REPLY");
+      if (selection && *selection == '\0')
+        show_menu = 1;
+#endif
+    }
+
+  loop_level--;
+  line_number = save_line_number;
+
+  dispose_words (releaser);
+  discard_unwind_frame ("select");
+  return (retval);
+}
+#endif /* SELECT_COMMAND */
+
+/* Execute a CASE command.  The syntax is: CASE word_desc IN pattern_list ESAC.
+   The pattern_list is a linked list of pattern clauses; each clause contains
+   some patterns to compare word_desc against, and an associated command to
+   execute. */
+static int
+execute_case_command (case_command)
+     CASE_COM *case_command;
+{
+  register WORD_LIST *list;
+  WORD_LIST *wlist, *es;
+  PATTERN_LIST *clauses;
+  char *word, *pattern;
+  int retval, match, ignore_return, save_line_number;
+
+  save_line_number = line_number;
+  line_number = case_command->line;
+
+  command_string_index = 0;
+  print_case_command_head (case_command);
+
+  if (echo_command_at_execute)
+    xtrace_print_case_command_head (case_command);
+
+  if (this_command_name == 0 || (STREQ (this_command_name, "trap") == 0))
+    {
+      FREE (the_printed_command_except_trap);
+      the_printed_command_except_trap = savestring (the_printed_command);
+    }
+
+  retval = run_debug_trap();
+#if defined (DEBUGGER)
+  /* In debugging mode, if the DEBUG trap returns a non-zero status, we
+     skip the command. */
+  if (debugging_mode && retval != EXECUTION_SUCCESS)
+    {
+      line_number = save_line_number;
+      return (EXECUTION_SUCCESS);
+    }
+#endif
+
+  /* Posix.2 specifies that the WORD is tilde expanded. */
+  if (member ('~', case_command->word->word))
+    {
+      word = bash_tilde_expand (case_command->word->word, 0);
+      free (case_command->word->word);
+      case_command->word->word = word;
+    }
+
+  wlist = expand_word_unsplit (case_command->word, 0);
+  word = wlist ? string_list (wlist) : savestring ("");
+  dispose_words (wlist);
+
+  retval = EXECUTION_SUCCESS;
+  ignore_return = case_command->flags & CMD_IGNORE_RETURN;
+
+  begin_unwind_frame ("case");
+  add_unwind_protect ((Function *)xfree, word);
+
+#define EXIT_CASE()  goto exit_case_command
+
+  for (clauses = case_command->clauses; clauses; clauses = clauses->next)
+    {
+      QUIT;
+      for (list = clauses->patterns; list; list = list->next)
+       {
+         /* Posix.2 specifies to tilde expand each member of the pattern
+            list. */
+         if (member ('~', list->word->word))
+           {
+             pattern = bash_tilde_expand (list->word->word, 0);
+             free (list->word->word);
+             list->word->word = pattern;
+           }
+
+         es = expand_word_leave_quoted (list->word, 0);
+
+         if (es && es->word && es->word->word && *(es->word->word))
+           pattern = quote_string_for_globbing (es->word->word, QGLOB_CVTNULL);
+         else
+           {
+             pattern = (char *)xmalloc (1);
+             pattern[0] = '\0';
+           }
+
+         /* Since the pattern does not undergo quote removal (as per
+            Posix.2, section 3.9.4.3), the strmatch () call must be able
+            to recognize backslashes as escape characters. */
+         match = strmatch (pattern, word, FNMATCH_EXTFLAG) != FNM_NOMATCH;
+         free (pattern);
+
+         dispose_words (es);
+
+         if (match)
+           {
+             if (clauses->action && ignore_return)
+               clauses->action->flags |= CMD_IGNORE_RETURN;
+             retval = execute_command (clauses->action);
+             EXIT_CASE ();
+           }
+
+         QUIT;
+       }
+    }
+
+exit_case_command:
+  free (word);
+  discard_unwind_frame ("case");
+  line_number = save_line_number;
+  return (retval);
+}
+
+#define CMD_WHILE 0
+#define CMD_UNTIL 1
+
+/* The WHILE command.  Syntax: WHILE test DO action; DONE.
+   Repeatedly execute action while executing test produces
+   EXECUTION_SUCCESS. */
+static int
+execute_while_command (while_command)
+     WHILE_COM *while_command;
+{
+  return (execute_while_or_until (while_command, CMD_WHILE));
+}
+
+/* UNTIL is just like WHILE except that the test result is negated. */
+static int
+execute_until_command (while_command)
+     WHILE_COM *while_command;
+{
+  return (execute_while_or_until (while_command, CMD_UNTIL));
+}
+
+/* The body for both while and until.  The only difference between the
+   two is that the test value is treated differently.  TYPE is
+   CMD_WHILE or CMD_UNTIL.  The return value for both commands should
+   be EXECUTION_SUCCESS if no commands in the body are executed, and
+   the status of the last command executed in the body otherwise. */
+static int
+execute_while_or_until (while_command, type)
+     WHILE_COM *while_command;
+     int type;
+{
+  int return_value, body_status;
+
+  body_status = EXECUTION_SUCCESS;
+  loop_level++;
+
+  while_command->test->flags |= CMD_IGNORE_RETURN;
+  if (while_command->flags & CMD_IGNORE_RETURN)
+    while_command->action->flags |= CMD_IGNORE_RETURN;
+
+  while (1)
+    {
+      return_value = execute_command (while_command->test);
+      REAP ();
+
+      /* Need to handle `break' in the test when we would break out of the
+         loop.  The job control code will set `breaking' to loop_level
+         when a job in a loop is stopped with SIGTSTP.  If the stopped job
+         is in the loop test, `breaking' will not be reset unless we do
+         this, and the shell will cease to execute commands. */
+      if (type == CMD_WHILE && return_value != EXECUTION_SUCCESS)
+       {
+         if (breaking)
+           breaking--;
+         break;
+       }
+      if (type == CMD_UNTIL && return_value == EXECUTION_SUCCESS)
+       {
+         if (breaking)
+           breaking--;
+         break;
+       }
+
+      QUIT;
+      body_status = execute_command (while_command->action);
+      QUIT;
+
+      if (breaking)
+       {
+         breaking--;
+         break;
+       }
+
+      if (continuing)
+       {
+         continuing--;
+         if (continuing)
+           break;
+       }
+    }
+  loop_level--;
+
+  return (body_status);
+}
+
+/* IF test THEN command [ELSE command].
+   IF also allows ELIF in the place of ELSE IF, but
+   the parser makes *that* stupidity transparent. */
+static int
+execute_if_command (if_command)
+     IF_COM *if_command;
+{
+  int return_value, save_line_number;
+
+  save_line_number = line_number;
+  if_command->test->flags |= CMD_IGNORE_RETURN;
+  return_value = execute_command (if_command->test);
+  line_number = save_line_number;
+
+  if (return_value == EXECUTION_SUCCESS)
+    {
+      QUIT;
+
+      if (if_command->true_case && (if_command->flags & CMD_IGNORE_RETURN))
+       if_command->true_case->flags |= CMD_IGNORE_RETURN;
+
+      return (execute_command (if_command->true_case));
+    }
+  else
+    {
+      QUIT;
+
+      if (if_command->false_case && (if_command->flags & CMD_IGNORE_RETURN))
+       if_command->false_case->flags |= CMD_IGNORE_RETURN;
+
+      return (execute_command (if_command->false_case));
+    }
+}
+
+#if defined (DPAREN_ARITHMETIC)
+static int
+execute_arith_command (arith_command)
+     ARITH_COM *arith_command;
+{
+  int expok, save_line_number, retval;
+  intmax_t expresult;
+  WORD_LIST *new;
+
+  expresult = 0;
+
+  save_line_number = line_number;
+  this_command_name = "((";    /* )) */
+  line_number = arith_command->line;
+  /* If we're in a function, update the line number information. */
+  if (variable_context && interactive_shell)
+    line_number -= function_line_number;
+
+  command_string_index = 0;
+  print_arith_command (arith_command->exp);
+  FREE (the_printed_command_except_trap);
+  the_printed_command_except_trap = savestring (the_printed_command);
+
+  /* Run the debug trap before each arithmetic command, but do it after we
+     update the line number information and before we expand the various
+     words in the expression. */
+  retval = run_debug_trap ();
+#if defined (DEBUGGER)
+  /* In debugging mode, if the DEBUG trap returns a non-zero status, we
+     skip the command. */
+  if (debugging_mode && retval != EXECUTION_SUCCESS)
+    {
+      line_number = save_line_number;
+      return (EXECUTION_SUCCESS);
+    }
+#endif
+
+  new = expand_words_no_vars (arith_command->exp);
+
+  /* If we're tracing, make a new word list with `((' at the front and `))'
+     at the back and print it. */
+  if (echo_command_at_execute)
+    xtrace_print_arith_cmd (new);
+
+  if (new)
+    {
+      expresult = evalexp (new->word->word, &expok);
+      line_number = save_line_number;
+      dispose_words (new);
+    }
+  else
+    {
+      expresult = 0;
+      expok = 1;
+    }
+
+  if (expok == 0)
+    return (EXECUTION_FAILURE);
+
+  return (expresult == 0 ? EXECUTION_FAILURE : EXECUTION_SUCCESS);
+}
+#endif /* DPAREN_ARITHMETIC */
+
+#if defined (COND_COMMAND)
+
+static char *nullstr = "";
+
+static int
+execute_cond_node (cond)
+     COND_COM *cond;
+{
+  int result, invert, patmatch, rmatch, mflags;
+  char *arg1, *arg2;
+
+  invert = (cond->flags & CMD_INVERT_RETURN);
+
+  if (cond->type == COND_EXPR)
+    result = execute_cond_node (cond->left);
+  else if (cond->type == COND_OR)
+    {
+      result = execute_cond_node (cond->left);
+      if (result != EXECUTION_SUCCESS)
+       result = execute_cond_node (cond->right);
+    }
+  else if (cond->type == COND_AND)
+    {
+      result = execute_cond_node (cond->left);
+      if (result == EXECUTION_SUCCESS)
+       result = execute_cond_node (cond->right);
+    }
+  else if (cond->type == COND_UNARY)
+    {
+      arg1 = cond_expand_word (cond->left->op, 0);
+      if (arg1 == 0)
+       arg1 = nullstr;
+      if (echo_command_at_execute)
+       xtrace_print_cond_term (cond->type, invert, cond->op, arg1, (char *)NULL);
+      result = unary_test (cond->op->word, arg1) ? EXECUTION_SUCCESS : EXECUTION_FAILURE;
+      if (arg1 != nullstr)
+       free (arg1);
+    }
+  else if (cond->type == COND_BINARY)
+    {
+      patmatch = ((cond->op->word[1] == '=') && (cond->op->word[2] == '\0') &&
+                 (cond->op->word[0] == '!' || cond->op->word[0] == '=') ||
+                 (cond->op->word[0] == '=' && cond->op->word[1] == '\0'));
+#if defined (COND_REGEXP)
+      rmatch = (cond->op->word[0] == '=' && cond->op->word[1] == '~' &&
+               cond->op->word[2] == '\0');
+#endif
+
+      arg1 = cond_expand_word (cond->left->op, 0);
+      if (arg1 == 0)
+       arg1 = nullstr;
+      arg2 = cond_expand_word (cond->right->op, patmatch);
+      if (arg2 == 0)
+       arg2 = nullstr;
+
+      if (echo_command_at_execute)
+       xtrace_print_cond_term (cond->type, invert, cond->op, arg1, arg2);
+
+#if defined (COND_REGEXP)
+      if (rmatch)
+       {
+         mflags = SHMAT_PWARN;
+#if defined (ARRAY_VARS)
+         mflags |= SHMAT_SUBEXP;
+#endif
+
+         result = sh_regmatch (arg1, arg2, mflags);
+       }
+      else
+#endif /* COND_REGEXP */
+       result = binary_test (cond->op->word, arg1, arg2, TEST_PATMATCH|TEST_ARITHEXP)
+                               ? EXECUTION_SUCCESS
+                               : EXECUTION_FAILURE;
+      if (arg1 != nullstr)
+       free (arg1);
+      if (arg2 != nullstr)
+       free (arg2);
+    }
+  else
+    {
+      command_error ("execute_cond_node", CMDERR_BADTYPE, cond->type, 0);
+      jump_to_top_level (DISCARD);
+      result = EXECUTION_FAILURE;
+    }
+
+  if (invert)
+    result = (result == EXECUTION_SUCCESS) ? EXECUTION_FAILURE : EXECUTION_SUCCESS;
+
+  return result;
+}
+
+static int
+execute_cond_command (cond_command)
+     COND_COM *cond_command;
+{
+  int retval, save_line_number;
+
+  retval = EXECUTION_SUCCESS;
+  save_line_number = line_number;
+
+  this_command_name = "[[";
+  line_number = cond_command->line;
+  /* If we're in a function, update the line number information. */
+  if (variable_context && interactive_shell)
+    line_number -= function_line_number;
+
+  command_string_index = 0;
+  print_cond_command (cond_command);
+  FREE (the_printed_command_except_trap);
+  the_printed_command_except_trap = savestring (the_printed_command);
+
+  /* Run the debug trap before each conditional command, but do it after we
+     update the line number information. */
+  retval = run_debug_trap ();
+#if defined (DEBUGGER)
+  /* In debugging mode, if the DEBUG trap returns a non-zero status, we
+     skip the command. */
+  if (debugging_mode && retval != EXECUTION_SUCCESS)
+    {
+      line_number = save_line_number;
+      return (EXECUTION_SUCCESS);
+    }
+#endif
+
+#if 0
+  debug_print_cond_command (cond_command);
+#endif
+
+  last_command_exit_value = retval = execute_cond_node (cond_command);
+  line_number = save_line_number;
+  return (retval);
+}
+#endif /* COND_COMMAND */
+
+static void
+bind_lastarg (arg)
+     char *arg;
+{
+  SHELL_VAR *var;
+
+  if (arg == 0)
+    arg = "";
+  var = bind_variable ("_", arg);
+  VUNSETATTR (var, att_exported);
+}
+
+/* Execute a null command.  Fork a subshell if the command uses pipes or is
+   to be run asynchronously.  This handles all the side effects that are
+   supposed to take place. */
+static int
+execute_null_command (redirects, pipe_in, pipe_out, async, old_last_command_subst_pid)
+     REDIRECT *redirects;
+     int pipe_in, pipe_out, async;
+     pid_t old_last_command_subst_pid;
+{
+  int r;
+
+  if (pipe_in != NO_PIPE || pipe_out != NO_PIPE || async)
+    {
+      /* We have a null command, but we really want a subshell to take
+        care of it.  Just fork, do piping and redirections, and exit. */
+      if (make_child ((char *)NULL, async) == 0)
+       {
+         /* Cancel traps, in trap.c. */
+         restore_original_signals ();          /* XXX */
+
+         do_piping (pipe_in, pipe_out);
+
+         subshell_environment = SUBSHELL_ASYNC;
+
+         if (do_redirections (redirects, RX_ACTIVE) == 0)
+           exit (EXECUTION_SUCCESS);
+         else
+           exit (EXECUTION_FAILURE);
+       }
+      else
+       {
+         close_pipes (pipe_in, pipe_out);
+#if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD)
+         unlink_fifo_list ();
+#endif
+         return (EXECUTION_SUCCESS);
+       }
+    }
+  else
+    {
+      /* Even if there aren't any command names, pretend to do the
+        redirections that are specified.  The user expects the side
+        effects to take place.  If the redirections fail, then return
+        failure.  Otherwise, if a command substitution took place while
+        expanding the command or a redirection, return the value of that
+        substitution.  Otherwise, return EXECUTION_SUCCESS. */
+
+      r = do_redirections (redirects, RX_ACTIVE|RX_UNDOABLE);
+      cleanup_redirects (redirection_undo_list);
+      redirection_undo_list = (REDIRECT *)NULL;
+
+      if (r != 0)
+       return (EXECUTION_FAILURE);
+      else if (old_last_command_subst_pid != last_command_subst_pid)
+       return (last_command_exit_value);
+      else
+       return (EXECUTION_SUCCESS);
+    }
+}
+
+/* This is a hack to suppress word splitting for assignment statements
+   given as arguments to builtins with the ASSIGNMENT_BUILTIN flag set. */
+static void
+fix_assignment_words (words)
+     WORD_LIST *words;
+{
+  WORD_LIST *w;
+  struct builtin *b;
+
+  if (words == 0)
+    return;
+
+  b = 0;
+
+  for (w = words; w; w = w->next)
+    if (w->word->flags & W_ASSIGNMENT)
+      {
+       if (b == 0)
+         {
+           b = builtin_address_internal (words->word->word, 0);
+           if (b == 0 || (b->flags & ASSIGNMENT_BUILTIN) == 0)
+             return;
+         }
+       w->word->flags |= (W_NOSPLIT|W_NOGLOB|W_TILDEEXP);
+      }
+}
+
+/* The meaty part of all the executions.  We have to start hacking the
+   real execution of commands here.  Fork a process, set things up,
+   execute the command. */
+static int
+execute_simple_command (simple_command, pipe_in, pipe_out, async, fds_to_close)
+     SIMPLE_COM *simple_command;
+     int pipe_in, pipe_out, async;
+     struct fd_bitmap *fds_to_close;
+{
+  WORD_LIST *words, *lastword;
+  char *command_line, *lastarg, *temp;
+  int first_word_quoted, result, builtin_is_special, already_forked, dofork;
+  pid_t old_last_command_subst_pid, old_last_async_pid;
+  sh_builtin_func_t *builtin;
+  SHELL_VAR *func;
+
+  result = EXECUTION_SUCCESS;
+  special_builtin_failed = builtin_is_special = 0;
+  command_line = (char *)0;
+
+  /* If we're in a function, update the line number information. */
+  if (variable_context && interactive_shell)
+    line_number -= function_line_number;
+
+  /* Remember what this command line looks like at invocation. */
+  command_string_index = 0;
+  print_simple_command (simple_command);
+
+  if (this_command_name == 0 || (STREQ (this_command_name, "trap") == 0))
+    {
+      FREE (the_printed_command_except_trap);
+      the_printed_command_except_trap = savestring (the_printed_command);
+    }
+
+  /* Run the debug trap before each simple command, but do it after we
+     update the line number information. */
+  result = run_debug_trap ();
+#if defined (DEBUGGER)
+  /* In debugging mode, if the DEBUG trap returns a non-zero status, we
+     skip the command. */
+  if (debugging_mode && result != EXECUTION_SUCCESS)
+    return (EXECUTION_SUCCESS);
+#endif
+
+  first_word_quoted =
+    simple_command->words ? (simple_command->words->word->flags & W_QUOTED): 0;
+
+  old_last_command_subst_pid = last_command_subst_pid;
+  old_last_async_pid = last_asynchronous_pid;
+
+  already_forked = dofork = 0;
+
+  /* If we're in a pipeline or run in the background, set DOFORK so we
+     make the child early, before word expansion.  This keeps assignment
+     statements from affecting the parent shell's environment when they
+     should not. */
+  dofork = pipe_in != NO_PIPE || pipe_out != NO_PIPE || async;
+
+  /* Something like `%2 &' should restart job 2 in the background, not cause
+     the shell to fork here. */
+  if (dofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE &&
+       simple_command->words && simple_command->words->word &&
+       simple_command->words->word->word &&
+       (simple_command->words->word->word[0] == '%'))
+    dofork = 0;
+
+  if (dofork)
+    {
+#if 0
+      /* XXX memory leak if expand_words() error causes a jump_to_top_level */
+      command_line = savestring (the_printed_command);
+#endif
+
+      /* Do this now, because execute_disk_command will do it anyway in the
+        vast majority of cases. */
+      maybe_make_export_env ();
+
+#if 0
+      if (make_child (command_line, async) == 0)
+#else
+      if (make_child (savestring (the_printed_command), async) == 0)
+#endif
+       {
+         already_forked = 1;
+         simple_command->flags |= CMD_NO_FORK;
+
+         subshell_environment = (pipe_in != NO_PIPE || pipe_out != NO_PIPE)
+                                       ? (SUBSHELL_PIPE|SUBSHELL_FORK)
+                                       : (SUBSHELL_ASYNC|SUBSHELL_FORK);
+
+         /* We need to do this before piping to handle some really
+            pathological cases where one of the pipe file descriptors
+            is < 2. */
+         if (fds_to_close)
+           close_fd_bitmap (fds_to_close);
+
+         do_piping (pipe_in, pipe_out);
+         pipe_in = pipe_out = NO_PIPE;
+
+         last_asynchronous_pid = old_last_async_pid;
+       }
+      else
+       {
+         close_pipes (pipe_in, pipe_out);
+#if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD)
+         unlink_fifo_list ();
+#endif
+         command_line = (char *)NULL;      /* don't free this. */
+         bind_lastarg ((char *)NULL);
+         return (result);
+       }
+    }
+
+  /* If we are re-running this as the result of executing the `command'
+     builtin, do not expand the command words a second time. */
+  if ((simple_command->flags & CMD_INHIBIT_EXPANSION) == 0)
+    {
+      current_fds_to_close = fds_to_close;
+      fix_assignment_words (simple_command->words);
+      words = expand_words (simple_command->words);
+      current_fds_to_close = (struct fd_bitmap *)NULL;
+    }
+  else
+    words = copy_word_list (simple_command->words);
+
+  /* It is possible for WORDS not to have anything left in it.
+     Perhaps all the words consisted of `$foo', and there was
+     no variable `$foo'. */
+  if (words == 0)
+    {
+      this_command_name = 0;
+      result = execute_null_command (simple_command->redirects,
+                                    pipe_in, pipe_out,
+                                    already_forked ? 0 : async,
+                                    old_last_command_subst_pid);
+      if (already_forked)
+       exit (result);
+      else
+       {
+         bind_lastarg ((char *)NULL);
+         set_pipestatus_from_exit (result);
+         return (result);
+       }
+    }
+
+  lastarg = (char *)NULL;
+
+  begin_unwind_frame ("simple-command");
+
+  if (echo_command_at_execute)
+    xtrace_print_word_list (words, 1);
+
+  builtin = (sh_builtin_func_t *)NULL;
+  func = (SHELL_VAR *)NULL;
+  if ((simple_command->flags & CMD_NO_FUNCTIONS) == 0)
+    {
+      /* Posix.2 says special builtins are found before functions.  We
+        don't set builtin_is_special anywhere other than here, because
+        this path is followed only when the `command' builtin is *not*
+        being used, and we don't want to exit the shell if a special
+        builtin executed with `command builtin' fails.  `command' is not
+        a special builtin. */
+      if (posixly_correct)
+       {
+         builtin = find_special_builtin (words->word->word);
+         if (builtin)
+           builtin_is_special = 1;
+       }
+      if (builtin == 0)
+       func = find_function (words->word->word);
+    }
+
+  add_unwind_protect (dispose_words, words);
+  QUIT;
+
+  /* Bind the last word in this command to "$_" after execution. */
+  for (lastword = words; lastword->next; lastword = lastword->next)
+    ;
+  lastarg = lastword->word->word;
+
+#if defined (JOB_CONTROL)
+  /* Is this command a job control related thing? */
+  if (words->word->word[0] == '%' && already_forked == 0)
+    {
+      this_command_name = async ? "bg" : "fg";
+      last_shell_builtin = this_shell_builtin;
+      this_shell_builtin = builtin_address (this_command_name);
+      result = (*this_shell_builtin) (words);
+      goto return_result;
+    }
+
+  /* One other possiblilty.  The user may want to resume an existing job.
+     If they do, find out whether this word is a candidate for a running
+     job. */
+  if (job_control && already_forked == 0 && async == 0 &&
+       !first_word_quoted &&
+       !words->next &&
+       words->word->word[0] &&
+       !simple_command->redirects &&
+       pipe_in == NO_PIPE &&
+       pipe_out == NO_PIPE &&
+       (temp = get_string_value ("auto_resume")))
+    {
+      int job, jflags, started_status;
+
+      jflags = JM_STOPPED|JM_FIRSTMATCH;
+      if (STREQ (temp, "exact"))
+       jflags |= JM_EXACT;
+      else if (STREQ (temp, "substring"))
+       jflags |= JM_SUBSTRING;
+      else
+       jflags |= JM_PREFIX;
+      job = get_job_by_name (words->word->word, jflags);
+      if (job != NO_JOB)
+       {
+         run_unwind_frame ("simple-command");
+         this_command_name = "fg";
+         last_shell_builtin = this_shell_builtin;
+         this_shell_builtin = builtin_address ("fg");
+
+         started_status = start_job (job, 1);
+         return ((started_status < 0) ? EXECUTION_FAILURE : started_status);
+       }
+    }
+#endif /* JOB_CONTROL */
+
+  /* Remember the name of this command globally. */
+  this_command_name = words->word->word;
+
+  QUIT;
+
+  /* This command could be a shell builtin or a user-defined function.
+     We have already found special builtins by this time, so we do not
+     set builtin_is_special.  If this is a function or builtin, and we
+     have pipes, then fork a subshell in here.  Otherwise, just execute
+     the command directly. */
+  if (func == 0 && builtin == 0)
+    builtin = find_shell_builtin (this_command_name);
+
+  last_shell_builtin = this_shell_builtin;
+  this_shell_builtin = builtin;
+
+  if (builtin || func)
+    {
+      if (already_forked)
+       {
+         /* reset_terminating_signals (); */   /* XXX */
+         /* Cancel traps, in trap.c. */
+         restore_original_signals ();
+
+         if (async)
+           {
+             if ((simple_command->flags & CMD_STDIN_REDIR) &&
+                   pipe_in == NO_PIPE &&
+                   (stdin_redirects (simple_command->redirects) == 0))
+               async_redirect_stdin ();
+             setup_async_signals ();
+           }
+
+         subshell_level++;
+         execute_subshell_builtin_or_function
+           (words, simple_command->redirects, builtin, func,
+            pipe_in, pipe_out, async, fds_to_close,
+            simple_command->flags);
+         subshell_level--;
+       }
+      else
+       {
+         result = execute_builtin_or_function
+           (words, builtin, func, simple_command->redirects, fds_to_close,
+            simple_command->flags);
+         if (builtin)
+           {
+             if (result > EX_SHERRBASE)
+               {
+                 result = builtin_status (result);
+                 if (builtin_is_special)
+                   special_builtin_failed = 1;
+               }
+             /* In POSIX mode, if there are assignment statements preceding
+                a special builtin, they persist after the builtin
+                completes. */
+             if (posixly_correct && builtin_is_special && temporary_env)
+               merge_temporary_env ();
+           }
+         else          /* function */
+           {
+             if (result == EX_USAGE)
+               result = EX_BADUSAGE;
+             else if (result > EX_SHERRBASE)
+               result = EXECUTION_FAILURE;
+           }
+
+         set_pipestatus_from_exit (result);
+
+         goto return_result;
+       }
+    }
+
+  if (command_line == 0)
+    command_line = savestring (the_printed_command);
+
+  execute_disk_command (words, simple_command->redirects, command_line,
+                       pipe_in, pipe_out, async, fds_to_close,
+                       simple_command->flags);
+
+ return_result:
+  bind_lastarg (lastarg);
+  FREE (command_line);
+  dispose_words (words);
+  discard_unwind_frame ("simple-command");
+  this_command_name = (char *)NULL;    /* points to freed memory now */
+  return (result);
+}
+
+/* Translate the special builtin exit statuses.  We don't really need a
+   function for this; it's a placeholder for future work. */
+static int
+builtin_status (result)
+     int result;
+{
+  int r;
+
+  switch (result)
+    {
+    case EX_USAGE:
+      r = EX_BADUSAGE;
+      break;
+    case EX_REDIRFAIL:
+    case EX_BADSYNTAX:
+    case EX_BADASSIGN:
+    case EX_EXPFAIL:
+      r = EXECUTION_FAILURE;
+      break;
+    default:
+      r = EXECUTION_SUCCESS;
+      break;
+    }
+  return (r);
+}
+
+static int
+execute_builtin (builtin, words, flags, subshell)
+     sh_builtin_func_t *builtin;
+     WORD_LIST *words;
+     int flags, subshell;
+{
+  int old_e_flag, result, eval_unwind;
+  int isbltinenv;
+
+  old_e_flag = exit_immediately_on_error;
+  /* The eval builtin calls parse_and_execute, which does not know about
+     the setting of flags, and always calls the execution functions with
+     flags that will exit the shell on an error if -e is set.  If the
+     eval builtin is being called, and we're supposed to ignore the exit
+     value of the command, we turn the -e flag off ourselves, then
+     restore it when the command completes. */
+  if (subshell == 0 && builtin == eval_builtin && (flags & CMD_IGNORE_RETURN))
+    {
+      begin_unwind_frame ("eval_builtin");
+      unwind_protect_int (exit_immediately_on_error);
+      exit_immediately_on_error = 0;
+      eval_unwind = 1;
+    }
+  else
+    eval_unwind = 0;
+
+  /* The temporary environment for a builtin is supposed to apply to
+     all commands executed by that builtin.  Currently, this is a
+     problem only with the `source' and `eval' builtins. */
+  isbltinenv = (builtin == source_builtin || builtin == eval_builtin);
+  if (isbltinenv)
+    {
+      if (subshell == 0)
+       begin_unwind_frame ("builtin_env");
+
+      if (temporary_env)
+       {
+         push_scope (VC_BLTNENV, temporary_env);
+         if (subshell == 0)
+           add_unwind_protect (pop_scope, "1");
+          temporary_env = (HASH_TABLE *)NULL;    
+       }
+    }
+
+  /* `return' does a longjmp() back to a saved environment in execute_function.
+     If a variable assignment list preceded the command, and the shell is
+     running in POSIX mode, we need to merge that into the shell_variables
+     table, since `return' is a POSIX special builtin. */
+  if (posixly_correct && subshell == 0 && builtin == return_builtin && temporary_env)
+    {
+      begin_unwind_frame ("return_temp_env");
+      add_unwind_protect (merge_temporary_env, (char *)NULL);
+    }
+
+  result = ((*builtin) (words->next));
+
+  /* This shouldn't happen, but in case `return' comes back instead of
+     longjmp'ing, we need to unwind. */
+  if (posixly_correct && subshell == 0 && builtin == return_builtin && temporary_env)
+    discard_unwind_frame ("return_temp_env");
+
+  if (subshell == 0 && isbltinenv)
+    run_unwind_frame ("builtin_env");
+
+  if (eval_unwind)
+    {
+      exit_immediately_on_error += old_e_flag;
+      discard_unwind_frame ("eval_builtin");
+    }
+
+  return (result);
+}
+
+static int
+execute_function (var, words, flags, fds_to_close, async, subshell)
+     SHELL_VAR *var;
+     WORD_LIST *words;
+     int flags;
+     struct fd_bitmap *fds_to_close;
+     int async, subshell;
+{
+  int return_val, result;
+  COMMAND *tc, *fc, *save_current;
+  char *debug_trap, *error_trap, *return_trap;
+#if defined (ARRAY_VARS)
+  SHELL_VAR *funcname_v, *bash_source_v, *bash_lineno_v;
+  ARRAY *funcname_a, *bash_source_a, *bash_lineno_a;
+#endif
+  FUNCTION_DEF *shell_fn;
+  char *sfile, *t;
+  static int funcnest = 0;
+
+  USE_VAR(fc);
+
+#if defined (ARRAY_VARS)
+  GET_ARRAY_FROM_VAR ("FUNCNAME", funcname_v, funcname_a);
+  GET_ARRAY_FROM_VAR ("BASH_SOURCE", bash_source_v, bash_source_a);
+  GET_ARRAY_FROM_VAR ("BASH_LINENO", bash_lineno_v, bash_lineno_a);
+#endif
+
+  tc = (COMMAND *)copy_command (function_cell (var));
+  if (tc && (flags & CMD_IGNORE_RETURN))
+    tc->flags |= CMD_IGNORE_RETURN;
+
+  if (subshell == 0)
+    {
+      begin_unwind_frame ("function_calling");
+      push_context (var->name, subshell, temporary_env);
+      add_unwind_protect (pop_context, (char *)NULL);
+      unwind_protect_int (line_number);
+      unwind_protect_int (return_catch_flag);
+      unwind_protect_jmp_buf (return_catch);
+      add_unwind_protect (dispose_command, (char *)tc);
+      unwind_protect_pointer (this_shell_function);
+      unwind_protect_int (loop_level);
+    }
+  else
+    push_context (var->name, subshell, temporary_env); /* don't unwind-protect for subshells */
+
+  temporary_env = (HASH_TABLE *)NULL;
+
+  this_shell_function = var;
+  make_funcname_visible (1);
+
+  debug_trap = TRAP_STRING(DEBUG_TRAP);
+  error_trap = TRAP_STRING(ERROR_TRAP);
+  return_trap = TRAP_STRING(RETURN_TRAP);
+  
+  /* The order of the unwind protects for debug_trap, error_trap and
+     return_trap is important here!  unwind-protect commands are run
+     in reverse order of registration.  If this causes problems, take
+     out the xfree unwind-protect calls and live with the small memory leak. */
+
+  /* function_trace_mode != 0 means that all functions inherit the DEBUG trap.
+     if the function has the trace attribute set, it inherits the DEBUG trap */
+  if (debug_trap && ((trace_p (var) == 0) && function_trace_mode == 0))
+    {
+      if (subshell == 0)
+       {
+         debug_trap = savestring (debug_trap);
+         add_unwind_protect (xfree, debug_trap);
+         add_unwind_protect (set_debug_trap, debug_trap);
+       }
+      restore_default_signal (DEBUG_TRAP);
+    }
+
+  /* error_trace_mode != 0 means that functions inherit the ERR trap. */
+  if (error_trap && error_trace_mode == 0)
+    {
+      if (subshell == 0)
+       {
+         error_trap = savestring (error_trap);
+         add_unwind_protect (xfree, error_trap);
+         add_unwind_protect (set_error_trap, error_trap);
+       }
+      restore_default_signal (ERROR_TRAP);
+    }
+
+  if (return_trap && ((trace_p (var) == 0) && function_trace_mode == 0))
+    {
+      if (subshell == 0)
+       {
+         return_trap = savestring (return_trap);
+         add_unwind_protect (xfree, return_trap);
+         add_unwind_protect (set_return_trap, return_trap);
+       }
+      restore_default_signal (RETURN_TRAP);
+    }
+  
+  funcnest++;
+#if defined (ARRAY_VARS)
+  /* This is quite similar to the code in shell.c and elsewhere. */
+  shell_fn = find_function_def (this_shell_function->name);
+  sfile = shell_fn ? shell_fn->source_file : "";
+  array_push (funcname_a, this_shell_function->name);
+
+  array_push (bash_source_a, sfile);
+  t = itos (executing_line_number ());
+  array_push (bash_lineno_a, t);
+  free (t);
+#endif
+
+  /* The temporary environment for a function is supposed to apply to
+     all commands executed within the function body. */
+
+  remember_args (words->next, 1);
+
+  /* Update BASH_ARGV and BASH_ARGC */
+  if (debugging_mode)
+    push_args (words->next);
+
+  /* Number of the line on which the function body starts. */
+  line_number = function_line_number = tc->line;
+
+  if (subshell)
+    {
+#if defined (JOB_CONTROL)
+      stop_pipeline (async, (COMMAND *)NULL);
+#endif
+      fc = (tc->type == cm_group) ? tc->value.Group->command : tc;
+
+      if (fc && (flags & CMD_IGNORE_RETURN))
+       fc->flags |= CMD_IGNORE_RETURN;
+    }
+  else
+    fc = tc;
+
+  return_catch_flag++;
+  return_val = setjmp (return_catch);
+
+  if (return_val)
+    result = return_catch_value;
+  else
+    {
+      /* Run the debug trap here so we can trap at the start of a function's
+        execution rather than the execution of the body's first command. */
+      showing_function_line = 1;
+      save_current = currently_executing_command;
+      result = run_debug_trap ();
+#if defined (DEBUGGER)
+      /* In debugging mode, if the DEBUG trap returns a non-zero status, we
+        skip the command. */
+      if (debugging_mode == 0 || result == EXECUTION_SUCCESS)
+       {
+         showing_function_line = 0;
+         currently_executing_command = save_current;
+         result = execute_command_internal (fc, 0, NO_PIPE, NO_PIPE, fds_to_close);
+
+         /* Run the RETURN trap in the function's context */
+         save_current = currently_executing_command;
+         run_return_trap ();
+         currently_executing_command = save_current;
+       }
+#else
+      result = execute_command_internal (fc, 0, NO_PIPE, NO_PIPE, fds_to_close);
+#endif
+      showing_function_line = 0;
+    }
+
+  /* Restore BASH_ARGC and BASH_ARGV */
+  if (debugging_mode)
+    pop_args ();
+
+  if (subshell == 0)
+    run_unwind_frame ("function_calling");
+
+  funcnest--;
+#if defined (ARRAY_VARS)
+  array_pop (bash_source_a);
+  array_pop (funcname_a);
+  array_pop (bash_lineno_a);
+#endif
+  
+  if (variable_context == 0 || this_shell_function == 0)
+    make_funcname_visible (0);
+
+  return (result);
+}
+
+/* A convenience routine for use by other parts of the shell to execute
+   a particular shell function. */
+int
+execute_shell_function (var, words)
+     SHELL_VAR *var;
+     WORD_LIST *words;
+{
+  int ret;
+  struct fd_bitmap *bitmap;
+
+  bitmap = new_fd_bitmap (FD_BITMAP_DEFAULT_SIZE);
+  begin_unwind_frame ("execute-shell-function");
+  add_unwind_protect (dispose_fd_bitmap, (char *)bitmap);
+      
+  ret = execute_function (var, words, 0, bitmap, 0, 0);
+
+  dispose_fd_bitmap (bitmap);
+  discard_unwind_frame ("execute-shell-function");
+
+  return ret;
+}
+
+/* Execute a shell builtin or function in a subshell environment.  This
+   routine does not return; it only calls exit().  If BUILTIN is non-null,
+   it points to a function to call to execute a shell builtin; otherwise
+   VAR points at the body of a function to execute.  WORDS is the arguments
+   to the command, REDIRECTS specifies redirections to perform before the
+   command is executed. */
+static void
+execute_subshell_builtin_or_function (words, redirects, builtin, var,
+                                     pipe_in, pipe_out, async, fds_to_close,
+                                     flags)
+     WORD_LIST *words;
+     REDIRECT *redirects;
+     sh_builtin_func_t *builtin;
+     SHELL_VAR *var;
+     int pipe_in, pipe_out, async;
+     struct fd_bitmap *fds_to_close;
+     int flags;
+{
+  int result, r;
+#if defined (JOB_CONTROL)
+  int jobs_hack;
+
+  jobs_hack = (builtin == jobs_builtin) &&
+               ((subshell_environment & SUBSHELL_ASYNC) == 0 || pipe_out != NO_PIPE);
+#endif
+
+  /* A subshell is neither a login shell nor interactive. */
+  login_shell = interactive = 0;
+
+  subshell_environment = SUBSHELL_ASYNC;
+
+  maybe_make_export_env ();    /* XXX - is this needed? */
+
+#if defined (JOB_CONTROL)
+  /* Eradicate all traces of job control after we fork the subshell, so
+     all jobs begun by this subshell are in the same process group as
+     the shell itself. */
+
+  /* Allow the output of `jobs' to be piped. */
+  if (jobs_hack)
+    kill_current_pipeline ();
+  else
+    without_job_control ();
+
+  set_sigchld_handler ();
+#endif /* JOB_CONTROL */
+
+  set_sigint_handler ();
+
+  if (fds_to_close)
+    close_fd_bitmap (fds_to_close);
+
+  do_piping (pipe_in, pipe_out);
+
+  if (do_redirections (redirects, RX_ACTIVE) != 0)
+    exit (EXECUTION_FAILURE);
+
+  if (builtin)
+    {
+      /* Give builtins a place to jump back to on failure,
+        so we don't go back up to main(). */
+      result = setjmp (top_level);
+
+      if (result == EXITPROG)
+       exit (last_command_exit_value);
+      else if (result)
+       exit (EXECUTION_FAILURE);
+      else
+       {
+         r = execute_builtin (builtin, words, flags, 1);
+         if (r == EX_USAGE)
+           r = EX_BADUSAGE;
+         exit (r);
+       }
+    }
+  else
+    exit (execute_function (var, words, flags, fds_to_close, async, 1));
+}
+
+/* Execute a builtin or function in the current shell context.  If BUILTIN
+   is non-null, it is the builtin command to execute, otherwise VAR points
+   to the body of a function.  WORDS are the command's arguments, REDIRECTS
+   are the redirections to perform.  FDS_TO_CLOSE is the usual bitmap of
+   file descriptors to close.
+
+   If BUILTIN is exec_builtin, the redirections specified in REDIRECTS are
+   not undone before this function returns. */
+static int
+execute_builtin_or_function (words, builtin, var, redirects,
+                            fds_to_close, flags)
+     WORD_LIST *words;
+     sh_builtin_func_t *builtin;
+     SHELL_VAR *var;
+     REDIRECT *redirects;
+     struct fd_bitmap *fds_to_close;
+     int flags;
+{
+  int result;
+  REDIRECT *saved_undo_list;
+  sh_builtin_func_t *saved_this_shell_builtin;
+
+  if (do_redirections (redirects, RX_ACTIVE|RX_UNDOABLE) != 0)
+    {
+      cleanup_redirects (redirection_undo_list);
+      redirection_undo_list = (REDIRECT *)NULL;
+      dispose_exec_redirects ();
+      return (EX_REDIRFAIL);   /* was EXECUTION_FAILURE */
+    }
+
+  saved_this_shell_builtin = this_shell_builtin;
+  saved_undo_list = redirection_undo_list;
+
+  /* Calling the "exec" builtin changes redirections forever. */
+  if (builtin == exec_builtin)
+    {
+      dispose_redirects (saved_undo_list);
+      saved_undo_list = exec_redirection_undo_list;
+      exec_redirection_undo_list = (REDIRECT *)NULL;
+    }
+  else
+    dispose_exec_redirects ();
+
+  if (saved_undo_list)
+    {
+      begin_unwind_frame ("saved redirects");
+      add_unwind_protect (cleanup_redirects, (char *)saved_undo_list);
+    }
+
+  redirection_undo_list = (REDIRECT *)NULL;
+
+  if (builtin)
+    result = execute_builtin (builtin, words, flags, 0);
+  else
+    result = execute_function (var, words, flags, fds_to_close, 0, 0);
+
+  /* We do this before undoing the effects of any redirections. */
+  if (ferror (stdout))
+    clearerr (stdout);  
+
+  /* If we are executing the `command' builtin, but this_shell_builtin is
+     set to `exec_builtin', we know that we have something like
+     `command exec [redirection]', since otherwise `exec' would have
+     overwritten the shell and we wouldn't get here.  In this case, we
+     want to behave as if the `command' builtin had not been specified
+     and preserve the redirections. */
+  if (builtin == command_builtin && this_shell_builtin == exec_builtin)
+    {
+      if (saved_undo_list)
+       dispose_redirects (saved_undo_list);
+      redirection_undo_list = exec_redirection_undo_list;
+      saved_undo_list = exec_redirection_undo_list = (REDIRECT *)NULL;      
+      discard_unwind_frame ("saved_redirects");
+    }
+
+  if (saved_undo_list)
+    {
+      redirection_undo_list = saved_undo_list;
+      discard_unwind_frame ("saved redirects");
+    }
+
+  if (redirection_undo_list)
+    {
+      cleanup_redirects (redirection_undo_list);
+      redirection_undo_list = (REDIRECT *)NULL;
+    }
+
+  return (result);
+}
+
+void
+setup_async_signals ()
+{
+#if defined (__BEOS__)
+  set_signal_handler (SIGHUP, SIG_IGN);        /* they want csh-like behavior */
+#endif
+
+#if defined (JOB_CONTROL)
+  if (job_control == 0)
+#endif
+    {
+      set_signal_handler (SIGINT, SIG_IGN);
+      set_signal_ignored (SIGINT);
+      set_signal_handler (SIGQUIT, SIG_IGN);
+      set_signal_ignored (SIGQUIT);
+    }
+}
+
+/* Execute a simple command that is hopefully defined in a disk file
+   somewhere.
+
+   1) fork ()
+   2) connect pipes
+   3) look up the command
+   4) do redirections
+   5) execve ()
+   6) If the execve failed, see if the file has executable mode set.
+   If so, and it isn't a directory, then execute its contents as
+   a shell script.
+
+   Note that the filename hashing stuff has to take place up here,
+   in the parent.  This is probably why the Bourne style shells
+   don't handle it, since that would require them to go through
+   this gnarly hair, for no good reason.
+
+   NOTE: callers expect this to fork or exit(). */
+static void
+execute_disk_command (words, redirects, command_line, pipe_in, pipe_out,
+                     async, fds_to_close, cmdflags)
+     WORD_LIST *words;
+     REDIRECT *redirects;
+     char *command_line;
+     int pipe_in, pipe_out, async;
+     struct fd_bitmap *fds_to_close;
+     int cmdflags;
+{
+  char *pathname, *command, **args;
+  int nofork;
+  pid_t pid;
+
+  nofork = (cmdflags & CMD_NO_FORK);  /* Don't fork, just exec, if no pipes */
+  pathname = words->word->word;
+
+#if defined (RESTRICTED_SHELL)
+  command = (char *)NULL;
+  if (restricted && xstrchr (pathname, '/'))
+    {
+      internal_error (_("%s: restricted: cannot specify `/' in command names"),
+                   pathname);
+      last_command_exit_value = EXECUTION_FAILURE;
+
+      /* If we're not going to fork below, we must already be in a child
+         process or a context in which it's safe to call exit(2).  */
+      if (nofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE)
+       exit (last_command_exit_value);
+      else
+       goto parent_return;
+    }
+#endif /* RESTRICTED_SHELL */
+
+  command = search_for_command (pathname);
+
+  if (command)
+    {
+      maybe_make_export_env ();
+      put_command_name_into_env (command);
+    }
+
+  /* We have to make the child before we check for the non-existence
+     of COMMAND, since we want the error messages to be redirected. */
+  /* If we can get away without forking and there are no pipes to deal with,
+     don't bother to fork, just directly exec the command. */
+  if (nofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE)
+    pid = 0;
+  else
+    pid = make_child (savestring (command_line), async);
+
+  if (pid == 0)
+    {
+      int old_interactive;
+
+#if 0
+      /* This has been disabled for the time being. */
+#if !defined (ARG_MAX) || ARG_MAX >= 10240
+      if (posixly_correct == 0)
+       put_gnu_argv_flags_into_env ((long)getpid (), glob_argv_flags);
+#endif
+#endif
+
+      /* Cancel traps, in trap.c. */
+      restore_original_signals ();
+
+      /* restore_original_signals may have undone the work done
+        by make_child to ensure that SIGINT and SIGQUIT are ignored
+        in asynchronous children. */
+      if (async)
+       {
+         if ((cmdflags & CMD_STDIN_REDIR) &&
+               pipe_in == NO_PIPE &&
+               (stdin_redirects (redirects) == 0))
+           async_redirect_stdin ();
+         setup_async_signals ();
+       }
+
+      /* This functionality is now provided by close-on-exec of the
+        file descriptors manipulated by redirection and piping.
+        Some file descriptors still need to be closed in all children
+        because of the way bash does pipes; fds_to_close is a
+        bitmap of all such file descriptors. */
+      if (fds_to_close)
+       close_fd_bitmap (fds_to_close);
+
+      do_piping (pipe_in, pipe_out);
+
+      old_interactive = interactive;
+      if (async)
+       interactive = 0;
+
+      subshell_environment = SUBSHELL_FORK;
+
+      if (redirects && (do_redirections (redirects, RX_ACTIVE) != 0))
+       {
+#if defined (PROCESS_SUBSTITUTION)
+         /* Try to remove named pipes that may have been created as the
+            result of redirections. */
+         unlink_fifo_list ();
+#endif /* PROCESS_SUBSTITUTION */
+         exit (EXECUTION_FAILURE);
+       }
+
+      if (async)
+       interactive = old_interactive;
+
+      if (command == 0)
+       {
+         internal_error (_("%s: command not found"), pathname);
+         exit (EX_NOTFOUND);   /* Posix.2 says the exit status is 127 */
+       }
+
+      /* Execve expects the command name to be in args[0].  So we
+        leave it there, in the same format that the user used to
+        type it in. */
+      args = strvec_from_word_list (words, 0, 0, (int *)NULL);
+      exit (shell_execve (command, args, export_env));
+    }
+  else
+    {
+parent_return:
+      /* Make sure that the pipes are closed in the parent. */
+      close_pipes (pipe_in, pipe_out);
+#if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD)
+      unlink_fifo_list ();
+#endif
+      FREE (command);
+    }
+}
+
+/* CPP defines to decide whether a particular index into the #! line
+   corresponds to a valid interpreter name or argument character, or
+   whitespace.  The MSDOS define is to allow \r to be treated the same
+   as \n. */
+
+#if !defined (MSDOS)
+#  define STRINGCHAR(ind) \
+    (ind < sample_len && !whitespace (sample[ind]) && sample[ind] != '\n')
+#  define WHITECHAR(ind) \
+    (ind < sample_len && whitespace (sample[ind]))
+#else  /* MSDOS */
+#  define STRINGCHAR(ind) \
+    (ind < sample_len && !whitespace (sample[ind]) && sample[ind] != '\n' && sample[ind] != '\r')
+#  define WHITECHAR(ind) \
+    (ind < sample_len && whitespace (sample[ind]))
+#endif /* MSDOS */
+
+static char *
+getinterp (sample, sample_len, endp)
+     char *sample;
+     int sample_len, *endp;
+{
+  register int i;
+  char *execname;
+  int start;
+
+  /* Find the name of the interpreter to exec. */
+  for (i = 2; i < sample_len && whitespace (sample[i]); i++)
+    ;
+
+  for (start = i; STRINGCHAR(i); i++)
+    ;
+
+  execname = substring (sample, start, i);
+
+  if (endp)
+    *endp = i;
+  return execname;
+}
+
+#if !defined (HAVE_HASH_BANG_EXEC)
+/* If the operating system on which we're running does not handle
+   the #! executable format, then help out.  SAMPLE is the text read
+   from the file, SAMPLE_LEN characters.  COMMAND is the name of
+   the script; it and ARGS, the arguments given by the user, will
+   become arguments to the specified interpreter.  ENV is the environment
+   to pass to the interpreter.
+
+   The word immediately following the #! is the interpreter to execute.
+   A single argument to the interpreter is allowed. */
+
+static int
+execute_shell_script (sample, sample_len, command, args, env)
+     char *sample;
+     int sample_len;
+     char *command;
+     char **args, **env;
+{
+  char *execname, *firstarg;
+  int i, start, size_increment, larry;
+
+  /* Find the name of the interpreter to exec. */
+  execname = getinterp (sample, sample_len, &i);
+  size_increment = 1;
+
+  /* Now the argument, if any. */
+  for (firstarg = (char *)NULL, start = i; WHITECHAR(i); i++)
+    ;
+
+  /* If there is more text on the line, then it is an argument for the
+     interpreter. */
+
+  if (STRINGCHAR(i))  
+    {
+      for (start = i; STRINGCHAR(i); i++)
+       ;
+      firstarg = substring ((char *)sample, start, i);
+      size_increment = 2;
+    }
+
+  larry = strvec_len (args) + size_increment;
+  args = strvec_resize (args, larry + 1);
+
+  for (i = larry - 1; i; i--)
+    args[i] = args[i - size_increment];
+
+  args[0] = execname;
+  if (firstarg)
+    {
+      args[1] = firstarg;
+      args[2] = command;
+    }
+  else
+    args[1] = command;
+
+  args[larry] = (char *)NULL;
+
+  return (shell_execve (execname, args, env));
+}
+#undef STRINGCHAR
+#undef WHITECHAR
+
+#endif /* !HAVE_HASH_BANG_EXEC */
+
+static void
+initialize_subshell ()
+{
+#if defined (ALIAS)
+  /* Forget about any aliases that we knew of.  We are in a subshell. */
+  delete_all_aliases ();
+#endif /* ALIAS */
+
+#if defined (HISTORY)
+  /* Forget about the history lines we have read.  This is a non-interactive
+     subshell. */
+  history_lines_this_session = 0;
+#endif
+
+#if defined (JOB_CONTROL)
+  /* Forget about the way job control was working. We are in a subshell. */
+  without_job_control ();
+  set_sigchld_handler ();
+#endif /* JOB_CONTROL */
+
+  /* Reset the values of the shell flags and options. */
+  reset_shell_flags ();
+  reset_shell_options ();
+  reset_shopt_options ();
+
+  /* Zero out builtin_env, since this could be a shell script run from a
+     sourced file with a temporary environment supplied to the `source/.'
+     builtin.  Such variables are not supposed to be exported (empirical
+     testing with sh and ksh).  Just throw it away; don't worry about a
+     memory leak. */
+  if (vc_isbltnenv (shell_variables))
+    shell_variables = shell_variables->down;
+
+  clear_unwind_protect_list (0);
+
+  /* We're no longer inside a shell function. */
+  variable_context = return_catch_flag = 0;
+
+  /* If we're not interactive, close the file descriptor from which we're
+     reading the current shell script. */
+  if (interactive_shell == 0)
+    unset_bash_input (0);
+}
+
+#if defined (HAVE_SETOSTYPE) && defined (_POSIX_SOURCE)
+#  define SETOSTYPE(x) __setostype(x)
+#else
+#  define SETOSTYPE(x)
+#endif
+
+#define READ_SAMPLE_BUF(file, buf, len) \
+  do \
+    { \
+      fd = open(file, O_RDONLY); \
+      if (fd >= 0) \
+       { \
+         len = read (fd, buf, 80); \
+         close (fd); \
+       } \
+      else \
+       len = -1; \
+    } \
+  while (0)
+      
+/* Call execve (), handling interpreting shell scripts, and handling
+   exec failures. */
+int
+shell_execve (command, args, env)
+     char *command;
+     char **args, **env;
+{
+  struct stat finfo;
+  int larray, i, fd;
+  char sample[80];
+  int sample_len;
+
+  SETOSTYPE (0);               /* Some systems use for USG/POSIX semantics */
+  execve (command, args, env);
+  i = errno;                   /* error from execve() */
+  SETOSTYPE (1);
+
+  /* If we get to this point, then start checking out the file.
+     Maybe it is something we can hack ourselves. */
+  if (i != ENOEXEC)
+    {
+      if ((stat (command, &finfo) == 0) && (S_ISDIR (finfo.st_mode)))
+       internal_error (_("%s: is a directory"), command);
+      else if (executable_file (command) == 0)
+       {
+         errno = i;
+         file_error (command);
+       }
+      else
+       {
+         /* The file has the execute bits set, but the kernel refuses to
+            run it for some reason.  See why. */
+#if defined (HAVE_HASH_BANG_EXEC)
+         READ_SAMPLE_BUF (command, sample, sample_len);
+         if (sample_len > 2 && sample[0] == '#' && sample[1] == '!')
+           {
+             char *interp;
+
+             interp = getinterp (sample, sample_len, (int *)NULL);
+             errno = i;
+             sys_error (_("%s: %s: bad interpreter"), command, interp ? interp : "");
+             FREE (interp);
+             return (EX_NOEXEC);
+           }
+#endif
+         errno = i;
+         file_error (command);
+       }
+      return ((i == ENOENT) ? EX_NOTFOUND : EX_NOEXEC);        /* XXX Posix.2 says that exit status is 126 */
+    }
+
+  /* This file is executable.
+     If it begins with #!, then help out people with losing operating
+     systems.  Otherwise, check to see if it is a binary file by seeing
+     if the contents of the first line (or up to 80 characters) are in the
+     ASCII set.  If it's a text file, execute the contents as shell commands,
+     otherwise return 126 (EX_BINARY_FILE). */
+  READ_SAMPLE_BUF (command, sample, sample_len);
+
+  if (sample_len == 0)
+    return (EXECUTION_SUCCESS);
+
+  /* Is this supposed to be an executable script?
+     If so, the format of the line is "#! interpreter [argument]".
+     A single argument is allowed.  The BSD kernel restricts
+     the length of the entire line to 32 characters (32 bytes
+     being the size of the BSD exec header), but we allow 80
+     characters. */
+  if (sample_len > 0)
+    {
+#if !defined (HAVE_HASH_BANG_EXEC)
+      if (sample_len > 2 && sample[0] == '#' && sample[1] == '!')
+       return (execute_shell_script (sample, sample_len, command, args, env));
+      else
+#endif
+      if (check_binary_file (sample, sample_len))
+       {
+         internal_error (_("%s: cannot execute binary file"), command);
+         return (EX_BINARY_FILE);
+       }
+    }
+
+  /* We have committed to attempting to execute the contents of this file
+     as shell commands. */
+
+  initialize_subshell ();
+
+  set_sigint_handler ();
+
+  /* Insert the name of this shell into the argument list. */
+  larray = strvec_len (args) + 1;
+  args = strvec_resize (args, larray + 1);
+
+  for (i = larray - 1; i; i--)
+    args[i] = args[i - 1];
+
+  args[0] = shell_name;
+  args[1] = command;
+  args[larray] = (char *)NULL;
+
+  if (args[0][0] == '-')
+    args[0]++;
+
+#if defined (RESTRICTED_SHELL)
+  if (restricted)
+    change_flag ('r', FLAG_OFF);
+#endif
+
+  if (subshell_argv)
+    {
+      /* Can't free subshell_argv[0]; that is shell_name. */
+      for (i = 1; i < subshell_argc; i++)
+       free (subshell_argv[i]);
+      free (subshell_argv);
+    }
+
+  dispose_command (currently_executing_command);       /* XXX */
+  currently_executing_command = (COMMAND *)NULL;
+
+  subshell_argc = larray;
+  subshell_argv = args;
+  subshell_envp = env;
+
+  unbind_args ();      /* remove the positional parameters */
+
+  longjmp (subshell_top_level, 1);
+  /*NOTREACHED*/
+}
+
+static int
+execute_intern_function (name, function)
+     WORD_DESC *name;
+     COMMAND *function;
+{
+  SHELL_VAR *var;
+
+  if (check_identifier (name, posixly_correct) == 0)
+    {
+      if (posixly_correct && interactive_shell == 0)
+       {
+         last_command_exit_value = EX_USAGE;
+         jump_to_top_level (ERREXIT);
+       }
+      return (EXECUTION_FAILURE);
+    }
+
+  var = find_function (name->word);
+  if (var && (readonly_p (var) || noassign_p (var)))
+    {
+      if (readonly_p (var))
+       internal_error (_("%s: readonly function"), var->name);
+      return (EXECUTION_FAILURE);
+    }
+
+  bind_function (name->word, function);
+  return (EXECUTION_SUCCESS);
+}
+
+#if defined (INCLUDE_UNUSED)
+#if defined (PROCESS_SUBSTITUTION)
+void
+close_all_files ()
+{
+  register int i, fd_table_size;
+
+  fd_table_size = getdtablesize ();
+  if (fd_table_size > 256)     /* clamp to a reasonable value */
+    fd_table_size = 256;
+
+  for (i = 3; i < fd_table_size; i++)
+    close (i);
+}
+#endif /* PROCESS_SUBSTITUTION */
+#endif
+
+static void
+close_pipes (in, out)
+     int in, out;
+{
+  if (in >= 0)
+    close (in);
+  if (out >= 0)
+    close (out);
+}
+
+static void
+dup_error (oldd, newd)
+     int oldd, newd;
+{
+  sys_error (_("cannot duplicate fd %d to fd %d"), oldd, newd);
+}
+
+/* Redirect input and output to be from and to the specified pipes.
+   NO_PIPE and REDIRECT_BOTH are handled correctly. */
+static void
+do_piping (pipe_in, pipe_out)
+     int pipe_in, pipe_out;
+{
+  if (pipe_in != NO_PIPE)
+    {
+      if (dup2 (pipe_in, 0) < 0)
+       dup_error (pipe_in, 0);
+      if (pipe_in > 0)
+       close (pipe_in);
+    }
+  if (pipe_out != NO_PIPE)
+    {
+      if (pipe_out != REDIRECT_BOTH)
+       {
+         if (dup2 (pipe_out, 1) < 0)
+           dup_error (pipe_out, 1);
+         if (pipe_out == 0 || pipe_out > 1)
+           close (pipe_out);
+       }
+      else
+       {
+         if (dup2 (1, 2) < 0)
+           dup_error (1, 2);
+       }
+    }
+}
index 3799580..a015d78 100644 (file)
--- a/externs.h
+++ b/externs.h
@@ -52,6 +52,7 @@ extern void print_cond_command __P((COND_COM *));
 
 /* set -x support */
 extern char *indirection_level_string __P((void));
+extern void xtrace_print_assignment __P((char *, char *, int, int));
 extern void xtrace_print_word_list __P((WORD_LIST *, int));
 extern void xtrace_print_for_command_head __P((FOR_COM *));
 #if defined (SELECT_COMMAND)
diff --git a/externs.h~ b/externs.h~
new file mode 100644 (file)
index 0000000..a015d78
--- /dev/null
@@ -0,0 +1,375 @@
+/* externs.h -- extern function declarations which do not appear in their
+   own header file. */
+
+/* Copyright (C) 1993-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 2, or (at your option) any later
+   version.
+
+   Bash is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or
+   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+   for more details.
+
+   You should have received a copy of the GNU General Public License along
+   with Bash; see the file COPYING.  If not, write to the Free Software
+   Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+
+/* Make sure that this is included *after* config.h! */
+
+#if !defined (_EXTERNS_H_)
+#  define _EXTERNS_H_
+
+#include "stdc.h"
+
+/* Functions from expr.c. */
+extern intmax_t evalexp __P((char *, int *));
+
+/* Functions from print_cmd.c. */
+extern char *make_command_string __P((COMMAND *));
+extern char *named_function_string __P((char *, COMMAND *, int));
+
+extern void print_command __P((COMMAND *));
+extern void print_simple_command __P((SIMPLE_COM *));
+extern void print_word_list __P((WORD_LIST *, char *));
+
+/* debugger support */
+extern void print_for_command_head __P((FOR_COM *));
+#if defined (SELECT_COMMAND)
+extern void print_select_command_head __P((SELECT_COM *));
+#endif
+extern void print_case_command_head __P((CASE_COM *));
+#if defined (DPAREN_ARITHMETIC)
+extern void print_arith_command __P((WORD_LIST *));
+#endif
+#if defined (COND_COMMAND)
+extern void print_cond_command __P((COND_COM *));
+#endif
+
+/* set -x support */
+extern char *indirection_level_string __P((void));
+extern void xtrace_print_assignment __P((char *, char *, int, int));
+extern void xtrace_print_word_list __P((WORD_LIST *, int));
+extern void xtrace_print_for_command_head __P((FOR_COM *));
+#if defined (SELECT_COMMAND)
+extern void xtrace_print_select_command_head __P((SELECT_COM *));
+#endif
+extern void xtrace_print_case_command_head __P((CASE_COM *));
+#if defined (DPAREN_ARITHMETIC)
+extern void xtrace_print_arith_cmd __P((WORD_LIST *));
+#endif
+#if defined (COND_COMMAND)
+extern void xtrace_print_cond_term __P((int, int, WORD_DESC *, char *, char *));
+#endif
+
+/* Functions from shell.c. */
+extern void exit_shell __P((int)) __attribute__((__noreturn__));
+extern void sh_exit __P((int)) __attribute__((__noreturn__));
+extern void disable_priv_mode __P((void));
+extern void unbind_args __P((void));
+
+#if defined (RESTRICTED_SHELL)
+extern int shell_is_restricted __P((char *));
+extern int maybe_make_restricted __P((char *));
+#endif
+
+extern void unset_bash_input __P((int));
+extern void get_current_user_info __P((void));
+
+/* Functions from eval.c. */
+extern int reader_loop __P((void));
+extern int parse_command __P((void));
+extern int read_command __P((void));
+
+/* Functions from braces.c. */
+#if defined (BRACE_EXPANSION)
+extern char **brace_expand __P((char *));
+#endif
+
+/* Miscellaneous functions from parse.y */
+extern int yyparse __P((void));
+extern int return_EOF __P((void));
+extern void reset_parser __P((void));
+extern WORD_LIST *parse_string_to_word_list __P((char *, int, const char *));
+
+extern void free_pushed_string_input __P((void));
+
+extern char *decode_prompt_string __P((char *));
+
+extern int get_current_prompt_level __P((void));
+extern void set_current_prompt_level __P((int));
+
+#if defined (HISTORY)
+extern char *history_delimiting_chars __P((void));
+#endif
+
+/* Declarations for functions defined in locale.c */
+extern void set_default_locale __P((void));
+extern void set_default_locale_vars __P((void));
+extern int set_locale_var __P((char *, char *));
+extern int set_lang __P((char *, char *));
+extern char *get_locale_var __P((char *));
+extern char *localetrans __P((char *, int, int *));
+extern char *mk_msgstr __P((char *, int *));
+extern char *localeexpand __P((char *, int, int, int, int *));
+
+/* Declarations for functions defined in list.c. */
+extern void list_walk __P((GENERIC_LIST *, sh_glist_func_t *));
+extern void wlist_walk __P((WORD_LIST *, sh_icpfunc_t *));
+extern GENERIC_LIST *list_reverse ();
+extern int list_length ();
+extern GENERIC_LIST *list_append ();
+extern GENERIC_LIST *list_remove ();
+
+/* Declarations for functions defined in stringlib.c */
+extern int find_string_in_alist __P((char *, STRING_INT_ALIST *, int));
+extern char *find_token_in_alist __P((int, STRING_INT_ALIST *, int));
+extern int find_index_in_alist __P((char *, STRING_INT_ALIST *, int));
+
+extern char *substring __P((char *, int, int));
+extern char *strsub __P((char *, char *, char *, int));
+extern char *strcreplace __P((char *, int, char *, int));
+extern void strip_leading __P((char *));
+extern void strip_trailing __P((char *, int, int));
+extern void xbcopy __P((char *, char *, int));
+
+/* Functions from version.c. */
+extern char *shell_version_string __P((void));
+extern void show_shell_version __P((int));
+
+/* Functions from the bash library, lib/sh/libsh.a.  These should really
+   go into a separate include file. */
+
+/* declarations for functions defined in lib/sh/clktck.c */
+extern long get_clk_tck __P((void));
+
+/* declarations for functions defined in lib/sh/clock.c */
+extern void clock_t_to_secs ();
+extern void print_clock_t ();
+
+/* Declarations for functions defined in lib/sh/fmtulong.c */
+#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 */
+
+extern char *fmtulong __P((unsigned long int, int, char *, size_t, int));
+
+/* Declarations for functions defined in lib/sh/fmtulong.c */
+#if defined (HAVE_LONG_LONG)
+extern char *fmtullong __P((unsigned long long int, int, char *, size_t, int));
+#endif
+
+/* Declarations for functions defined in lib/sh/fmtumax.c */
+extern char *fmtumax __P((uintmax_t, int, char *, size_t, int));
+
+/* Declarations for functions defined in lib/sh/getcwd.c */
+#if !defined (HAVE_GETCWD)
+extern char *getcwd __P((char *, size_t));
+#endif
+
+/* Declarations for functions defined in lib/sh/itos.c */
+extern char *inttostr __P((intmax_t, char *, size_t));
+extern char *itos __P((intmax_t));
+extern char *uinttostr __P((uintmax_t, char *, size_t));
+extern char *uitos __P((uintmax_t));
+
+/* declarations for functions defined in lib/sh/makepath.c */
+#define MP_DOTILDE     0x01
+#define MP_DOCWD       0x02
+#define MP_RMDOT       0x04
+
+extern char *sh_makepath __P((const char *, const char *, int));
+
+/* declarations for functions defined in lib/sh/netconn.c */
+extern int isnetconn __P((int));
+
+/* declarations for functions defined in lib/sh/netopen.c */
+extern int netopen __P((char *));
+
+/* Declarations for  functions defined in lib/sh/oslib.c */
+
+#if !defined (HAVE_DUP2) || defined (DUP2_BROKEN)
+extern int dup2 __P((int, int));
+#endif
+
+#if !defined (HAVE_GETDTABLESIZE)
+extern int getdtablesize __P((void));
+#endif /* !HAVE_GETDTABLESIZE */
+
+#if !defined (HAVE_GETHOSTNAME)
+extern int gethostname __P((char *, int));
+#endif /* !HAVE_GETHOSTNAME */
+
+extern int getmaxgroups __P((void));
+extern long getmaxchild __P((void));
+
+/* declarations for functions defined in lib/sh/pathcanon.c */
+#define PATH_CHECKDOTDOT       0x0001
+#define PATH_CHECKEXISTS       0x0002
+#define PATH_HARDPATH          0x0004
+#define PATH_NOALLOC           0x0008
+
+extern char *sh_canonpath __P((char *, int));
+
+/* declarations for functions defined in lib/sh/pathphys.c */
+extern char *sh_physpath __P((char *, int));
+extern char *sh_realpath __P((const char *, char *));
+
+/* declarations for functions defined in lib/sh/setlinebuf.c */
+#ifdef NEED_SH_SETLINEBUF_DECL
+extern int sh_setlinebuf __P((FILE *));
+#endif
+
+/* declarations for functions defined in lib/sh/shmatch.c */
+extern int sh_regmatch __P((const char *, const char *, int));
+
+/* defines for flags argument to sh_regmatch. */
+#define SHMAT_SUBEXP           0x001   /* save subexpressions in SH_REMATCH */
+#define SHMAT_PWARN            0x002   /* print a warning message on invalid regexp */
+
+/* declarations for functions defined in lib/sh/shquote.c */
+extern char *sh_single_quote __P((char *));
+extern char *sh_double_quote __P((char *));
+extern char *sh_un_double_quote __P((char *));
+extern char *sh_backslash_quote __P((char *));
+extern char *sh_backslash_quote_for_double_quotes __P((char *));
+extern int sh_contains_shell_metas __P((char *));
+
+/* declarations for functions defined in lib/sh/spell.c */
+extern int spname __P((char *, char *));
+
+/* declarations for functions defined in lib/sh/strcasecmp.c */
+#if !defined (HAVE_STRCASECMP)
+extern int strncasecmp __P((const char *, const char *, int));
+extern int strcasecmp __P((const char *, const char *));
+#endif /* HAVE_STRCASECMP */
+
+/* declarations for functions defined in lib/sh/strerror.c */
+#if !defined (strerror)
+extern char *strerror __P((int));
+#endif
+
+/* declarations for functions defined in lib/sh/strftime.c */
+#if !defined (HAVE_STRFTIME) && defined (NEED_STRFTIME_DECL)
+extern size_t strftime __P((char *, size_t, const char *, const struct tm *));
+#endif
+
+/* declarations for functions defined in lib/sh/strindex.c */
+extern char *strindex __P((const char *, const char *));
+
+/* declarations for functions and structures defined in lib/sh/stringlist.c */
+
+/* This is a general-purpose argv-style array struct. */
+typedef struct _list_of_strings {
+  char **list;
+  int list_size;
+  int list_len;
+} STRINGLIST;
+
+typedef int sh_strlist_map_func_t __P((char *));
+
+extern STRINGLIST *strlist_create __P((int));
+extern STRINGLIST *strlist_resize __P((STRINGLIST *, int));
+extern void strlist_flush __P((STRINGLIST *));
+extern void strlist_dispose __P((STRINGLIST *));
+extern int strlist_remove __P((STRINGLIST *, char *));
+extern STRINGLIST *strlist_copy __P((STRINGLIST *));
+extern STRINGLIST *strlist_merge __P((STRINGLIST *, STRINGLIST *));
+extern STRINGLIST *strlist_append __P((STRINGLIST *, STRINGLIST *));
+extern STRINGLIST *strlist_prefix_suffix __P((STRINGLIST *, char *, char *));
+extern void strlist_print __P((STRINGLIST *, char *));
+extern void strlist_walk __P((STRINGLIST *, sh_strlist_map_func_t *));
+extern void strlist_sort __P((STRINGLIST *));
+
+/* declarations for functions defined in lib/sh/stringvec.c */
+
+extern char **strvec_create __P((int));
+extern char **strvec_resize __P((char **, int));
+extern void strvec_flush __P((char **));
+extern void strvec_dispose __P((char **));
+extern int strvec_remove __P((char **, char *));
+extern int strvec_len __P((char **));
+extern int strvec_search __P((char **, char *));
+extern char **strvec_copy __P((char **));
+extern int strvec_strcmp __P((char **, char **));
+extern void strvec_sort __P((char **));
+
+extern char **strvec_from_word_list __P((WORD_LIST *, int, int, int *));
+extern WORD_LIST *strvec_to_word_list __P((char **, int, int));
+
+/* declarations for functions defined in lib/sh/strtod.c */
+#if !defined (HAVE_STRTOD)
+extern double strtod __P((const char *, char **));
+#endif
+
+/* declarations for functions defined in lib/sh/strtol.c */
+#if !HAVE_DECL_STRTOL
+extern long strtol __P((const char *, char **, int));
+#endif
+
+/* declarations for functions defined in lib/sh/strtoll.c */
+#if defined (HAVE_LONG_LONG) && !HAVE_DECL_STRTOLL
+extern long long strtoll __P((const char *, char **, int));
+#endif
+
+/* declarations for functions defined in lib/sh/strtoul.c */
+#if !HAVE_DECL_STRTOUL
+extern unsigned long strtoul __P((const char *, char **, int));
+#endif
+
+/* declarations for functions defined in lib/sh/strtoull.c */
+#if defined (HAVE_LONG_LONG) && !HAVE_DECL_STRTOULL
+extern unsigned long long strtoull __P((const char *, char **, int));
+#endif
+
+/* declarations for functions defined in lib/sh/strimax.c */
+#if !HAVE_DECL_STRTOIMAX
+extern intmax_t strtoimax __P((const char *, char **, int));
+#endif
+
+/* declarations for functions defined in lib/sh/strumax.c */
+#if !HAVE_DECL_STRTOUMAX
+extern uintmax_t strtoumax __P((const char *, char **, int));
+#endif
+
+/* declarations for functions defined in lib/sh/strtrans.c */
+extern char *ansicstr __P((char *, int, int, int *, int *));
+extern char *ansic_quote __P((char *, int, int *));
+extern int ansic_shouldquote __P((const char *));
+extern char *ansiexpand __P((char *, int, int, int *));
+
+/* declarations for functions defined in lib/sh/timeval.c.  No prototypes
+   so we don't have to count on having a definition of struct timeval in
+   scope when this file is included. */
+extern void timeval_to_secs ();
+extern void print_timeval ();
+
+/* declarations for functions defined in lib/sh/tmpfile.c */
+#define MT_USETMPDIR           0x0001
+#define MT_READWRITE           0x0002
+#define MT_USERANDOM           0x0004
+
+extern char *sh_mktmpname __P((char *, int));
+extern int sh_mktmpfd __P((char *, int, char **));
+/* extern FILE *sh_mktmpfp __P((char *, int, char **)); */
+
+/* declarations for functions defined in lib/sh/xstrchr.c */
+#undef xstrchr
+extern char *xstrchr __P((const char *, int));
+
+/* declarations for functions defined in lib/sh/zread.c */
+extern ssize_t zread __P((int, char *, size_t));
+extern ssize_t zreadintr __P((int, char *, size_t));
+extern ssize_t zreadc __P((int, char *));
+extern void zreset __P((void));
+extern void zsyncfd __P((int));
+
+/* declarations for functions defined in lib/sh/zwrite.c */
+extern int zwrite __P((int, char *, size_t));
+
+#endif /* _EXTERNS_H_ */
index 1435ac5..df4b113 100644 (file)
--- a/general.c
+++ b/general.c
@@ -238,6 +238,22 @@ check_identifier (word, check_word)
     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
diff --git a/general.c~ b/general.c~
new file mode 100644 (file)
index 0000000..74c00f7
--- /dev/null
@@ -0,0 +1,958 @@
+/* general.c -- Stuff that is used by all files. */
+
+/* Copyright (C) 1987-2004 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 2, or (at your option) any later
+   version.
+
+   Bash is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or
+   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+   for more details.
+
+   You should have received a copy of the GNU General Public License along
+   with Bash; see the file COPYING.  If not, write to the Free Software
+   Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+
+#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 <tilde/tilde.h>
+
+#if !defined (errno)
+extern int errno;
+#endif /* !errno */
+
+extern int expand_aliases;
+extern int interrupt_immediately;
+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. */
+char *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;
+    }
+
+  /* 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 == '+')
+    {
+      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)
+     char *string;
+     intmax_t *result;
+{
+  intmax_t value;
+  char *ep;
+
+  if (result)
+    *result = 0;
+
+  errno = 0;
+  value = strtoimax (string, &ep, 10);
+  if (errno)
+    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) || shellquote (*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 && 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);
+         if (string[newi++] != ']')
+           return (0);
+         return ((string[newi] == '=') ? newi : 0);
+       }
+#endif /* ARRAY_VARS */
+
+      /* Variable names in assignment statements may contain only letters,
+        digits, and `_'. */
+      if (legal_variable_char (c) == 0)
+       return (0);
+
+      indx++;
+    }
+  return (0);
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*          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);
+}
+
+/* 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);
+    }
+  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 (ISSPACE (c) == 0 && ISPRINT (c) == 0)
+       return (1);
+    }
+
+  return (0);
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                 Functions to inspect pathnames                  */
+/*                                                                 */
+/* **************************************************************** */
+
+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) && test_eaccess (fn, W_OK) == 0);
+}
+
+
+/* **************************************************************** */
+/*                                                                 */
+/*                 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 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 *)xstrchr (string, '/') != (char *)NULL);
+}
+
+/* Return the `basename' of the pathname in STRING (the stuff after the
+   last '/').  If STRING is not a full pathname, simply return it. */
+char *
+base_pathname (string)
+     char *string;
+{
+  char *p;
+
+  if (absolute_pathname (string) == 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);
+}
+
+/* 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_suffixes;
+
+/* 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;
+
+      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;
+    }
+}
+
+/* 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;
+}
+
+/* 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). */
+char *
+bash_tilde_expand (s, assign_p)
+     const char *s;
+     int assign_p;
+{
+  int old_immed, r;
+  char *ret;
+
+  old_immed = interrupt_immediately;
+  interrupt_immediately = 1;
+  tilde_additional_prefixes = assign_p ? bash_tilde_prefixes : (char **)0;
+  r = (*s == '~') ? unquoted_tilde_word (s) : 1;
+  ret = r ? tilde_expand (s) : savestring (s);
+  interrupt_immediately = old_immed;
+  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;
+}
index 9f39a48..afc7fb0 100644 (file)
--- a/general.h
+++ b/general.h
@@ -277,6 +277,7 @@ extern int all_digits __P((char *));
 extern int legal_number __P((char *, intmax_t *));
 extern int legal_identifier __P((char *));
 extern int check_identifier __P((WORD_DESC *, int));
+extern int legal_alias_name __P((char *, int));
 extern int assignment __P((const char *, int));
 
 extern int sh_unset_nodelay_mode __P((int));
diff --git a/general.h~ b/general.h~
new file mode 100644 (file)
index 0000000..9f39a48
--- /dev/null
@@ -0,0 +1,311 @@
+/* general.h -- defines that everybody likes to use. */
+
+/* Copyright (C) 1993-2004 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 2, or (at your option) any later
+   version.
+
+   Bash is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or
+   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+   for more details.
+
+   You should have received a copy of the GNU General Public License along
+   with Bash; see the file COPYING.  If not, write to the Free Software
+   Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+
+#if !defined (_GENERAL_H_)
+#define _GENERAL_H_
+
+#include "stdc.h"
+
+#include "bashtypes.h"
+
+#if defined (HAVE_SYS_RESOURCE_H) && defined (RLIMTYPE)
+#  if defined (HAVE_SYS_TIME_H)
+#    include <sys/time.h>
+#  endif
+#  include <sys/resource.h>
+#endif
+
+#if defined (HAVE_STRING_H)
+#  include <string.h>
+#else
+#  include <strings.h>
+#endif /* !HAVE_STRING_H */
+
+#if defined (HAVE_LIMITS_H)
+#  include <limits.h>
+#endif
+
+#include "xmalloc.h"
+
+/* NULL pointer type. */
+#if !defined (NULL)
+#  if defined (__STDC__)
+#    define NULL ((void *) 0)
+#  else
+#    define NULL 0x0
+#  endif /* !__STDC__ */
+#endif /* !NULL */
+
+/* Hardly used anymore */
+#define pointer_to_int(x)      (int)((char *)x - (char *)0)
+
+#if defined (alpha) && defined (__GNUC__) && !defined (strchr) && !defined (__STDC__)
+extern char *strchr (), *strrchr ();
+#endif
+
+#if !defined (strcpy) && (defined (HAVE_DECL_STRCPY) && !HAVE_DECL_STRCPY)
+extern char *strcpy __P((char *, const char *));
+#endif
+
+#if !defined (savestring)
+#  define savestring(x) (char *)strcpy (xmalloc (1 + strlen (x)), (x))
+#endif
+
+#ifndef member
+#  define member(c, s) ((c) ? ((char *)xstrchr ((s), (c)) != (char *)NULL) : 0)
+#endif
+
+#ifndef whitespace
+#define whitespace(c) (((c) == ' ') || ((c) == '\t'))
+#endif
+
+#ifndef CHAR_MAX
+#  ifdef __CHAR_UNSIGNED__
+#    define CHAR_MAX   0xff
+#  else
+#    define CHAR_MAX   0x7f
+#  endif
+#endif
+
+#ifndef CHAR_BIT
+#  define CHAR_BIT 8
+#endif
+
+/* Nonzero if the integer type T is signed.  */
+#define TYPE_SIGNED(t) (! ((t) 0 < (t) -1))
+
+/* Bound on length of the string representing an integer value of type T.
+   Subtract one for the sign bit if T is signed;
+   302 / 1000 is log10 (2) rounded up;
+   add one for integer division truncation;
+   add one more for a minus sign if t is signed.  */
+#define INT_STRLEN_BOUND(t) \
+  ((sizeof (t) * CHAR_BIT - TYPE_SIGNED (t)) * 302 / 1000 \
+   + 1 + TYPE_SIGNED (t))
+
+
+/* Define exactly what a legal shell identifier consists of. */
+#define legal_variable_starter(c) (ISALPHA(c) || (c == '_'))
+#define legal_variable_char(c) (ISALNUM(c) || c == '_')
+
+/* Definitions used in subst.c and by the `read' builtin for field
+   splitting. */
+#define spctabnl(c)    ((c) == ' ' || (c) == '\t' || (c) == '\n')
+
+/* All structs which contain a `next' field should have that field
+   as the first field in the struct.  This means that functions
+   can be written to handle the general case for linked lists. */
+typedef struct g_list {
+  struct g_list *next;
+} GENERIC_LIST;
+
+/* Here is a generic structure for associating character strings
+   with integers.  It is used in the parser for shell tokenization. */
+typedef struct {
+  char *word;
+  int token;
+} STRING_INT_ALIST;
+
+/* A macro to avoid making an uneccessary function call. */
+#define REVERSE_LIST(list, type) \
+  ((list && list->next) ? (type)list_reverse ((GENERIC_LIST *)list) \
+                       : (type)(list))
+
+#if __GNUC__ > 1
+#  define FASTCOPY(s, d, n)  __builtin_memcpy (d, s, n)
+#else /* !__GNUC__ */
+#  if !defined (HAVE_BCOPY)
+#    if !defined (HAVE_MEMMOVE)
+#      define FASTCOPY(s, d, n)  memcpy (d, s, n)
+#    else
+#      define FASTCOPY(s, d, n)  memmove (d, s, n)
+#    endif /* !HAVE_MEMMOVE */
+#  else /* HAVE_BCOPY */
+#    define FASTCOPY(s, d, n)  bcopy (s, d, n)
+#  endif /* HAVE_BCOPY */
+#endif /* !__GNUC__ */
+
+/* String comparisons that possibly save a function call each. */
+#define STREQ(a, b) ((a)[0] == (b)[0] && strcmp(a, b) == 0)
+#define STREQN(a, b, n) ((n == 0) ? (1) \
+                                 : ((a)[0] == (b)[0] && strncmp(a, b, n) == 0))
+
+/* More convenience definitions that possibly save system or libc calls. */
+#define STRLEN(s) (((s) && (s)[0]) ? ((s)[1] ? ((s)[2] ? strlen(s) : 2) : 1) : 0)
+#define FREE(s)  do { if (s) free (s); } while (0)
+#define MEMBER(c, s) (((c) && c == (s)[0] && !(s)[1]) || (member(c, s)))
+
+/* A fairly hairy macro to check whether an allocated string has more room,
+   and to resize it using xrealloc if it does not.
+   STR is the string (char *)
+   CIND is the current index into the string (int)
+   ROOM is the amount of additional room we need in the string (int)
+   CSIZE is the currently-allocated size of STR (int)
+   SINCR is how much to increment CSIZE before calling xrealloc (int) */
+
+#define RESIZE_MALLOCED_BUFFER(str, cind, room, csize, sincr) \
+  do { \
+    if ((cind) + (room) >= csize) \
+      { \
+       while ((cind) + (room) >= csize) \
+         csize += (sincr); \
+       str = xrealloc (str, csize); \
+      } \
+  } while (0)
+
+/* Function pointers can be declared as (Function *)foo. */
+#if !defined (_FUNCTION_DEF)
+#  define _FUNCTION_DEF
+typedef int Function ();
+typedef void VFunction ();
+typedef char *CPFunction ();           /* no longer used */
+typedef char **CPPFunction ();         /* no longer used */
+#endif /* _FUNCTION_DEF */
+
+#ifndef SH_FUNCTION_TYPEDEF
+#  define SH_FUNCTION_TYPEDEF
+
+/* Shell function typedefs with prototypes */
+/* `Generic' function pointer typedefs */
+
+typedef int sh_intfunc_t __P((int));
+typedef int sh_ivoidfunc_t __P((void));
+typedef int sh_icpfunc_t __P((char *));
+typedef int sh_icppfunc_t __P((char **));
+typedef int sh_iptrfunc_t __P((PTR_T));
+
+typedef void sh_voidfunc_t __P((void));
+typedef void sh_vintfunc_t __P((int));
+typedef void sh_vcpfunc_t __P((char *));
+typedef void sh_vcppfunc_t __P((char **));
+typedef void sh_vptrfunc_t __P((PTR_T));
+
+typedef int sh_wdesc_func_t __P((WORD_DESC *));
+typedef int sh_wlist_func_t __P((WORD_LIST *));
+
+typedef int sh_glist_func_t __P((GENERIC_LIST *));
+
+typedef char *sh_string_func_t __P((char *));  /* like savestring, et al. */
+
+typedef int sh_msg_func_t __P((const char *, ...));    /* printf(3)-like */
+typedef void sh_vmsg_func_t __P((const char *, ...));  /* printf(3)-like */
+
+/* Specific function pointer typedefs.  Most of these could be done
+   with #defines. */
+typedef void sh_sv_func_t __P((char *));       /* sh_vcpfunc_t */
+typedef void sh_free_func_t __P((PTR_T));      /* sh_vptrfunc_t */
+typedef void sh_resetsig_func_t __P((int));    /* sh_vintfunc_t */
+
+typedef int sh_ignore_func_t __P((const char *));      /* sh_icpfunc_t */
+
+typedef int sh_assign_func_t __P((const char *));      /* sh_icpfunc_t */
+
+typedef int sh_builtin_func_t __P((WORD_LIST *)); /* sh_wlist_func_t */
+
+#endif /* SH_FUNCTION_TYPEDEF */
+
+#define NOW    ((time_t) time ((time_t *) 0))
+
+/* Some defines for calling file status functions. */
+#define FS_EXISTS        0x1
+#define FS_EXECABLE      0x2
+#define FS_EXEC_PREFERRED 0x4
+#define FS_EXEC_ONLY     0x8
+#define FS_DIRECTORY     0x10
+#define FS_NODIRS        0x20
+
+/* Default maximum for move_to_high_fd */
+#define HIGH_FD_MAX    256
+
+/* The type of function passed as the fourth argument to qsort(3). */
+#ifdef __STDC__
+typedef int QSFUNC (const void *, const void *);
+#else
+typedef int QSFUNC ();
+#endif 
+
+/* Some useful definitions for Unix pathnames.  Argument convention:
+   x == string, c == character */
+
+#if !defined (__CYGWIN__)
+#  define ABSPATH(x)   ((x)[0] == '/')
+#  define RELPATH(x)   ((x)[0] != '/')
+#else /* __CYGWIN__ */
+#  define ABSPATH(x)   (((x)[0] && ISALPHA((unsigned char)(x)[0]) && (x)[1] == ':' && (x)[2] == '/') || (x)[0] == '/')
+#  define RELPATH(x)   (!(x)[0] || ((x)[1] != ':' && (x)[0] != '/'))
+#endif /* __CYGWIN__ */
+
+#define ROOTEDPATH(x)  (ABSPATH(x))
+
+#define DIRSEP '/'
+#define ISDIRSEP(c)    ((c) == '/')
+#define PATHSEP(c)     (ISDIRSEP(c) || (c) == 0)
+
+#if 0
+/* Declarations for functions defined in xmalloc.c */
+extern PTR_T xmalloc __P((size_t));
+extern PTR_T xrealloc __P((void *, size_t));
+extern void xfree __P((void *));
+#endif
+
+/* Declarations for functions defined in general.c */
+extern void posix_initialize __P((int));
+
+#if defined (RLIMTYPE)
+extern RLIMTYPE string_to_rlimtype __P((char *));
+extern void print_rlimtype __P((RLIMTYPE, int));
+#endif
+
+extern int all_digits __P((char *));
+extern int legal_number __P((char *, intmax_t *));
+extern int legal_identifier __P((char *));
+extern int check_identifier __P((WORD_DESC *, int));
+extern int assignment __P((const char *, int));
+
+extern int sh_unset_nodelay_mode __P((int));
+extern int sh_validfd __P((int));
+extern void check_dev_tty __P((void));
+extern int move_to_high_fd __P((int, int, int));
+extern int check_binary_file __P((char *, int));
+
+#ifdef _POSIXSTAT_H_
+extern int same_file __P((char *, char *, struct stat *, struct stat *));
+#endif
+
+extern int file_isdir __P((char  *));
+extern int file_iswdir __P((char  *));
+
+extern char *make_absolute __P((char *, char *));
+extern int absolute_pathname __P((const char *));
+extern int absolute_program __P((const char *));
+extern char *base_pathname __P((char *));
+extern char *full_pathname __P((char *));
+extern char *polite_directory_format __P((char *));
+
+extern char *extract_colon_unit __P((char *, int *));
+
+extern void tilde_initialize __P((void));
+extern char *bash_tilde_expand __P((const char *, int));
+
+extern int group_member __P((gid_t));
+extern char **get_group_list __P((int *));
+extern int *get_group_array __P((int *));
+
+#endif /* _GENERAL_H_ */
index 492f6af..a737780 100644 (file)
@@ -1,6 +1,6 @@
 /* shmbutil.h -- utility functions for multibyte characters. */
 
-/* Copyright (C) 2002 Free Software Foundation, Inc.
+/* Copyright (C) 2002-2004 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
 
 #include "stdc.h"
 
-/************************************************/
-/* check multibyte capability for I18N code     */
-/************************************************/
-
-/* For platforms which support the ISO C amendement 1 functionality we
-   support user defined character classes.  */
-   /* Solaris 2.5 has a bug: <wchar.h> must be included before <wctype.h>.  */
-#if defined (HAVE_WCTYPE_H) && defined (HAVE_WCHAR_H)
-#  include <wchar.h>
-#  include <wctype.h>
-#  if defined (HAVE_MBSRTOWCS) /* system is supposed to support XPG5 */
-#    define HANDLE_MULTIBYTE      1
-#  endif
-#endif /* HAVE_WCTYPE_H && HAVE_WCHAR_H */
-
-/* Some systems, like BeOS, have multibyte encodings but lack mbstate_t.  */
-#if HANDLE_MULTIBYTE && !defined (HAVE_MBSTATE_T)
-#  define wcsrtombs(dest, src, len, ps) (wcsrtombs) (dest, src, len, 0)
-#  define mbsrtowcs(dest, src, len, ps) (mbsrtowcs) (dest, src, len, 0)
-#  define wcrtomb(s, wc, ps) (wcrtomb) (s, wc, 0)
-#  define mbrtowc(pwc, s, n, ps) (mbrtowc) (pwc, s, n, 0)
-#  define mbrlen(s, n, ps) (mbrlen) (s, n, 0)
-#  define mbstate_t int
-#endif /* HANDLE_MULTIBYTE && !HAVE_MBSTATE_T */
-
-/* Make sure MB_LEN_MAX is at least 16 on systems that claim to be able to
-   handle multibyte chars (some systems define MB_LEN_MAX as 1) */
-#ifdef HANDLE_MULTIBYTE
-#  include <limits.h>
-#  if defined(MB_LEN_MAX) && (MB_LEN_MAX < 16)
-#    undef MB_LEN_MAX
-#  endif
-#  if !defined (MB_LEN_MAX)
-#    define MB_LEN_MAX 16
-#  endif
-#endif /* HANDLE_MULTIBYTE */
-
-/************************************************/
-/* end of multibyte capability checks for I18N  */
-/************************************************/
+/* Include config.h for HANDLE_MULTIBYTE */
+#include <config.h>
 
 #if defined (HANDLE_MULTIBYTE)
 
diff --git a/include/shmbutil.h.save1 b/include/shmbutil.h.save1
new file mode 100644 (file)
index 0000000..492f6af
--- /dev/null
@@ -0,0 +1,470 @@
+/* shmbutil.h -- utility functions for multibyte characters. */
+
+/* Copyright (C) 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 2, or (at your option) any later
+   version.
+
+   Bash is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or
+   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+   for more details.
+
+   You should have received a copy of the GNU General Public License along
+   with Bash; see the file COPYING.  If not, write to the Free Software
+   Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+                                 
+#if !defined (_SH_MBUTIL_H_)
+#define _SH_MBUTIL_H_
+
+#include "stdc.h"
+
+/************************************************/
+/* check multibyte capability for I18N code     */
+/************************************************/
+
+/* For platforms which support the ISO C amendement 1 functionality we
+   support user defined character classes.  */
+   /* Solaris 2.5 has a bug: <wchar.h> must be included before <wctype.h>.  */
+#if defined (HAVE_WCTYPE_H) && defined (HAVE_WCHAR_H)
+#  include <wchar.h>
+#  include <wctype.h>
+#  if defined (HAVE_MBSRTOWCS) /* system is supposed to support XPG5 */
+#    define HANDLE_MULTIBYTE      1
+#  endif
+#endif /* HAVE_WCTYPE_H && HAVE_WCHAR_H */
+
+/* Some systems, like BeOS, have multibyte encodings but lack mbstate_t.  */
+#if HANDLE_MULTIBYTE && !defined (HAVE_MBSTATE_T)
+#  define wcsrtombs(dest, src, len, ps) (wcsrtombs) (dest, src, len, 0)
+#  define mbsrtowcs(dest, src, len, ps) (mbsrtowcs) (dest, src, len, 0)
+#  define wcrtomb(s, wc, ps) (wcrtomb) (s, wc, 0)
+#  define mbrtowc(pwc, s, n, ps) (mbrtowc) (pwc, s, n, 0)
+#  define mbrlen(s, n, ps) (mbrlen) (s, n, 0)
+#  define mbstate_t int
+#endif /* HANDLE_MULTIBYTE && !HAVE_MBSTATE_T */
+
+/* Make sure MB_LEN_MAX is at least 16 on systems that claim to be able to
+   handle multibyte chars (some systems define MB_LEN_MAX as 1) */
+#ifdef HANDLE_MULTIBYTE
+#  include <limits.h>
+#  if defined(MB_LEN_MAX) && (MB_LEN_MAX < 16)
+#    undef MB_LEN_MAX
+#  endif
+#  if !defined (MB_LEN_MAX)
+#    define MB_LEN_MAX 16
+#  endif
+#endif /* HANDLE_MULTIBYTE */
+
+/************************************************/
+/* end of multibyte capability checks for I18N  */
+/************************************************/
+
+#if defined (HANDLE_MULTIBYTE)
+
+extern size_t xmbsrtowcs __P((wchar_t *, const char **, size_t, mbstate_t *));
+extern size_t xdupmbstowcs __P((wchar_t **, char ***, const char *));
+
+extern char *xstrchr __P((const char *, int));
+
+#ifndef MB_INVALIDCH
+#define MB_INVALIDCH(x)                ((x) == (size_t)-1 || (x) == (size_t)-2)
+#define MB_NULLWCH(x)          ((x) == 0)
+#endif
+
+#else /* !HANDLE_MULTIBYTE */
+
+#undef MB_LEN_MAX
+#undef MB_CUR_MAX
+
+#define MB_LEN_MAX     1
+#define MB_CUR_MAX     1
+
+#undef xstrchr
+#define xstrchr(s, c)  strchr(s, c)
+
+#ifndef MB_INVALIDCH
+#define MB_INVALIDCH(x)                (0)
+#define MB_NULLWCH(x)          (0)
+#endif
+
+#endif /* !HANDLE_MULTIBYTE */
+
+/* Declare and initialize a multibyte state.  Call must be terminated
+   with `;'. */
+#if defined (HANDLE_MULTIBYTE)
+#  define DECLARE_MBSTATE \
+       mbstate_t state; \
+       memset (&state, '\0', sizeof (mbstate_t))
+#else
+#  define DECLARE_MBSTATE
+#endif  /* !HANDLE_MULTIBYTE */
+
+/* Initialize or reinitialize a multibyte state named `state'.  Call must be
+   terminated with `;'. */
+#if defined (HANDLE_MULTIBYTE)
+#  define INITIALIZE_MBSTATE memset (&state, '\0', sizeof (mbstate_t))
+#else
+#  define INITIALIZE_MBSTATE
+#endif  /* !HANDLE_MULTIBYTE */
+
+/* Advance one (possibly multi-byte) character in string _STR of length
+   _STRSIZE, starting at index _I.  STATE must have already been declared. */
+#if defined (HANDLE_MULTIBYTE)
+#  define ADVANCE_CHAR(_str, _strsize, _i) \
+    do \
+      { \
+       if (MB_CUR_MAX > 1) \
+         { \
+           mbstate_t state_bak; \
+           size_t mblength; \
+\
+           state_bak = state; \
+           mblength = mbrlen ((_str) + (_i), (_strsize) - (_i), &state); \
+\
+           if (mblength == (size_t)-2 || mblength == (size_t)-1) \
+             { \
+               state = state_bak; \
+               (_i)++; \
+             } \
+           else if (mblength == 0) \
+             (_i)++; \
+           else \
+             (_i) += mblength; \
+         } \
+       else \
+         (_i)++; \
+      } \
+    while (0)
+#else
+#  define ADVANCE_CHAR(_str, _strsize, _i)     (_i)++
+#endif  /* !HANDLE_MULTIBYTE */
+
+/* Advance one (possibly multibyte) character in the string _STR of length
+   _STRSIZE.
+   SPECIAL:  assume that _STR will be incremented by 1 after this call. */
+#if defined (HANDLE_MULTIBYTE)
+#  define ADVANCE_CHAR_P(_str, _strsize) \
+    do \
+      { \
+       if (MB_CUR_MAX > 1) \
+         { \
+           mbstate_t state_bak; \
+           size_t mblength; \
+\
+           state_bak = state; \
+           mblength = mbrlen ((_str), (_strsize), &state); \
+\
+           if (mblength == (size_t)-2 || mblength == (size_t)-1) \
+             { \
+               state = state_bak; \
+               mblength = 1; \
+             } \
+           else \
+             (_str) += (mblength < 1) ? 0 : (mblength - 1); \
+         } \
+      } \
+    while (0)
+#else
+#  define ADVANCE_CHAR_P(_str, _strsize)
+#endif  /* !HANDLE_MULTIBYTE */
+
+/* Back up one (possibly multi-byte) character in string _STR of length
+   _STRSIZE, starting at index _I.  STATE must have already been declared. */
+#if defined (HANDLE_MULTIBYTE)
+#  define BACKUP_CHAR(_str, _strsize, _i) \
+    do \
+      { \
+       if (MB_CUR_MAX > 1) \
+         { \
+           mbstate_t state_bak; \
+           size_t mblength; \
+           int _x, _p; /* _x == temp index into string, _p == prev index */ \
+\
+           _x = _p = 0; \
+           while (_x < (_i)) \
+             { \
+               state_bak = state; \
+               mblength = mbrlen ((_str) + (_x), (_strsize) - (_x), &state); \
+\
+               if (mblength == (size_t)-2 || mblength == (size_t)-1) \
+                 { \
+                   state = state_bak; \
+                   _x++; \
+                 } \
+               else if (mblength == 0) \
+                 _x++; \
+               else \
+                 { \
+                   _p = _x; /* _p == start of prev mbchar */ \
+                   _x += mblength; \
+                 } \
+             } \
+           (_i) = _p; \
+         } \
+       else \
+         (_i)--; \
+      } \
+    while (0)
+#else
+#  define BACKUP_CHAR(_str, _strsize, _i)      (_i)--
+#endif  /* !HANDLE_MULTIBYTE */
+
+/* Back up one (possibly multibyte) character in the string _BASE of length
+   _STRSIZE starting at _STR (_BASE <= _STR <= (_BASE + _STRSIZE) ).
+   SPECIAL: DO NOT assume that _STR will be decremented by 1 after this call. */
+#if defined (HANDLE_MULTIBYTE)
+#  define BACKUP_CHAR_P(_base, _strsize, _str) \
+    do \
+      { \
+       if (MB_CUR_MAX > 1) \
+         { \
+           mbstate_t state_bak; \
+           size_t mblength; \
+           char *_x, _p; /* _x == temp pointer into string, _p == prev pointer */ \
+\
+           _x = _p = _base; \
+           while (_x < (_str)) \
+             { \
+               state_bak = state; \
+               mblength = mbrlen (_x, (_strsize) - _x, &state); \
+\
+               if (mblength == (size_t)-2 || mblength == (size_t)-1) \
+                 { \
+                   state = state_bak; \
+                   _x++; \
+                 } \
+               else if (mblength == 0) \
+                 _x++; \
+               else \
+                 { \
+                   _p = _x; /* _p == start of prev mbchar */ \
+                   _x += mblength; \
+                 } \
+             } \
+           (_str) = _p; \
+         } \
+       else \
+         (_str)--; \
+      } \
+    while (0)
+#else
+#  define BACKUP_CHAR_P(_base, _strsize, _str) (_str)--
+#endif  /* !HANDLE_MULTIBYTE */
+
+/* Copy a single character from the string _SRC to the string _DST.
+   _SRCEND is a pointer to the end of _SRC. */
+#if defined (HANDLE_MULTIBYTE)
+#  define COPY_CHAR_P(_dst, _src, _srcend) \
+    do \
+      { \
+       if (MB_CUR_MAX > 1) \
+         { \
+           mbstate_t state_bak; \
+           size_t mblength; \
+           int _k; \
+\
+           state_bak = state; \
+           mblength = mbrlen ((_src), (_srcend) - (_src), &state); \
+           if (mblength == (size_t)-2 || mblength == (size_t)-1) \
+             { \
+               state = state_bak; \
+               mblength = 1; \
+             } \
+           else \
+             mblength = (mblength < 1) ? 1 : mblength; \
+\
+           for (_k = 0; _k < mblength; _k++) \
+             *(_dst)++ = *(_src)++; \
+         } \
+       else \
+         *(_dst)++ = *(_src)++; \
+      } \
+    while (0)
+#else
+#  define COPY_CHAR_P(_dst, _src, _srcend)     *(_dst)++ = *(_src)++
+#endif  /* !HANDLE_MULTIBYTE */
+
+/* Copy a single character from the string _SRC at index _SI to the string
+   _DST at index _DI.  _SRCEND is a pointer to the end of _SRC. */
+#if defined (HANDLE_MULTIBYTE)
+#  define COPY_CHAR_I(_dst, _di, _src, _srcend, _si) \
+    do \
+      { \
+       if (MB_CUR_MAX > 1) \
+         { \
+           mbstate_t state_bak; \
+           size_t mblength; \
+           int _k; \
+\
+           state_bak = state; \
+           mblength = mbrlen ((_src) + (_si), (_srcend) - ((_src)+(_si)), &state); \
+           if (mblength == (size_t)-2 || mblength == (size_t)-1) \
+             { \
+               state = state_bak; \
+               mblength = 1; \
+             } \
+           else \
+             mblength = (mblength < 1) ? 1 : mblength; \
+\
+           for (_k = 0; _k < mblength; _k++) \
+             _dst[_di++] = _src[_si++]; \
+         } \
+       else \
+         _dst[_di++] = _src[_si++]; \
+      } \
+    while (0)
+#else
+#  define COPY_CHAR_I(_dst, _di, _src, _srcend, _si)   _dst[_di++] = _src[_si++]
+#endif  /* !HANDLE_MULTIBYTE */
+
+/****************************************************************
+ *                                                             *
+ * The following are only guaranteed to work in subst.c                *
+ *                                                             *
+ ****************************************************************/
+
+#if defined (HANDLE_MULTIBYTE)
+#  define SCOPY_CHAR_I(_dst, _escchar, _sc, _src, _si, _slen) \
+    do \
+      { \
+       if (MB_CUR_MAX > 1) \
+         { \
+           mbstate_t state_bak; \
+           size_t mblength; \
+           int _i; \
+\
+           state_bak = state; \
+           mblength = mbrlen ((_src) + (_si), (_slen) - (_si), &state); \
+           if (mblength == (size_t)-2 || mblength == (size_t)-1) \
+             { \
+               state = state_bak; \
+               mblength = 1; \
+             } \
+           else \
+             mblength = (mblength < 1) ? 1 : mblength; \
+\
+           temp = xmalloc (mblength + 2); \
+           temp[0] = _escchar; \
+           for (_i = 0; _i < mblength; _i++) \
+             temp[_i + 1] = _src[_si++]; \
+           temp[mblength + 1] = '\0'; \
+\
+           goto add_string; \
+         } \
+       else \
+         { \
+           _dst[0] = _escchar; \
+           _dst[1] = _sc; \
+         } \
+      } \
+    while (0)
+#else
+#  define SCOPY_CHAR_I(_dst, _escchar, _sc, _src, _si, _slen) \
+    _dst[0] = _escchar; \
+    _dst[1] = _sc
+#endif  /* !HANDLE_MULTIBYTE */
+
+#if defined (HANDLE_MULTIBYTE)
+#  define SCOPY_CHAR_M(_dst, _src, _srcend, _si) \
+    do \
+      { \
+       if (MB_CUR_MAX > 1) \
+         { \
+           mbstate_t state_bak; \
+           size_t mblength; \
+\
+           state_bak = state; \
+           mblength = mbrlen ((_src) + (_si), (_srcend) - ((_src) + (_si)), &state); \
+           if (mblength == (size_t)-2 || mblength == (size_t)-1) \
+             { \
+               state = state_bak; \
+               mblength = 1; \
+             } \
+           else \
+             mblength = (mblength < 1) ? 1 : mblength; \
+\
+           FASTCOPY(((_src) + (_si)), (_dst), mblength); \
+\
+           (_dst) += mblength; \
+           (_si) += mblength; \
+         } \
+       else \
+         { \
+           *(_dst)++ = _src[(_si)]; \
+           (_si)++; \
+         } \
+      } \
+    while (0)
+#else
+#  define SCOPY_CHAR_M(_dst, _src, _srcend, _si) \
+       *(_dst)++ = _src[(_si)]; \
+       (_si)++
+#endif  /* !HANDLE_MULTIBYTE */
+
+#if HANDLE_MULTIBYTE
+#  define SADD_MBCHAR(_dst, _src, _si, _srcsize) \
+    do \
+      { \
+       if (MB_CUR_MAX > 1) \
+         { \
+           int i; \
+           mbstate_t state_bak; \
+           size_t mblength; \
+\
+           state_bak = state; \
+           mblength = mbrlen ((_src) + (_si), (_srcsize) - (_si), &state); \
+           if (mblength == (size_t)-1 || mblength == (size_t)-2) \
+             { \
+               state = state_bak; \
+               mblength = 1; \
+             } \
+           if (mblength < 1) \
+             mblength = 1; \
+\
+           _dst = (char *)xmalloc (mblength + 1); \
+           for (i = 0; i < mblength; i++) \
+             (_dst)[i] = (_src)[(_si)++]; \
+           (_dst)[mblength] = '\0'; \
+\
+           goto add_string; \
+         } \
+      } \
+    while (0)
+
+#else
+#  define SADD_MBCHAR(_dst, _src, _si, _srcsize)
+#endif
+
+/* Watch out when using this -- it's just straight textual subsitution */
+#if defined (HANDLE_MULTIBYTE)
+#  define SADD_MBQCHAR_BODY(_dst, _src, _si, _srcsize) \
+\
+           int i; \
+           mbstate_t state_bak; \
+           size_t mblength; \
+\
+           state_bak = state; \
+           mblength = mbrlen ((_src) + (_si), (_srcsize) - (_si), &state); \
+           if (mblength == (size_t)-1 || mblength == (size_t)-2) \
+             { \
+               state = state_bak; \
+               mblength = 1; \
+             } \
+           if (mblength < 1) \
+             mblength = 1; \
+\
+           (_dst) = (char *)xmalloc (mblength + 2); \
+           (_dst)[0] = CTLESC; \
+           for (i = 0; i < mblength; i++) \
+             (_dst)[i+1] = (_src)[(_si)++]; \
+           (_dst)[mblength+1] = '\0'; \
+\
+           goto add_string
+
+#endif /* HANDLE_MULTIBYTE */
+#endif /* _SH_MBUTIL_H_ */
index 30d310e..492f6af 100644 (file)
@@ -67,6 +67,7 @@
 #if defined (HANDLE_MULTIBYTE)
 
 extern size_t xmbsrtowcs __P((wchar_t *, const char **, size_t, mbstate_t *));
+extern size_t xdupmbstowcs __P((wchar_t **, char ***, const char *));
 
 extern char *xstrchr __P((const char *, int));
 
diff --git a/jobs.c b/jobs.c
index eeb19ba..3950b93 100644 (file)
--- a/jobs.c
+++ b/jobs.c
@@ -3433,6 +3433,12 @@ set_job_control (arg)
 
   old = job_control;
   job_control = arg;
+
+  /* If we're turning on job control, reset pipeline_pgrp so make_child will
+     put new child processes into the right pgrp */
+  if (job_control != old && job_control)
+    pipeline_pgrp = 0;
+
   return (old);
 }
 
diff --git a/jobs.c~ b/jobs.c~
new file mode 100644 (file)
index 0000000..b4e06c7
--- /dev/null
+++ b/jobs.c~
@@ -0,0 +1,3535 @@
+/* The thing that makes children, remembers them, and contains wait loops. */
+
+/* This file works with both POSIX and BSD systems.  It implements job
+   control. */
+
+/* Copyright (C) 1989-2003 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 2, or (at your option) any later
+   version.
+
+   Bash is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or
+   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+   for more details.
+
+   You should have received a copy of the GNU General Public License along
+   with Bash; see the file COPYING.  If not, write to the Free Software
+   Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+
+#include "config.h"
+
+#include "bashtypes.h"
+#include "trap.h"
+#include <stdio.h>
+#include <signal.h>
+#include <errno.h>
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#include "posixtime.h"
+
+#if defined (HAVE_SYS_RESOURCE_H) && defined (HAVE_WAIT3) && !defined (_POSIX_VERSION) && !defined (RLIMTYPE)
+#  include <sys/resource.h>
+#endif /* !_POSIX_VERSION && HAVE_SYS_RESOURCE_H && HAVE_WAIT3 && !RLIMTYPE */
+
+#if defined (HAVE_SYS_FILE_H)
+#  include <sys/file.h>
+#endif
+
+#include "filecntl.h"
+#include <sys/ioctl.h>
+#include <sys/param.h>
+
+#if defined (BUFFERED_INPUT)
+#  include "input.h"
+#endif
+
+/* Need to include this up here for *_TTY_DRIVER definitions. */
+#include "shtty.h"
+
+/* Define this if your output is getting swallowed.  It's a no-op on
+   machines with the termio or termios tty drivers. */
+/* #define DRAIN_OUTPUT */
+
+/* For the TIOCGPGRP and TIOCSPGRP ioctl parameters on HP-UX */
+#if defined (hpux) && !defined (TERMIOS_TTY_DRIVER)
+#  include <bsdtty.h>
+#endif /* hpux && !TERMIOS_TTY_DRIVER */
+
+#if !defined (STRUCT_WINSIZE_IN_SYS_IOCTL)
+/* For struct winsize on SCO */
+/*   sys/ptem.h has winsize but needs mblk_t from sys/stream.h */
+#  if defined (HAVE_SYS_PTEM_H) && defined (TIOCGWINSZ) && defined (SIGWINCH)
+#    if defined (HAVE_SYS_STREAM_H)
+#      include <sys/stream.h>
+#    endif
+#    include <sys/ptem.h>
+#  endif /* HAVE_SYS_PTEM_H && TIOCGWINSZ && SIGWINCH */
+#endif /* !STRUCT_WINSIZE_IN_SYS_IOCTL */
+
+#include "bashansi.h"
+#include "bashintl.h"
+#include "shell.h"
+#include "jobs.h"
+#include "flags.h"
+
+#include "builtins/builtext.h"
+#include "builtins/common.h"
+
+#if !defined (errno)
+extern int errno;
+#endif /* !errno */
+
+#define DEFAULT_CHILD_MAX 32
+#define MAX_JOBS_IN_ARRAY 4096         /* testing */
+
+/* Take care of system dependencies that must be handled when waiting for
+   children.  The arguments to the WAITPID macro match those to the Posix.1
+   waitpid() function. */
+
+#if defined (ultrix) && defined (mips) && defined (_POSIX_VERSION)
+#  define WAITPID(pid, statusp, options) \
+       wait3 ((union wait *)statusp, options, (struct rusage *)0)
+#else
+#  if defined (_POSIX_VERSION) || defined (HAVE_WAITPID)
+#    define WAITPID(pid, statusp, options) \
+       waitpid ((pid_t)pid, statusp, options)
+#  else
+#    if defined (HAVE_WAIT3)
+#      define WAITPID(pid, statusp, options) \
+       wait3 (statusp, options, (struct rusage *)0)
+#    else
+#      define WAITPID(pid, statusp, options) \
+       wait3 (statusp, options, (int *)0)
+#    endif /* HAVE_WAIT3 */
+#  endif /* !_POSIX_VERSION && !HAVE_WAITPID*/
+#endif /* !(Ultrix && mips && _POSIX_VERSION) */
+
+/* getpgrp () varies between systems.  Even systems that claim to be
+   Posix.1 compatible lie sometimes (Ultrix, SunOS4, apollo). */
+#if defined (GETPGRP_VOID)
+#  define getpgid(p) getpgrp ()
+#else
+#  define getpgid(p) getpgrp (p)
+#endif /* !GETPGRP_VOID */
+
+/* If the system needs it, REINSTALL_SIGCHLD_HANDLER will reinstall the
+   handler for SIGCHLD. */
+#if defined (MUST_REINSTALL_SIGHANDLERS)
+#  define REINSTALL_SIGCHLD_HANDLER signal (SIGCHLD, sigchld_handler)
+#else
+#  define REINSTALL_SIGCHLD_HANDLER
+#endif /* !MUST_REINSTALL_SIGHANDLERS */
+
+/* Some systems let waitpid(2) tell callers about stopped children. */
+#if !defined (WCONTINUED)
+#  define WCONTINUED 0
+#endif
+#if !defined (WIFCONTINUED)
+#  define WIFCONTINUED(s)      (0)
+#endif
+
+/* The number of additional slots to allocate when we run out. */
+#define JOB_SLOTS 8
+
+typedef int sh_job_map_func_t __P((JOB *, int, int, int));
+
+#if defined (READLINE)
+extern void rl_set_screen_size __P((int, int));
+#endif
+
+/* Variables used here but defined in other files. */
+extern int subshell_environment, line_number;
+extern int posixly_correct, shell_level;
+extern int interrupt_immediately;
+extern int last_command_exit_value, last_command_exit_signal;
+extern int loop_level, breaking;
+extern int sourcelevel;
+extern sh_builtin_func_t *this_shell_builtin;
+extern char *shell_name, *this_command_name;
+extern sigset_t top_level_mask;
+extern procenv_t wait_intr_buf;
+extern int wait_signal_received;
+extern WORD_LIST *subst_assign_varlist;
+
+/* The array of known jobs. */
+JOB **jobs = (JOB **)NULL;
+
+/* The number of slots currently allocated to JOBS. */
+int job_slots = 0;
+
+/* The controlling tty for this shell. */
+int shell_tty = -1;
+
+/* The shell's process group. */
+pid_t shell_pgrp = NO_PID;
+
+/* The terminal's process group. */
+pid_t terminal_pgrp = NO_PID;
+
+/* The process group of the shell's parent. */
+pid_t original_pgrp = NO_PID;
+
+/* The process group of the pipeline currently being made. */
+pid_t pipeline_pgrp = (pid_t)0;
+
+#if defined (PGRP_PIPE)
+/* Pipes which each shell uses to communicate with the process group leader
+   until all of the processes in a pipeline have been started.  Then the
+   process leader is allowed to continue. */
+int pgrp_pipe[2] = { -1, -1 };
+#endif
+
+/* The job which is current; i.e. the one that `%+' stands for. */
+int current_job = NO_JOB;
+
+/* The previous job; i.e. the one that `%-' stands for. */
+int previous_job = NO_JOB;
+
+/* Last child made by the shell.  */
+pid_t last_made_pid = NO_PID;
+
+/* Pid of the last asynchronous child. */
+pid_t last_asynchronous_pid = NO_PID;
+
+/* The pipeline currently being built. */
+PROCESS *the_pipeline = (PROCESS *)NULL;
+
+/* If this is non-zero, do job control. */
+int job_control = 1;
+
+/* Call this when you start making children. */
+int already_making_children = 0;
+
+/* If this is non-zero, $LINES and $COLUMNS are reset after every process
+   exits from get_tty_state(). */
+int check_window_size;
+
+/* Functions local to this file. */
+
+static void get_new_window_size __P((int));
+
+static void run_sigchld_trap __P((int));
+
+static sighandler wait_sigint_handler __P((int));
+static sighandler sigchld_handler __P((int));
+static sighandler sigwinch_sighandler __P((int));
+static sighandler sigcont_sighandler __P((int));
+static sighandler sigstop_sighandler __P((int));
+
+static int waitchld __P((pid_t, int));
+
+static PROCESS *find_pipeline __P((pid_t, int, int *));
+
+static char *current_working_directory __P((void));
+static char *job_working_directory __P((void));
+static char *j_strsignal __P((int));
+static char *printable_job_status __P((int, PROCESS *, int));
+
+static pid_t find_last_pid __P((int, int));
+
+static int set_new_line_discipline __P((int));
+static int map_over_jobs __P((sh_job_map_func_t *, int, int));
+static int job_last_stopped __P((int));
+static int job_last_running __P((int));
+static int most_recent_job_in_state __P((int, JOB_STATE));
+static int find_job __P((pid_t, int));
+static int print_job __P((JOB *, int, int, int));
+static int process_exit_status __P((WAIT));
+static int process_exit_signal __P((WAIT));
+static int job_exit_status __P((int));
+static int job_exit_signal __P((int));
+static int set_job_status_and_cleanup __P((int));
+
+static WAIT raw_job_exit_status __P((int));
+
+static void notify_of_job_status __P((void));
+static void cleanup_dead_jobs __P((void));
+static int compact_jobs_list __P((int));
+static void discard_pipeline __P((PROCESS *));
+static void add_process __P((char *, pid_t));
+static void print_pipeline __P((PROCESS *, int, int, FILE *));
+static void pretty_print_job __P((int, int, FILE *));
+static void set_current_job __P((int));
+static void reset_current __P((void));
+static void set_job_running __P((int));
+static void setjstatus __P((int));
+static void mark_all_jobs_as_dead __P((void));
+static void mark_dead_jobs_as_notified __P((int));
+static void restore_sigint_handler __P((void));
+#if defined (PGRP_PIPE)
+static void pipe_read __P((int *));
+static void pipe_close __P((int *));
+#endif
+
+#if defined (ARRAY_VARS)
+static int *pstatuses;         /* list of pipeline statuses */
+static int statsize;
+#endif
+
+/* Used to synchronize between wait_for and other functions and the SIGCHLD
+   signal handler. */
+static int sigchld;
+static int queue_sigchld;
+
+#define QUEUE_SIGCHLD(os)      (os) = sigchld, queue_sigchld++
+
+#define UNQUEUE_SIGCHLD(os) \
+       do { \
+         queue_sigchld--; \
+         if (queue_sigchld == 0 && os != sigchld) \
+           waitchld (-1, 0); \
+       } while (0)
+
+static SigHandler *old_tstp, *old_ttou, *old_ttin;
+static SigHandler *old_cont = (SigHandler *)SIG_DFL;
+
+#if defined (TIOCGWINSZ) && defined (SIGWINCH)
+static SigHandler *old_winch = (SigHandler *)SIG_DFL;
+#endif
+
+/* A place to temporarily save the current pipeline. */
+static PROCESS *saved_pipeline;
+static int saved_already_making_children;
+
+/* Set this to non-zero whenever you don't want the jobs list to change at
+   all: no jobs deleted and no status change notifications.  This is used,
+   for example, when executing SIGCHLD traps, which may run arbitrary
+   commands. */
+static int jobs_list_frozen;
+
+static char retcode_name_buffer[64];
+
+static long child_max = -1L;
+
+#if !defined (_POSIX_VERSION)
+
+/* These are definitions to map POSIX 1003.1 functions onto existing BSD
+   library functions and system calls. */
+#define setpgid(pid, pgrp)     setpgrp (pid, pgrp)
+#define tcsetpgrp(fd, pgrp)    ioctl ((fd), TIOCSPGRP, &(pgrp))
+
+pid_t
+tcgetpgrp (fd)
+     int fd;
+{
+  pid_t pgrp;
+
+  /* ioctl will handle setting errno correctly. */
+  if (ioctl (fd, TIOCGPGRP, &pgrp) < 0)
+    return (-1);
+  return (pgrp);
+}
+
+#endif /* !_POSIX_VERSION */
+
+/* Return the working directory for the current process.  Unlike
+   job_working_directory, this does not call malloc (), nor do any
+   of the functions it calls.  This is so that it can safely be called
+   from a signal handler. */
+static char *
+current_working_directory ()
+{
+  char *dir;
+  static char d[PATH_MAX];
+
+  dir = get_string_value ("PWD");
+
+  if (dir == 0 && the_current_working_directory && no_symbolic_links)
+    dir = the_current_working_directory;
+
+  if (dir == 0)
+    {
+      dir = getcwd (d, sizeof(d));
+      if (dir)
+       dir = d;
+    }
+
+  return (dir == 0) ? "<unknown>" : dir;
+}
+
+/* Return the working directory for the current process. */
+static char *
+job_working_directory ()
+{
+  char *dir;
+
+  dir = get_string_value ("PWD");
+  if (dir)
+    return (savestring (dir));
+
+  dir = get_working_directory ("job-working-directory");
+  if (dir)
+    return (dir);
+
+  return (savestring ("<unknown>"));
+}
+
+void
+making_children ()
+{
+  if (already_making_children)
+    return;
+
+  already_making_children = 1;
+  start_pipeline ();
+}
+
+void
+stop_making_children ()
+{
+  already_making_children = 0;
+}
+
+void
+cleanup_the_pipeline ()
+{
+  if (the_pipeline)
+    {
+      discard_pipeline (the_pipeline);
+      the_pipeline = (PROCESS *)NULL;
+    }
+}
+
+void
+save_pipeline (clear)
+     int clear;
+{
+  saved_pipeline = the_pipeline;
+  saved_already_making_children = already_making_children;
+  if (clear)
+    the_pipeline = (PROCESS *)NULL;
+}
+
+void
+restore_pipeline (discard)
+     int discard;
+{
+  PROCESS *old_pipeline;
+
+  old_pipeline = the_pipeline;
+  the_pipeline = saved_pipeline;
+  already_making_children = saved_already_making_children;
+  if (discard)
+    discard_pipeline (old_pipeline);
+}
+
+/* Start building a pipeline.  */
+void
+start_pipeline ()
+{
+  if (the_pipeline)
+    {
+      cleanup_the_pipeline ();
+      pipeline_pgrp = 0;
+#if defined (PGRP_PIPE)
+      pipe_close (pgrp_pipe);
+#endif
+    }
+
+#if defined (PGRP_PIPE)
+  if (job_control)
+    {
+      if (pipe (pgrp_pipe) == -1)
+       sys_error ("start_pipeline: pgrp pipe");
+    }
+#endif
+}
+
+/* Stop building a pipeline.  Install the process list in the job array.
+   This returns the index of the newly installed job.
+   DEFERRED is a command structure to be executed upon satisfactory
+   execution exit of this pipeline. */
+int
+stop_pipeline (async, deferred)
+     int async;
+     COMMAND *deferred;
+{
+  register int i, j;
+  JOB *newjob;
+  sigset_t set, oset;
+
+  BLOCK_CHILD (set, oset);
+
+#if defined (PGRP_PIPE)
+  /* The parent closes the process group synchronization pipe. */
+  pipe_close (pgrp_pipe);
+#endif
+
+  cleanup_dead_jobs ();
+
+  if (job_slots == 0)
+    {
+      job_slots = JOB_SLOTS;
+      jobs = (JOB **)xmalloc (job_slots * sizeof (JOB *));
+
+      /* Now blank out these new entries. */
+      for (i = 0; i < job_slots; i++)
+       jobs[i] = (JOB *)NULL;
+    }
+
+  /* Scan from the last slot backward, looking for the next free one. */
+  if (interactive)
+    {
+      for (i = job_slots; i; i--)
+       if (jobs[i - 1])
+         break;
+    }
+  else
+    {
+      /* If we're not interactive, we don't need to monotonically increase
+        the job number (in fact, we don't care about the job number at all),
+        so we can simply scan for the first free slot.  This helps to keep
+        us from continuously reallocating the jobs array when running
+        certain kinds of shell loops, and saves time spent searching. */
+      for (i = 0; i < job_slots; i++)
+       if (jobs[i] == 0)
+         break;
+    }
+
+  /* Do we need more room? */
+
+  /* First try compaction */
+  if (subshell_environment && interactive_shell && i == job_slots && job_slots >= MAX_JOBS_IN_ARRAY)
+    i = compact_jobs_list (0);
+
+  /* If we can't compact, reallocate */
+  if (i == job_slots)
+    {
+      job_slots += JOB_SLOTS;
+      jobs = (JOB **)xrealloc (jobs, ((1 + job_slots) * sizeof (JOB *)));
+
+      for (j = i; j < job_slots; j++)
+       jobs[j] = (JOB *)NULL;
+    }
+
+  /* Add the current pipeline to the job list. */
+  if (the_pipeline)
+    {
+      register PROCESS *p;
+      int any_alive, any_stopped;
+
+      newjob = (JOB *)xmalloc (sizeof (JOB));
+
+      for (p = the_pipeline; p->next != the_pipeline; p = p->next)
+       ;
+      p->next = (PROCESS *)NULL;
+      newjob->pipe = REVERSE_LIST (the_pipeline, PROCESS *);
+      for (p = newjob->pipe; p->next; p = p->next)
+       ;
+      p->next = newjob->pipe;
+
+      the_pipeline = (PROCESS *)NULL;
+      newjob->pgrp = pipeline_pgrp;
+      pipeline_pgrp = 0;
+
+      newjob->flags = 0;
+
+      /* Flag to see if in another pgrp. */
+      if (job_control)
+       newjob->flags |= J_JOBCONTROL;
+
+      /* Set the state of this pipeline. */
+      p = newjob->pipe;
+      any_alive = any_stopped = 0;
+      do
+       {
+         any_alive |= p->running;
+         any_stopped |= WIFSTOPPED (p->status);
+         p = p->next;
+       }
+      while (p != newjob->pipe);
+
+      newjob->state = any_alive ? JRUNNING : (any_stopped ? JSTOPPED : JDEAD);
+      newjob->wd = job_working_directory ();
+      newjob->deferred = deferred;
+
+      newjob->j_cleanup = (sh_vptrfunc_t *)NULL;
+      newjob->cleanarg = (PTR_T) NULL;
+
+      jobs[i] = newjob;
+      if (newjob->state == JDEAD && (newjob->flags & J_FOREGROUND))
+       setjstatus (i);
+    }
+  else
+    newjob = (JOB *)NULL;
+
+  if (async)
+    {
+      if (newjob)
+       newjob->flags &= ~J_FOREGROUND;
+      reset_current ();
+    }
+  else
+    {
+      if (newjob)
+       {
+         newjob->flags |= J_FOREGROUND;
+         /*
+          *            !!!!! NOTE !!!!!  (chet@ins.cwru.edu)
+          *
+          * The currently-accepted job control wisdom says to set the
+          * terminal's process group n+1 times in an n-step pipeline:
+          * once in the parent and once in each child.  This is where
+          * the parent gives it away.
+          *
+          */
+         if (job_control && newjob->pgrp)
+           give_terminal_to (newjob->pgrp, 0);
+       }
+    }
+
+  stop_making_children ();
+  UNBLOCK_CHILD (oset);
+  return (current_job);
+}
+
+/* Delete all DEAD jobs that the user had received notification about. */
+static void
+cleanup_dead_jobs ()
+{
+  register int i;
+  int os;
+
+  if (job_slots == 0 || jobs_list_frozen)
+    return;
+
+  QUEUE_SIGCHLD(os);
+
+  for (i = 0; i < job_slots; i++)
+    if (jobs[i] && DEADJOB (i) && IS_NOTIFIED (i))
+      delete_job (i, 0);
+
+  UNQUEUE_SIGCHLD(os);
+}
+
+/* Compact the jobs list by removing dead jobs.  Assumed that we have filled
+   the jobs array to some predefined maximum.  Called when the shell is not
+   the foreground process (subshell_environment != 0).  Returns the first
+   available slot in the compacted list.  If that value is job_slots, then
+   the list needs to be reallocated.  The jobs array is in new memory if
+   this returns > 0 and < job_slots.  FLAGS is reserved for future use. */
+static int
+compact_jobs_list (flags)
+     int flags;
+{
+  sigset_t set, oset;
+  register int i, j;
+  int nremove, ndel;
+  JOB **newlist;
+
+  if (job_slots == 0 || jobs_list_frozen)
+    return job_slots;
+
+  if (child_max < 0)
+    child_max = getmaxchild ();
+
+  /* Take out at most a quarter of the jobs in the jobs array, but leave at
+     least child_max */
+  nremove = job_slots >> 2;
+  if ((job_slots - nremove) < child_max)
+    nremove = job_slots - child_max;
+
+  /* need to increase jobs list to at least CHILD_MAX entries */
+  if (nremove < 0)
+    return job_slots;
+
+  BLOCK_CHILD (set, oset);  
+
+  for (ndel = i = 0; i < job_slots; i++)
+    if (jobs[i])
+      {
+       if (DEADJOB (i) && (find_last_pid (i, 0) != last_asynchronous_pid))
+         {
+           delete_job (i, 0);
+           ndel++;
+           if (ndel == nremove)
+             break;
+         }
+      }
+
+  if (ndel == 0)
+    {
+      UNBLOCK_CHILD (oset);
+      return job_slots;
+    }
+
+  newlist = (JOB **)xmalloc ((1 + job_slots) * sizeof (JOB *));
+  for (i = j = 0; i < job_slots; i++)
+    if (jobs[i])
+      newlist[j++] = jobs[i];
+
+  ndel = j;
+  for ( ; j < job_slots; j++)
+    newlist[j] = (JOB *)NULL;
+
+  free (jobs);
+  jobs = newlist;
+
+  UNBLOCK_CHILD (oset);
+
+  return ndel;
+}
+
+/* Delete the job at INDEX from the job list.  Must be called
+   with SIGCHLD blocked. */
+void
+delete_job (job_index, warn_stopped)
+     int job_index, warn_stopped;
+{
+  register JOB *temp;
+
+  if (job_slots == 0 || jobs_list_frozen)
+    return;
+
+  if (warn_stopped && subshell_environment == 0 && STOPPED (job_index))
+    internal_warning (_("deleting stopped job %d with process group %ld"), job_index+1, (long)jobs[job_index]->pgrp);
+
+  temp = jobs[job_index];
+  if (job_index == current_job || job_index == previous_job)
+    reset_current ();
+
+  jobs[job_index] = (JOB *)NULL;
+
+  free (temp->wd);
+  discard_pipeline (temp->pipe);
+
+  if (temp->deferred)
+    dispose_command (temp->deferred);
+
+  free (temp);
+}
+
+/* Must be called with SIGCHLD blocked. */
+void
+nohup_job (job_index)
+     int job_index;
+{
+  register JOB *temp;
+
+  if (job_slots == 0)
+    return;
+
+  if (temp = jobs[job_index])
+    temp->flags |= J_NOHUP;
+}
+
+/* Get rid of the data structure associated with a process chain. */
+static void
+discard_pipeline (chain)
+     register PROCESS *chain;
+{
+  register PROCESS *this, *next;
+
+  this = chain;
+  do
+    {
+      next = this->next;
+      FREE (this->command);
+      free (this);
+      this = next;
+    }
+  while (this != chain);
+}
+
+/* Add this process to the chain being built in the_pipeline.
+   NAME is the command string that will be exec'ed later.
+   PID is the process id of the child. */
+static void
+add_process (name, pid)
+     char *name;
+     pid_t pid;
+{
+  PROCESS *t, *p;
+
+  t = (PROCESS *)xmalloc (sizeof (PROCESS));
+  t->next = the_pipeline;
+  t->pid = pid;
+  WSTATUS (t->status) = 0;
+  t->running = PS_RUNNING;
+  t->command = name;
+  the_pipeline = t;
+
+  if (t->next == 0)
+    t->next = t;
+  else
+    {
+      p = t->next;
+      while (p->next != t->next)
+       p = p->next;
+      p->next = t;
+    }
+}
+
+#if 0
+/* Take the last job and make it the first job.  Must be called with
+   SIGCHLD blocked. */
+int
+rotate_the_pipeline ()
+{
+  PROCESS *p;
+
+  if (the_pipeline->next == the_pipeline)
+    return;
+  for (p = the_pipeline; p->next != the_pipeline; p = p->next)
+    ;
+  the_pipeline = p;
+}
+
+/* Reverse the order of the processes in the_pipeline.  Must be called with
+   SIGCHLD blocked. */
+int
+reverse_the_pipeline ()
+{
+  PROCESS *p, *n;
+
+  if (the_pipeline->next == the_pipeline)
+    return;
+
+  for (p = the_pipeline; p->next != the_pipeline; p = p->next)
+    ;
+  p->next = (PROCESS *)NULL;
+
+  n = REVERSE_LIST (the_pipeline, PROCESS *);
+
+  the_pipeline = n;
+  for (p = the_pipeline; p->next; p = p->next)
+    ;
+  p->next = the_pipeline;
+}
+#endif
+
+/* Map FUNC over the list of jobs.  If FUNC returns non-zero,
+   then it is time to stop mapping, and that is the return value
+   for map_over_jobs.  FUNC is called with a JOB, arg1, arg2,
+   and INDEX. */
+static int
+map_over_jobs (func, arg1, arg2)
+     sh_job_map_func_t *func;
+     int arg1, arg2;
+{
+  register int i;
+  int result;
+  sigset_t set, oset;
+
+  if (job_slots == 0)
+    return 0;
+
+  BLOCK_CHILD (set, oset);
+
+  for (i = result = 0; i < job_slots; i++)
+    {
+      if (jobs[i])
+       {
+         result = (*func)(jobs[i], arg1, arg2, i);
+         if (result)
+           break;
+       }
+    }
+
+  UNBLOCK_CHILD (oset);
+
+  return (result);
+}
+
+/* Cause all the jobs in the current pipeline to exit. */
+void
+terminate_current_pipeline ()
+{
+  if (pipeline_pgrp && pipeline_pgrp != shell_pgrp)
+    {
+      killpg (pipeline_pgrp, SIGTERM);
+      killpg (pipeline_pgrp, SIGCONT);
+    }
+}
+
+/* Cause all stopped jobs to exit. */
+void
+terminate_stopped_jobs ()
+{
+  register int i;
+
+  for (i = 0; i < job_slots; i++)
+    {
+      if (jobs[i] && STOPPED (i))
+       {
+         killpg (jobs[i]->pgrp, SIGTERM);
+         killpg (jobs[i]->pgrp, SIGCONT);
+       }
+    }
+}
+
+/* Cause all jobs, running or stopped, to receive a hangup signal.  If
+   a job is marked J_NOHUP, don't send the SIGHUP. */
+void
+hangup_all_jobs ()
+{
+  register int i;
+
+  for (i = 0; i < job_slots; i++)
+    {
+      if (jobs[i])
+       {
+         if  ((jobs[i]->flags & J_NOHUP) == 0)
+           killpg (jobs[i]->pgrp, SIGHUP);
+         if (STOPPED (i))
+           killpg (jobs[i]->pgrp, SIGCONT);
+       }
+    }
+}
+
+void
+kill_current_pipeline ()
+{
+  stop_making_children ();
+  start_pipeline ();
+}
+
+/* Return the pipeline that PID belongs to.  Note that the pipeline
+   doesn't have to belong to a job.  Must be called with SIGCHLD blocked. */
+static PROCESS *
+find_pipeline (pid, running_only, jobp)
+     pid_t pid;
+     int running_only;
+     int *jobp;                /* index into jobs list or NO_JOB */
+{
+  int job;
+  register PROCESS *p;
+
+  /* See if this process is in the pipeline that we are building. */
+  if (jobp)
+    *jobp = NO_JOB;
+  if (the_pipeline)
+    {
+      p = the_pipeline;
+      do
+       {
+         /* Return it if we found it. */
+         if (p->pid == pid)
+           {
+             if ((running_only && PRUNNING(p)) || (running_only == 0))
+               return (p);
+           }
+
+         p = p->next;
+       }
+      while (p != the_pipeline);
+    }
+
+  job = find_job (pid, running_only);
+  if (jobp)
+    *jobp = job;
+  return (job == NO_JOB) ? (PROCESS *)NULL : jobs[job]->pipe;
+}
+
+/* Return the job index that PID belongs to, or NO_JOB if it doesn't
+   belong to any job.  Must be called with SIGCHLD blocked. */
+static int
+find_job (pid, running_only)
+     pid_t pid;
+     int running_only;
+{
+  register int i;
+  register PROCESS *p;
+
+  for (i = 0; i < job_slots; i++)
+    {
+      if (jobs[i])
+       {
+         p = jobs[i]->pipe;
+
+         do
+           {
+             if (p->pid == pid)
+               {
+                 if ((running_only && PRUNNING(p)) || (running_only == 0))
+                   return (i);
+               }
+
+             p = p->next;
+           }
+         while (p != jobs[i]->pipe);
+       }
+    }
+
+  return (NO_JOB);
+}
+
+/* Find a job given a PID.  If BLOCK is non-zero, block SIGCHLD as
+   required by find_job. */
+int
+get_job_by_pid (pid, block)
+     pid_t pid;
+     int block;
+{
+  int job;
+  sigset_t set, oset;
+
+  if (block)
+    BLOCK_CHILD (set, oset);
+
+  job = find_job (pid, 0);
+
+  if (block)
+    UNBLOCK_CHILD (oset);
+
+  return job;
+}
+
+/* Print descriptive information about the job with leader pid PID. */
+void
+describe_pid (pid)
+     pid_t pid;
+{
+  int job;
+  sigset_t set, oset;
+
+  BLOCK_CHILD (set, oset);
+
+  job = find_job (pid, 0);
+
+  if (job != NO_JOB)
+    printf ("[%d] %ld\n", job + 1, (long)pid);
+  else
+    programming_error (_("describe_pid: %ld: no such pid"), (long)pid);
+
+  UNBLOCK_CHILD (oset);
+}
+
+static char *
+j_strsignal (s)
+     int s;
+{
+  char *x;
+
+  x = strsignal (s);
+  if (x == 0)
+    {
+      x = retcode_name_buffer;
+      sprintf (x, "Signal %d", s);
+    }
+  return x;
+}
+
+static char *
+printable_job_status (j, p, format)
+     int j;
+     PROCESS *p;
+     int format;
+{
+  static char *temp;
+  int es;
+
+  temp = "Done";
+
+  if (STOPPED (j) && format == 0)
+    {
+      if (posixly_correct == 0 || p == 0 || (WIFSTOPPED (p->status) == 0))
+       temp = "Stopped";
+      else
+       {
+         temp = retcode_name_buffer;
+         sprintf (temp, "Stopped(%s)", signal_name (WSTOPSIG (p->status)));
+       }
+    }
+  else if (RUNNING (j))
+    temp = "Running";
+  else
+    {
+      if (WIFSTOPPED (p->status))
+       temp = j_strsignal (WSTOPSIG (p->status));
+      else if (WIFSIGNALED (p->status))
+       temp = j_strsignal (WTERMSIG (p->status));
+      else if (WIFEXITED (p->status))
+       {
+         temp = retcode_name_buffer;
+         es = WEXITSTATUS (p->status);
+         if (es == 0)
+           strcpy (temp, "Done");
+         else if (posixly_correct)
+           sprintf (temp, "Done(%d)", es);
+         else
+           sprintf (temp, "Exit %d", es);
+       }
+      else
+       temp = "Unknown status";
+    }
+
+  return temp;
+}
+
+/* This is the way to print out information on a job if you
+   know the index.  FORMAT is:
+
+    JLIST_NORMAL)   [1]+ Running          emacs
+    JLIST_LONG  )   [1]+ 2378 Running      emacs
+    -1   )   [1]+ 2378       emacs
+
+    JLIST_NORMAL)   [1]+ Stopped          ls | more
+    JLIST_LONG  )   [1]+ 2369 Stopped      ls
+                        2367       | more
+    JLIST_PID_ONLY)
+       Just list the pid of the process group leader (really
+       the process group).
+    JLIST_CHANGED_ONLY)
+       Use format JLIST_NORMAL, but list only jobs about which
+       the user has not been notified. */
+
+/* Print status for pipeline P.  If JOB_INDEX is >= 0, it is the index into
+   the JOBS array corresponding to this pipeline.  FORMAT is as described
+   above.  Must be called with SIGCHLD blocked.
+
+   If you're printing a pipeline that's not in the jobs array, like the
+   current pipeline as it's being created, pass -1 for JOB_INDEX */
+static void
+print_pipeline (p, job_index, format, stream)
+     PROCESS *p;
+     int job_index, format;
+     FILE *stream;
+{
+  PROCESS *first, *last, *show;
+  int es, name_padding;
+  char *temp;
+
+  if (p == 0)
+    return;
+
+  first = last = p;
+  while (last->next != first)
+    last = last->next;
+
+  for (;;)
+    {
+      if (p != first)
+       fprintf (stream, format ? "     " : " |");
+
+      if (format != JLIST_STANDARD)
+       fprintf (stream, "%5ld", (long)p->pid);
+
+      fprintf (stream, " ");
+
+      if (format > -1 && job_index >= 0)
+       {
+         show = format ? p : last;
+         temp = printable_job_status (job_index, show, format);
+
+         if (p != first)
+           {
+             if (format)
+               {
+                 if (show->running == first->running &&
+                     WSTATUS (show->status) == WSTATUS (first->status))
+                   temp = "";
+               }
+             else
+               temp = (char *)NULL;
+           }
+
+         if (temp)
+           {
+             fprintf (stream, "%s", temp);
+
+             es = STRLEN (temp);
+             if (es == 0)
+               es = 2; /* strlen ("| ") */
+             name_padding = LONGEST_SIGNAL_DESC - es;
+
+             fprintf (stream, "%*s", name_padding, "");
+
+             if ((WIFSTOPPED (show->status) == 0) &&
+                 (WIFCONTINUED (show->status) == 0) &&
+                 WIFCORED (show->status))
+               fprintf (stream, "(core dumped) ");
+           }
+       }
+
+      if (p != first && format)
+       fprintf (stream, "| ");
+
+      if (p->command)
+       fprintf (stream, "%s", p->command);
+
+      if (p == last && job_index >= 0)
+       {
+         temp = current_working_directory ();
+
+         if (RUNNING (job_index) && (IS_FOREGROUND (job_index) == 0))
+           fprintf (stream, " &");
+
+         if (strcmp (temp, jobs[job_index]->wd) != 0)
+           fprintf (stream,
+             "  (wd: %s)", polite_directory_format (jobs[job_index]->wd));
+       }
+
+      if (format || (p == last))
+       {
+         /* We need to add a CR only if this is an interactive shell, and
+            we're reporting the status of a completed job asynchronously.
+            We can't really check whether this particular job is being
+            reported asynchronously, so just add the CR if the shell is
+            currently interactive and asynchronous notification is enabled. */
+         if (asynchronous_notification && interactive)
+           fprintf (stream, "\r\n");
+         else
+           fprintf (stream, "\n");
+       }
+
+      if (p == last)
+       break;
+      p = p->next;
+    }
+  fflush (stream);
+}
+
+/* Print information to STREAM about jobs[JOB_INDEX] according to FORMAT.
+   Must be called with SIGCHLD blocked or queued with queue_sigchld */
+static void
+pretty_print_job (job_index, format, stream)
+     int job_index, format;
+     FILE *stream;
+{
+  register PROCESS *p;
+
+  /* Format only pid information about the process group leader? */
+  if (format == JLIST_PID_ONLY)
+    {
+      fprintf (stream, "%ld\n", (long)jobs[job_index]->pipe->pid);
+      return;
+    }
+
+  if (format == JLIST_CHANGED_ONLY)
+    {
+      if (IS_NOTIFIED (job_index))
+       return;
+      format = JLIST_STANDARD;
+    }
+
+  if (format != JLIST_NONINTERACTIVE)
+    fprintf (stream, "[%d]%c ", job_index + 1,
+             (job_index == current_job) ? '+':
+               (job_index == previous_job) ? '-' : ' ');
+
+  if (format == JLIST_NONINTERACTIVE)
+    format = JLIST_LONG;
+
+  p = jobs[job_index]->pipe;
+
+  print_pipeline (p, job_index, format, stream);
+
+  /* We have printed information about this job.  When the job's
+     status changes, waitchld () sets the notification flag to 0. */
+  jobs[job_index]->flags |= J_NOTIFIED;
+}
+
+static int
+print_job (job, format, state, job_index)
+     JOB *job;
+     int format, state, job_index;
+{
+  if (state == -1 || (JOB_STATE)state == job->state)
+    pretty_print_job (job_index, format, stdout);
+  return (0);
+}
+
+void
+list_one_job (job, format, ignore, job_index)
+     JOB *job;
+     int format, ignore, job_index;
+{
+  pretty_print_job (job_index, format, stdout);
+}
+
+void
+list_stopped_jobs (format)
+     int format;
+{
+  cleanup_dead_jobs ();
+  map_over_jobs (print_job, format, (int)JSTOPPED);
+}
+
+void
+list_running_jobs (format)
+     int format;
+{
+  cleanup_dead_jobs ();
+  map_over_jobs (print_job, format, (int)JRUNNING);
+}
+
+/* List jobs.  If FORMAT is non-zero, then the long form of the information
+   is printed, else just a short version. */
+void
+list_all_jobs (format)
+     int format;
+{
+  cleanup_dead_jobs ();
+  map_over_jobs (print_job, format, -1);
+}
+
+/* Fork, handling errors.  Returns the pid of the newly made child, or 0.
+   COMMAND is just for remembering the name of the command; we don't do
+   anything else with it.  ASYNC_P says what to do with the tty.  If
+   non-zero, then don't give it away. */
+pid_t
+make_child (command, async_p)
+     char *command;
+     int async_p;
+{
+  sigset_t set, oset;
+  pid_t pid;
+
+  sigemptyset (&set);
+  sigaddset (&set, SIGCHLD);
+  sigaddset (&set, SIGINT);
+  sigemptyset (&oset);
+  sigprocmask (SIG_BLOCK, &set, &oset);
+
+  making_children ();
+
+#if defined (BUFFERED_INPUT)
+  /* If default_buffered_input is active, we are reading a script.  If
+     the command is asynchronous, we have already duplicated /dev/null
+     as fd 0, but have not changed the buffered stream corresponding to
+     the old fd 0.  We don't want to sync the stream in this case. */
+  if (default_buffered_input != -1 &&
+      (!async_p || default_buffered_input > 0))
+    sync_buffered_stream (default_buffered_input);
+#endif /* BUFFERED_INPUT */
+
+  /* Create the child, handle severe errors. */
+  if ((pid = fork ()) < 0)
+    {
+      sys_error ("fork");
+
+      /* Kill all of the processes in the current pipeline. */
+      terminate_current_pipeline ();
+
+      /* Discard the current pipeline, if any. */
+      if (the_pipeline)
+       kill_current_pipeline ();
+
+      throw_to_top_level ();   /* Reset signals, etc. */
+    }
+
+  if (pid == 0)
+    {
+      /* In the child.  Give this child the right process group, set the
+        signals to the default state for a new process. */
+      pid_t mypid;
+
+      mypid = getpid ();
+#if defined (BUFFERED_INPUT)
+      /* Close default_buffered_input if it's > 0.  We don't close it if it's
+        0 because that's the file descriptor used when redirecting input,
+        and it's wrong to close the file in that case. */
+      unset_bash_input (0);
+#endif /* BUFFERED_INPUT */
+
+      /* Restore top-level signal mask. */
+      sigprocmask (SIG_SETMASK, &top_level_mask, (sigset_t *)NULL);
+
+      if (job_control)
+       {
+         /* All processes in this pipeline belong in the same
+            process group. */
+
+         if (pipeline_pgrp == 0)       /* This is the first child. */
+           pipeline_pgrp = mypid;
+
+         /* Check for running command in backquotes. */
+         if (pipeline_pgrp == shell_pgrp)
+           ignore_tty_job_signals ();
+         else
+           default_tty_job_signals ();
+
+         /* Set the process group before trying to mess with the terminal's
+            process group.  This is mandated by POSIX. */
+         /* This is in accordance with the Posix 1003.1 standard,
+            section B.7.2.4, which says that trying to set the terminal
+            process group with tcsetpgrp() to an unused pgrp value (like
+            this would have for the first child) is an error.  Section
+            B.4.3.3, p. 237 also covers this, in the context of job control
+            shells. */
+         if (setpgid (mypid, pipeline_pgrp) < 0)
+           sys_error ("child setpgid (%ld to %ld)", (long)mypid, (long)pipeline_pgrp);
+
+itrace("make_child: setpgid (pid = %d, pgrp = %d)", mypid, pipeline_pgrp);
+         /* By convention (and assumption above), if
+            pipeline_pgrp == shell_pgrp, we are making a child for
+            command substitution.
+            In this case, we don't want to give the terminal to the
+            shell's process group (we could be in the middle of a
+            pipeline, for example). */
+         if (async_p == 0 && pipeline_pgrp != shell_pgrp)
+           give_terminal_to (pipeline_pgrp, 0);
+
+#if defined (PGRP_PIPE)
+         if (pipeline_pgrp == mypid)
+           pipe_read (pgrp_pipe);
+#endif
+       }
+      else                     /* Without job control... */
+       {
+         if (pipeline_pgrp == 0)
+           pipeline_pgrp = shell_pgrp;
+
+         /* If these signals are set to SIG_DFL, we encounter the curious
+            situation of an interactive ^Z to a running process *working*
+            and stopping the process, but being unable to do anything with
+            that process to change its state.  On the other hand, if they
+            are set to SIG_IGN, jobs started from scripts do not stop when
+            the shell running the script gets a SIGTSTP and stops. */
+
+         default_tty_job_signals ();
+       }
+
+#if defined (PGRP_PIPE)
+      /* Release the process group pipe, since our call to setpgid ()
+        is done.  The last call to pipe_close is done in stop_pipeline. */
+      pipe_close (pgrp_pipe);
+#endif /* PGRP_PIPE */
+
+      if (async_p)
+       last_asynchronous_pid = getpid ();
+    }
+  else
+    {
+      /* In the parent.  Remember the pid of the child just created
+        as the proper pgrp if this is the first child. */
+
+      if (job_control)
+       {
+         if (pipeline_pgrp == 0)
+           {
+             pipeline_pgrp = pid;
+             /* Don't twiddle terminal pgrps in the parent!  This is the bug,
+                not the good thing of twiddling them in the child! */
+             /* give_terminal_to (pipeline_pgrp, 0); */
+           }
+         /* This is done on the recommendation of the Rationale section of
+            the POSIX 1003.1 standard, where it discusses job control and
+            shells.  It is done to avoid possible race conditions. (Ref.
+            1003.1 Rationale, section B.4.3.3, page 236). */
+         setpgid (pid, pipeline_pgrp);
+       }
+      else
+       {
+         if (pipeline_pgrp == 0)
+           pipeline_pgrp = shell_pgrp;
+       }
+
+      /* Place all processes into the jobs array regardless of the
+        state of job_control. */
+      add_process (command, pid);
+
+      if (async_p)
+       last_asynchronous_pid = pid;
+
+      last_made_pid = pid;
+
+      /* Unblock SIGINT and SIGCHLD. */
+      sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL);
+    }
+
+  return (pid);
+}
+
+/* These two functions are called only in child processes. */
+void
+ignore_tty_job_signals ()
+{
+  set_signal_handler (SIGTSTP, SIG_IGN);
+  set_signal_handler (SIGTTIN, SIG_IGN);
+  set_signal_handler (SIGTTOU, SIG_IGN);
+}
+
+void
+default_tty_job_signals ()
+{
+  set_signal_handler (SIGTSTP, SIG_DFL);
+  set_signal_handler (SIGTTIN, SIG_DFL);
+  set_signal_handler (SIGTTOU, SIG_DFL);
+}
+
+/* When we end a job abnormally, or if we stop a job, we set the tty to the
+   state kept in here.  When a job ends normally, we set the state in here
+   to the state of the tty. */
+
+static TTYSTRUCT shell_tty_info;
+
+#if defined (NEW_TTY_DRIVER)
+static struct tchars shell_tchars;
+static struct ltchars shell_ltchars;
+#endif /* NEW_TTY_DRIVER */
+
+#if defined (NEW_TTY_DRIVER) && defined (DRAIN_OUTPUT)
+/* Since the BSD tty driver does not allow us to change the tty modes
+   while simultaneously waiting for output to drain and preserving
+   typeahead, we have to drain the output ourselves before calling
+   ioctl.  We cheat by finding the length of the output queue, and
+   using select to wait for an appropriate length of time.  This is
+   a hack, and should be labeled as such (it's a hastily-adapted
+   mutation of a `usleep' implementation).  It's only reason for
+   existing is the flaw in the BSD tty driver. */
+
+static int ttspeeds[] =
+{
+  0, 50, 75, 110, 134, 150, 200, 300, 600, 1200,
+  1800, 2400, 4800, 9600, 19200, 38400
+};
+
+static void
+draino (fd, ospeed)
+     int fd, ospeed;
+{
+  register int delay = ttspeeds[ospeed];
+  int n;
+
+  if (!delay)
+    return;
+
+  while ((ioctl (fd, TIOCOUTQ, &n) == 0) && n)
+    {
+      if (n > (delay / 100))
+       {
+         struct timeval tv;
+
+         n *= 10;              /* 2 bits more for conservativeness. */
+         tv.tv_sec = n / delay;
+         tv.tv_usec = ((n % delay) * 1000000) / delay;
+         select (fd, (fd_set *)0, (fd_set *)0, (fd_set *)0, &tv);
+       }
+      else
+       break;
+    }
+}
+#endif /* NEW_TTY_DRIVER && DRAIN_OUTPUT */
+
+/* Return the fd from which we are actually getting input. */
+#define input_tty() (shell_tty != -1) ? shell_tty : fileno (stderr)
+
+/* Fill the contents of shell_tty_info with the current tty info. */
+int
+get_tty_state ()
+{
+  int tty;
+
+  tty = input_tty ();
+  if (tty != -1)
+    {
+#if defined (NEW_TTY_DRIVER)
+      ioctl (tty, TIOCGETP, &shell_tty_info);
+      ioctl (tty, TIOCGETC, &shell_tchars);
+      ioctl (tty, TIOCGLTC, &shell_ltchars);
+#endif /* NEW_TTY_DRIVER */
+
+#if defined (TERMIO_TTY_DRIVER)
+      ioctl (tty, TCGETA, &shell_tty_info);
+#endif /* TERMIO_TTY_DRIVER */
+
+#if defined (TERMIOS_TTY_DRIVER)
+      if (tcgetattr (tty, &shell_tty_info) < 0)
+       {
+#if 0
+         /* Only print an error message if we're really interactive at
+            this time. */
+         if (interactive)
+           sys_error ("[%ld: %d] tcgetattr", (long)getpid (), shell_level);
+#endif
+         return -1;
+       }
+#endif /* TERMIOS_TTY_DRIVER */
+      if (check_window_size)
+       get_new_window_size (0);
+    }
+  return 0;
+}
+
+/* Make the current tty use the state in shell_tty_info. */
+int
+set_tty_state ()
+{
+  int tty;
+
+  tty = input_tty ();
+  if (tty != -1)
+    {
+#if defined (NEW_TTY_DRIVER)
+#  if defined (DRAIN_OUTPUT)
+      draino (tty, shell_tty_info.sg_ospeed);
+#  endif /* DRAIN_OUTPUT */
+      ioctl (tty, TIOCSETN, &shell_tty_info);
+      ioctl (tty, TIOCSETC, &shell_tchars);
+      ioctl (tty, TIOCSLTC, &shell_ltchars);
+#endif /* NEW_TTY_DRIVER */
+
+#if defined (TERMIO_TTY_DRIVER)
+      ioctl (tty, TCSETAW, &shell_tty_info);
+#endif /* TERMIO_TTY_DRIVER */
+
+#if defined (TERMIOS_TTY_DRIVER)
+      if (tcsetattr (tty, TCSADRAIN, &shell_tty_info) < 0)
+       {
+         /* Only print an error message if we're really interactive at
+            this time. */
+         if (interactive)
+           sys_error ("[%ld: %d] tcsetattr", (long)getpid (), shell_level);
+         return -1;
+       }
+#endif /* TERMIOS_TTY_DRIVER */
+    }
+  return 0;
+}
+
+/* Given an index into the jobs array JOB, return the pid of the last
+   process in that job's pipeline.  This is the one whose exit status
+   counts.  Must be called with SIGCHLD blocked or queued. */
+static pid_t
+find_last_pid (job, block)
+     int job;
+     int block;
+{
+  register PROCESS *p;
+  sigset_t set, oset;
+
+  if (block)
+    BLOCK_CHILD (set, oset);
+
+  p = jobs[job]->pipe;
+  while (p->next != jobs[job]->pipe)
+    p = p->next;
+
+  if (block)
+    UNBLOCK_CHILD (oset);
+
+  return (p->pid);
+}
+
+/* Wait for a particular child of the shell to finish executing.
+   This low-level function prints an error message if PID is not
+   a child of this shell.  It returns -1 if it fails, or whatever
+   wait_for returns otherwise.  If the child is not found in the
+   jobs table, it returns 127. */
+int
+wait_for_single_pid (pid)
+     pid_t pid;
+{
+  register PROCESS *child;
+  sigset_t set, oset;
+  int r, job;
+
+  BLOCK_CHILD (set, oset);
+  child = find_pipeline (pid, 0, (int *)NULL);
+  UNBLOCK_CHILD (oset);
+
+  if (child == 0)
+    {
+      internal_error (_("wait: pid %ld is not a child of this shell"), (long)pid);
+      return (127);
+    }
+
+  r = wait_for (pid);
+
+  /* POSIX.2: if we just waited for a job, we can remove it from the jobs
+     table. */
+  BLOCK_CHILD (set, oset);
+  job = find_job (pid, 0);
+  if (job != NO_JOB && jobs[job] && DEADJOB (job))
+    jobs[job]->flags |= J_NOTIFIED;
+  UNBLOCK_CHILD (oset);
+
+  return r;
+}
+
+/* Wait for all of the backgrounds of this shell to finish. */
+void
+wait_for_background_pids ()
+{
+  register int i, r, waited_for;
+  sigset_t set, oset;
+  pid_t pid;
+
+  for (waited_for = 0;;)
+    {
+      BLOCK_CHILD (set, oset);
+
+      /* find first running job; if none running in foreground, break */
+      for (i = 0; i < job_slots; i++)
+       if (jobs[i] && RUNNING (i) && IS_FOREGROUND (i) == 0)
+         break;
+
+      if (i == job_slots)
+       {
+         UNBLOCK_CHILD (oset);
+         break;
+       }
+
+      /* now wait for the last pid in that job. */
+      pid = find_last_pid (i, 0);
+      UNBLOCK_CHILD (oset);
+      QUIT;
+      errno = 0;               /* XXX */
+      r = wait_for_single_pid (pid);
+      if (r == -1)
+       {
+         /* If we're mistaken about job state, compensate. */
+         if (errno == ECHILD)
+           mark_all_jobs_as_dead ();
+       }
+      else
+       waited_for++;
+    }
+
+  /* POSIX.2 says the shell can discard the statuses of all completed jobs if
+     `wait' is called with no arguments. */
+  mark_dead_jobs_as_notified (1);
+  cleanup_dead_jobs ();
+}
+
+/* Make OLD_SIGINT_HANDLER the SIGINT signal handler. */
+#define INVALID_SIGNAL_HANDLER (SigHandler *)wait_for_background_pids
+static SigHandler *old_sigint_handler = INVALID_SIGNAL_HANDLER;
+
+static void
+restore_sigint_handler ()
+{
+  if (old_sigint_handler != INVALID_SIGNAL_HANDLER)
+    {
+      set_signal_handler (SIGINT, old_sigint_handler);
+      old_sigint_handler = INVALID_SIGNAL_HANDLER;
+    }
+}
+
+static int wait_sigint_received;
+
+/* Handle SIGINT while we are waiting for children in a script to exit.
+   The `wait' builtin should be interruptible, but all others should be
+   effectively ignored (i.e. not cause the shell to exit). */
+static sighandler
+wait_sigint_handler (sig)
+     int sig;
+{
+  SigHandler *sigint_handler;
+
+  if (interrupt_immediately ||
+      (this_shell_builtin && this_shell_builtin == wait_builtin))
+    {
+      last_command_exit_value = EXECUTION_FAILURE;
+      restore_sigint_handler ();
+      /* If we got a SIGINT while in `wait', and SIGINT is trapped, do
+        what POSIX.2 says (see builtins/wait.def for more info). */
+      if (this_shell_builtin && this_shell_builtin == wait_builtin &&
+         signal_is_trapped (SIGINT) &&
+         ((sigint_handler = trap_to_sighandler (SIGINT)) == trap_handler))
+       {
+         interrupt_immediately = 0;
+         trap_handler (SIGINT);        /* set pending_traps[SIGINT] */
+         wait_signal_received = SIGINT;
+         longjmp (wait_intr_buf, 1);
+       }
+      
+      ADDINTERRUPT;
+      QUIT;
+    }
+
+  /* XXX - should this be interrupt_state?  If it is, the shell will act
+     as if it got the SIGINT interrupt. */
+  wait_sigint_received = 1;
+
+  /* Otherwise effectively ignore the SIGINT and allow the running job to
+     be killed. */
+  SIGRETURN (0);
+}
+
+static int
+process_exit_signal (status)
+     WAIT status;
+{
+  return (WIFSIGNALED (status) ? WTERMSIG (status) : 0);
+}
+
+static int
+process_exit_status (status)
+     WAIT status;
+{
+  if (WIFSIGNALED (status))
+    return (128 + WTERMSIG (status));
+  else if (WIFSTOPPED (status) == 0)
+    return (WEXITSTATUS (status));
+  else
+    return (EXECUTION_SUCCESS);
+}
+
+/* Return the exit status of the last process in the pipeline for job JOB.
+   This is the exit status of the entire job. */
+static WAIT
+raw_job_exit_status (job)
+     int job;
+{
+  register PROCESS *p;
+  int fail;
+
+  if (pipefail_opt)
+    {
+      fail = 0;
+      for (p = jobs[job]->pipe; p->next != jobs[job]->pipe; p = p->next)
+        if (p->status != EXECUTION_SUCCESS) fail = p->status;
+      return fail;
+    }
+
+  for (p = jobs[job]->pipe; p->next != jobs[job]->pipe; p = p->next)
+    ;
+  return (p->status);
+}
+
+/* Return the exit status of job JOB.  This is the exit status of the last
+   (rightmost) process in the job's pipeline, modified if the job was killed
+   by a signal or stopped. */
+static int
+job_exit_status (job)
+     int job;
+{
+  return (process_exit_status (raw_job_exit_status (job)));
+}
+
+static int
+job_exit_signal (job)
+     int job;
+{
+  return (process_exit_signal (raw_job_exit_status (job)));
+}
+
+#define FIND_CHILD(pid, child) \
+  do \
+    { \
+      child = find_pipeline (pid, 0, (int *)NULL); \
+      if (child == 0) \
+       { \
+         give_terminal_to (shell_pgrp, 0); \
+         UNBLOCK_CHILD (oset); \
+         internal_error (_("wait_for: No record of process %ld"), (long)pid); \
+         restore_sigint_handler (); \
+         return (termination_state = 127); \
+       } \
+    } \
+  while (0)
+
+/* Wait for pid (one of our children) to terminate, then
+   return the termination state.  Returns 127 if PID is not found in
+   the jobs table.  Returns -1 if waitchld() returns -1, indicating
+   that there are no unwaited-for child processes. */
+int
+wait_for (pid)
+     pid_t pid;
+{
+  int job, termination_state, r;
+  WAIT s;
+  register PROCESS *child;
+  sigset_t set, oset;
+  register PROCESS *p;
+
+  /* In the case that this code is interrupted, and we longjmp () out of it,
+     we are relying on the code in throw_to_top_level () to restore the
+     top-level signal mask. */
+  BLOCK_CHILD (set, oset);
+
+  /* Ignore interrupts while waiting for a job run without job control
+     to finish.  We don't want the shell to exit if an interrupt is
+     received, only if one of the jobs run is killed via SIGINT.  If
+     job control is not set, the job will be run in the same pgrp as
+     the shell, and the shell will see any signals the job gets. */
+
+  /* This is possibly a race condition -- should it go in stop_pipeline? */
+  wait_sigint_received = 0;
+  if (job_control == 0)
+    old_sigint_handler = set_signal_handler (SIGINT, wait_sigint_handler);
+
+  termination_state = last_command_exit_value;
+
+  if (interactive && job_control == 0)
+    QUIT;
+
+  /* If we say wait_for (), then we have a record of this child somewhere.
+     If it and none of its peers are running, don't call waitchld(). */
+
+  job = NO_JOB;
+  do
+    {
+      FIND_CHILD (pid, child);
+
+      /* If this child is part of a job, then we are really waiting for the
+        job to finish.  Otherwise, we are waiting for the child to finish.
+        We check for JDEAD in case the job state has been set by waitchld
+        after receipt of a SIGCHLD. */
+      if (job == NO_JOB)
+       job = find_job (pid, 0);
+
+      /* waitchld() takes care of setting the state of the job.  If the job
+        has already exited before this is called, sigchld_handler will have
+        called waitchld and the state will be set to JDEAD. */
+
+      if (child->running || (job != NO_JOB && RUNNING (job)))
+       {
+#if defined (WAITPID_BROKEN)    /* SCOv4 */
+         sigset_t suspend_set;
+         sigemptyset (&suspend_set);
+         sigsuspend (&suspend_set);
+#else /* !WAITPID_BROKEN */
+#  if defined (MUST_UNBLOCK_CHLD)
+         struct sigaction act, oact;
+         sigset_t nullset, chldset;
+
+         sigemptyset (&nullset);
+         sigemptyset (&chldset);
+         sigprocmask (SIG_SETMASK, &nullset, &chldset);
+         act.sa_handler = SIG_DFL;
+         sigemptyset (&act.sa_mask);
+         sigemptyset (&oact.sa_mask);
+         act.sa_flags = 0;
+         sigaction (SIGCHLD, &act, &oact);
+#  endif
+         queue_sigchld = 1;
+         r = waitchld (pid, 1);
+#  if defined (MUST_UNBLOCK_CHLD)
+         sigaction (SIGCHLD, &oact, (struct sigaction *)NULL);
+         sigprocmask (SIG_SETMASK, &chldset, (sigset_t *)NULL);
+#  endif
+         queue_sigchld = 0;
+         if (r == -1 && errno == ECHILD && this_shell_builtin == wait_builtin)
+           {
+             termination_state = -1;
+             goto wait_for_return;
+           }
+
+         /* If child is marked as running, but waitpid() returns -1/ECHILD,
+            there is something wrong.  Somewhere, wait should have returned
+            that child's pid.  Mark the child as not running and the job,
+            if it exists, as JDEAD. */
+         if (r == -1 && errno == ECHILD)
+           {
+             child->running = PS_DONE;
+             child->status = 0;        /* XXX -- can't find true status */
+             if (job != NO_JOB)
+               jobs[job]->state = JDEAD;
+           }
+#endif /* WAITPID_BROKEN */
+       }
+
+      /* If the shell is interactive, and job control is disabled, see
+        if the foreground process has died due to SIGINT and jump out
+        of the wait loop if it has.  waitchld has already restored the
+        old SIGINT signal handler. */
+      if (interactive && job_control == 0)
+       QUIT;
+    }
+  while (child->running || (job != NO_JOB && RUNNING (job)));
+
+  /* The exit state of the command is either the termination state of the
+     child, or the termination state of the job.  If a job, the status
+     of the last child in the pipeline is the significant one.  If the command
+     or job was terminated by a signal, note that value also. */
+  termination_state = (job != NO_JOB) ? job_exit_status (job)
+                                     : process_exit_status (child->status);
+  last_command_exit_signal = (job != NO_JOB) ? job_exit_signal (job)
+                                            : process_exit_signal (child->status);
+
+  if (job == NO_JOB || IS_JOBCONTROL (job))
+    {
+      /* XXX - under what circumstances is a job not present in the jobs
+        table (job == NO_JOB)?
+               1.  command substitution
+
+        In the case of command substitution, at least, it's probably not
+        the right thing to give the terminal to the shell's process group,
+        even though there is code in subst.c:command_substitute to work
+        around it.
+
+        Things that don't:
+               $PROMPT_COMMAND execution
+               process substitution
+       */
+#if 0
+if (job == NO_JOB)
+  itrace("wait_for: job == NO_JOB, giving the terminal to shell_pgrp (%ld)", (long)shell_pgrp);
+#endif
+
+      give_terminal_to (shell_pgrp, 0);
+    }
+
+  /* If the command did not exit cleanly, or the job is just
+     being stopped, then reset the tty state back to what it
+     was before this command.  Reset the tty state and notify
+     the user of the job termination only if the shell is
+     interactive.  Clean up any dead jobs in either case. */
+  if (job != NO_JOB)
+    {
+      if (interactive_shell && subshell_environment == 0)
+       {
+         /* This used to use `child->status'.  That's wrong, however, for
+            pipelines.  `child' is the first process in the pipeline.  It's
+            likely that the process we want to check for abnormal termination
+            or stopping is the last process in the pipeline, especially if
+            it's long-lived and the first process is short-lived.  Since we
+            know we have a job here, we can check all the processes in this
+            job's pipeline and see if one of them stopped or terminated due
+            to a signal.  We might want to change this later to just check
+            the last process in the pipeline.  If no process exits due to a
+            signal, S is left as the status of the last job in the pipeline. */
+         p = jobs[job]->pipe;
+         do
+           {
+             s = p->status;
+             if (WIFSIGNALED(s) || WIFSTOPPED(s))
+               break;
+             p = p->next;
+           }
+         while (p != jobs[job]->pipe);
+
+         if (WIFSIGNALED (s) || WIFSTOPPED (s))
+           {
+             set_tty_state ();
+
+             /* If the current job was stopped or killed by a signal, and
+                the user has requested it, get a possibly new window size */
+             if (check_window_size && (job == current_job || IS_FOREGROUND (job)))
+               get_new_window_size (0);
+           }
+         else
+           get_tty_state ();
+
+         /* If job control is enabled, the job was started with job
+            control, the job was the foreground job, and it was killed
+            by SIGINT, then print a newline to compensate for the kernel
+            printing the ^C without a trailing newline. */
+         if (job_control && IS_JOBCONTROL (job) && IS_FOREGROUND (job) &&
+               WIFSIGNALED (s) && WTERMSIG (s) == SIGINT)
+           {
+             /* If SIGINT is not trapped and the shell is in a for, while,
+                or until loop, act as if the shell received SIGINT as
+                well, so the loop can be broken.  This doesn't call the
+                SIGINT signal handler; maybe it should. */
+             if (signal_is_trapped (SIGINT) == 0 && loop_level)
+               ADDINTERRUPT;
+             else
+               {
+                 putchar ('\n');
+                 fflush (stdout);
+               }
+           }
+       }
+
+      /* Moved here from set_job_status_and_cleanup, which is in the SIGCHLD
+         signal handler path */
+      if (DEADJOB (job) && IS_FOREGROUND (job) /*&& subshell_environment == 0*/)
+       setjstatus (job);
+
+      /* If this job is dead, notify the user of the status.  If the shell
+        is interactive, this will display a message on the terminal.  If
+        the shell is not interactive, make sure we turn on the notify bit
+        so we don't get an unwanted message about the job's termination,
+        and so delete_job really clears the slot in the jobs table. */
+      notify_and_cleanup ();
+    }
+
+wait_for_return:
+
+  UNBLOCK_CHILD (oset);
+
+  /* Restore the original SIGINT signal handler before we return. */
+  restore_sigint_handler ();
+
+  return (termination_state);
+}
+
+/* Wait for the last process in the pipeline for JOB.  Returns whatever
+   wait_for returns: the last process's termination state or -1 if there
+   are no unwaited-for child processes or an error occurs. */
+int
+wait_for_job (job)
+     int job;
+{
+  pid_t pid;
+  int r;
+  sigset_t set, oset;
+
+  BLOCK_CHILD(set, oset);
+  if (JOBSTATE (job) == JSTOPPED)
+    internal_warning (_("wait_for_job: job %d is stopped"), job+1);
+
+  pid = find_last_pid (job, 0);
+  UNBLOCK_CHILD(oset);
+  r = wait_for (pid);
+
+  /* POSIX.2: we can remove the job from the jobs table if we just waited
+     for it. */
+  BLOCK_CHILD (set, oset);
+  if (job != NO_JOB && jobs[job] && DEADJOB (job))
+    jobs[job]->flags |= J_NOTIFIED;
+  UNBLOCK_CHILD (oset);
+
+  return r;
+}
+
+/* Print info about dead jobs, and then delete them from the list
+   of known jobs.  This does not actually delete jobs when the
+   shell is not interactive, because the dead jobs are not marked
+   as notified. */
+void
+notify_and_cleanup ()
+{
+  if (jobs_list_frozen)
+    return;
+
+  if (interactive || interactive_shell == 0 || sourcelevel)
+    notify_of_job_status ();
+
+  cleanup_dead_jobs ();
+}
+
+/* Make dead jobs disappear from the jobs array without notification.
+   This is used when the shell is not interactive. */
+void
+reap_dead_jobs ()
+{
+  mark_dead_jobs_as_notified (0);
+  cleanup_dead_jobs ();
+}
+
+/* Return the next closest (chronologically) job to JOB which is in
+   STATE.  STATE can be JSTOPPED, JRUNNING.  NO_JOB is returned if
+   there is no next recent job. */
+static int
+most_recent_job_in_state (job, state)
+     int job;
+     JOB_STATE state;
+{
+  register int i, result;
+  sigset_t set, oset;
+
+  BLOCK_CHILD (set, oset);
+
+  for (result = NO_JOB, i = job - 1; i >= 0; i--)
+    {
+      if (jobs[i] && (JOBSTATE (i) == state))
+       {
+         result = i;
+         break;
+       }
+    }
+
+  UNBLOCK_CHILD (oset);
+
+  return (result);
+}
+
+/* Return the newest *stopped* job older than JOB, or NO_JOB if not
+   found. */
+static int
+job_last_stopped (job)
+     int job;
+{
+  return (most_recent_job_in_state (job, JSTOPPED));
+}
+
+/* Return the newest *running* job older than JOB, or NO_JOB if not
+   found. */
+static int
+job_last_running (job)
+     int job;
+{
+  return (most_recent_job_in_state (job, JRUNNING));
+}
+
+/* Make JOB be the current job, and make previous be useful.  Must be
+   called with SIGCHLD blocked. */
+static void
+set_current_job (job)
+     int job;
+{
+  int candidate;
+
+  if (current_job != job)
+    {
+      previous_job = current_job;
+      current_job = job;
+    }
+
+  /* First choice for previous_job is the old current_job. */
+  if (previous_job != current_job &&
+      previous_job != NO_JOB &&
+      jobs[previous_job] &&
+      STOPPED (previous_job))
+    return;
+
+  /* Second choice:  Newest stopped job that is older than
+     the current job. */
+  candidate = NO_JOB;
+  if (STOPPED (current_job))
+    {
+      candidate = job_last_stopped (current_job);
+
+      if (candidate != NO_JOB)
+       {
+         previous_job = candidate;
+         return;
+       }
+    }
+
+  /* If we get here, there is either only one stopped job, in which case it is
+     the current job and the previous job should be set to the newest running
+     job, or there are only running jobs and the previous job should be set to
+     the newest running job older than the current job.  We decide on which
+     alternative to use based on whether or not JOBSTATE(current_job) is
+     JSTOPPED. */
+
+  candidate = RUNNING (current_job) ? job_last_running (current_job)
+                                   : job_last_running (job_slots);
+
+  if (candidate != NO_JOB)
+    {
+      previous_job = candidate;
+      return;
+    }
+
+  /* There is only a single job, and it is both `+' and `-'. */
+  previous_job = current_job;
+}
+
+/* Make current_job be something useful, if it isn't already. */
+
+/* Here's the deal:  The newest non-running job should be `+', and the
+   next-newest non-running job should be `-'.  If there is only a single
+   stopped job, the previous_job is the newest non-running job.  If there
+   are only running jobs, the newest running job is `+' and the
+   next-newest running job is `-'.  Must be called with SIGCHLD blocked. */
+
+static void
+reset_current ()
+{
+  int candidate;
+
+  if (job_slots && current_job != NO_JOB && jobs[current_job] && STOPPED (current_job))
+    candidate = current_job;
+  else
+    {
+      candidate = NO_JOB;
+
+      /* First choice: the previous job. */
+      if (previous_job != NO_JOB && jobs[previous_job] && STOPPED (previous_job))
+       candidate = previous_job;
+
+      /* Second choice: the most recently stopped job. */
+      if (candidate == NO_JOB)
+       candidate = job_last_stopped (job_slots);
+
+      /* Third choice: the newest running job. */
+      if (candidate == NO_JOB)
+       candidate = job_last_running (job_slots);
+    }
+
+  /* If we found a job to use, then use it.  Otherwise, there
+     are no jobs period. */
+  if (candidate != NO_JOB)
+    set_current_job (candidate);
+  else
+    current_job = previous_job = NO_JOB;
+}
+
+/* Set up the job structures so we know the job and its processes are
+   all running. */
+static void
+set_job_running (job)
+     int job;
+{
+  register PROCESS *p;
+
+  /* Each member of the pipeline is now running. */
+  p = jobs[job]->pipe;
+
+  do
+    {
+      if (WIFSTOPPED (p->status))
+       p->running = PS_RUNNING;        /* XXX - could be PS_STOPPED */
+      p = p->next;
+    }
+  while (p != jobs[job]->pipe);
+
+  /* This means that the job is running. */
+  JOBSTATE (job) = JRUNNING;
+}
+
+/* Start a job.  FOREGROUND if non-zero says to do that.  Otherwise,
+   start the job in the background.  JOB is a zero-based index into
+   JOBS.  Returns -1 if it is unable to start a job, and the return
+   status of the job otherwise. */
+int
+start_job (job, foreground)
+     int job, foreground;
+{
+  register PROCESS *p;
+  int already_running;
+  sigset_t set, oset;
+  char *wd;
+  static TTYSTRUCT save_stty;
+
+  BLOCK_CHILD (set, oset);
+
+  if (DEADJOB (job))
+    {
+      internal_error (_("%s: job has terminated"), this_command_name);
+      UNBLOCK_CHILD (oset);
+      return (-1);
+    }
+
+  already_running = RUNNING (job);
+
+  if (foreground == 0 && already_running)
+    {
+      internal_error (_("%s: job %d already in background"), this_command_name, job + 1);
+      UNBLOCK_CHILD (oset);
+      return (-1);
+    }
+
+  wd = current_working_directory ();
+
+  /* You don't know about the state of this job.  Do you? */
+  jobs[job]->flags &= ~J_NOTIFIED;
+
+  if (foreground)
+    {
+      set_current_job (job);
+      jobs[job]->flags |= J_FOREGROUND;
+    }
+
+  /* Tell the outside world what we're doing. */
+  p = jobs[job]->pipe;
+
+  if (foreground == 0)
+    fprintf (stderr, "[%d]%c ", job + 1,
+          (job == current_job) ? '+': ((job == previous_job) ? '-' : ' '));
+
+  do
+    {
+      fprintf (stderr, "%s%s",
+              p->command ? p->command : "",
+              p->next != jobs[job]->pipe? " | " : "");
+      p = p->next;
+    }
+  while (p != jobs[job]->pipe);
+
+  if (foreground == 0)
+    fprintf (stderr, " &");
+
+  if (strcmp (wd, jobs[job]->wd) != 0)
+    fprintf (stderr, " (wd: %s)", polite_directory_format (jobs[job]->wd));
+
+  fprintf (stderr, "\n");
+
+  /* Run the job. */
+  if (already_running == 0)
+    set_job_running (job);
+
+  /* Save the tty settings before we start the job in the foreground. */
+  if (foreground)
+    {
+      get_tty_state ();
+      save_stty = shell_tty_info;
+      /* Give the terminal to this job. */
+      if (IS_JOBCONTROL (job))
+       give_terminal_to (jobs[job]->pgrp, 0);
+    }
+  else
+    jobs[job]->flags &= ~J_FOREGROUND;
+
+  /* If the job is already running, then don't bother jump-starting it. */
+  if (already_running == 0)
+    {
+      jobs[job]->flags |= J_NOTIFIED;
+      killpg (jobs[job]->pgrp, SIGCONT);
+    }
+
+  if (foreground)
+    {
+      pid_t pid;
+      int s;
+
+      pid = find_last_pid (job, 0);
+      UNBLOCK_CHILD (oset);
+      s = wait_for (pid);
+      shell_tty_info = save_stty;
+      set_tty_state ();
+      return (s);
+    }
+  else
+    {
+      reset_current ();
+      UNBLOCK_CHILD (oset);
+      return (0);
+    }
+}
+
+/* Give PID SIGNAL.  This determines what job the pid belongs to (if any).
+   If PID does belong to a job, and the job is stopped, then CONTinue the
+   job after giving it SIGNAL.  Returns -1 on failure.  If GROUP is non-null,
+   then kill the process group associated with PID. */
+int
+kill_pid (pid, sig, group)
+     pid_t pid;
+     int sig, group;
+{
+  register PROCESS *p;
+  int job, result;
+  sigset_t set, oset;
+
+  result = EXECUTION_SUCCESS;
+  if (group)
+    {
+      BLOCK_CHILD (set, oset);
+      p = find_pipeline (pid, 0, &job);
+
+      if (job != NO_JOB)
+       {
+         jobs[job]->flags &= ~J_NOTIFIED;
+
+         /* Kill process in backquotes or one started without job control? */
+         if (jobs[job]->pgrp == shell_pgrp)
+           {
+             p = jobs[job]->pipe;
+
+             do
+               {
+                 kill (p->pid, sig);
+                 if (p->running == PS_DONE && (sig == SIGTERM || sig == SIGHUP))
+                   kill (p->pid, SIGCONT);
+                 p = p->next;
+               }
+             while (p != jobs[job]->pipe);
+           }
+         else
+           {
+             result = killpg (jobs[job]->pgrp, sig);
+             if (p && STOPPED (job) && (sig == SIGTERM || sig == SIGHUP))
+               killpg (jobs[job]->pgrp, SIGCONT);
+             /* If we're continuing a stopped job via kill rather than bg or
+                fg, emulate the `bg' behavior. */
+             if (p && STOPPED (job) && (sig == SIGCONT))
+               {
+                 set_job_running (job);
+                 jobs[job]->flags &= ~J_FOREGROUND;
+                 jobs[job]->flags |= J_NOTIFIED;
+               }
+           }
+       }
+      else
+       result = killpg (pid, sig);
+
+      UNBLOCK_CHILD (oset);
+    }
+  else
+    result = kill (pid, sig);
+
+  return (result);
+}
+
+/* sigchld_handler () flushes at least one of the children that we are
+   waiting for.  It gets run when we have gotten a SIGCHLD signal. */
+static sighandler
+sigchld_handler (sig)
+     int sig;
+{
+  int n, oerrno;
+
+  oerrno = errno;
+  REINSTALL_SIGCHLD_HANDLER;
+  sigchld++;
+  n = 0;
+  if (queue_sigchld == 0)
+    n = waitchld (-1, 0);
+  errno = oerrno;
+  SIGRETURN (n);
+}
+
+/* waitchld() reaps dead or stopped children.  It's called by wait_for and
+   sigchld_handler, and runs until there aren't any children terminating any
+   more.
+   If BLOCK is 1, this is to be a blocking wait for a single child, although
+   an arriving SIGCHLD could cause the wait to be non-blocking.  It returns
+   the number of children reaped, or -1 if there are no unwaited-for child
+   processes. */
+static int
+waitchld (wpid, block)
+     pid_t wpid;
+     int block;
+{
+  WAIT status;
+  PROCESS *child;
+  pid_t pid;
+  int call_set_current, last_stopped_job, job, children_exited, waitpid_flags;
+
+  call_set_current = children_exited = 0;
+  last_stopped_job = NO_JOB;
+
+  do
+    {
+      /* We don't want to be notified about jobs stopping if job control
+        is not active.  XXX - was interactive_shell instead of job_control */
+      waitpid_flags = (job_control && subshell_environment == 0)
+                       ? (WUNTRACED|WCONTINUED)
+                       : 0;
+      if (sigchld || block == 0)
+       waitpid_flags |= WNOHANG;
+      pid = WAITPID (-1, &status, waitpid_flags);
+
+      /* The check for WNOHANG is to make sure we decrement sigchld only
+        if it was non-zero before we called waitpid. */
+      if (sigchld > 0 && (waitpid_flags & WNOHANG))
+       sigchld--;
+  
+      /* If waitpid returns -1 with errno == ECHILD, there are no more
+        unwaited-for child processes of this shell. */
+      if (pid < 0 && errno == ECHILD)
+       {
+         if (children_exited == 0)
+           return -1;
+         else
+           break;
+       }
+
+      /* If waitpid returns 0, there are running children.  If it returns -1,
+        the only other error POSIX says it can return is EINTR. */
+      if (pid <= 0)
+       continue;       /* jumps right to the test */
+
+      /* children_exited is used to run traps on SIGCHLD.  We don't want to
+         run the trap if a process is just being continued. */
+      if (WIFCONTINUED(status) == 0)
+       children_exited++;
+
+      /* Locate our PROCESS for this pid. */
+      child = find_pipeline (pid, 1, &job);    /* want running procs only */
+
+      /* It is not an error to have a child terminate that we did
+        not have a record of.  This child could have been part of
+        a pipeline in backquote substitution.  Even so, I'm not
+        sure child is ever non-zero. */
+      if (child == 0)
+       continue;
+
+      while (child->pid != pid)
+       child = child->next;
+
+      /* Remember status, and whether or not the process is running. */
+      child->status = status;
+      child->running = WIFCONTINUED(status) ? PS_RUNNING : PS_DONE;
+
+      if (job == NO_JOB)
+       continue;
+
+      call_set_current += set_job_status_and_cleanup (job);
+
+      if (STOPPED (job))
+       last_stopped_job = job;
+      else if (DEADJOB (job) && last_stopped_job == job)
+       last_stopped_job = NO_JOB;
+    }
+  while ((sigchld || block == 0) && pid > (pid_t)0);
+
+  /* If a job was running and became stopped, then set the current
+     job.  Otherwise, don't change a thing. */
+  if (call_set_current)
+    {
+      if (last_stopped_job != NO_JOB)
+       set_current_job (last_stopped_job);
+      else
+       reset_current ();
+    }
+
+  /* Call a SIGCHLD trap handler for each child that exits, if one is set. */
+  if (job_control && signal_is_trapped (SIGCHLD) && children_exited &&
+      trap_list[SIGCHLD] != (char *)IGNORE_SIG)
+    run_sigchld_trap (children_exited);
+
+  /* We have successfully recorded the useful information about this process
+     that has just changed state.  If we notify asynchronously, and the job
+     that this process belongs to is no longer running, then notify the user
+     of that fact now. */
+  if (asynchronous_notification && interactive)
+    notify_of_job_status ();
+
+  return (children_exited);
+}
+
+/* Set the status of JOB and perform any necessary cleanup if the job is
+   marked as JDEAD.
+
+   Currently, the cleanup activity is restricted to handling any SIGINT
+   received while waiting for a foreground job to finish. */
+static int
+set_job_status_and_cleanup (job)
+     int job;
+{
+  PROCESS *child;
+  int tstatus, job_state, any_stopped, any_tstped, call_set_current;
+  SigHandler *temp_handler;
+
+  child = jobs[job]->pipe;
+  jobs[job]->flags &= ~J_NOTIFIED;
+
+  call_set_current = 0;
+
+  /*
+   * COMPUTE JOB STATUS
+   */
+
+  /* If all children are not running, but any of them is  stopped, then
+     the job is stopped, not dead. */
+  job_state = any_stopped = any_tstped = 0;
+  do
+    {
+      job_state |= child->running;
+      if (child->running == PS_DONE && (WIFSTOPPED (child->status)))
+       {
+         any_stopped = 1;
+         any_tstped |= interactive && job_control &&
+                           (WSTOPSIG (child->status) == SIGTSTP);
+       }
+      child = child->next;
+    }
+  while (child != jobs[job]->pipe);
+
+  /* If job_state != 0, the job is still running, so don't bother with
+     setting the process exit status and job state unless we're
+     transitioning from stopped to running. */
+  if (job_state != 0 && JOBSTATE(job) != JSTOPPED)
+    return 0;
+
+  /*
+   * SET JOB STATUS
+   */
+
+  /* The job is either stopped or dead.  Set the state of the job accordingly. */
+  if (any_stopped)
+    {
+      jobs[job]->state = JSTOPPED;
+      jobs[job]->flags &= ~J_FOREGROUND;
+      call_set_current++;
+      /* Suspending a job with SIGTSTP breaks all active loops. */
+      if (any_tstped && loop_level)
+       breaking = loop_level;
+    }
+  else if (job_state != 0)     /* was stopped, now running */
+    {
+      jobs[job]->state = JRUNNING;
+      call_set_current++;
+    }
+  else
+    {
+      jobs[job]->state = JDEAD;
+
+#if 0
+      if (IS_FOREGROUND (job))
+       setjstatus (job);
+#endif
+
+      /* If this job has a cleanup function associated with it, call it
+        with `cleanarg' as the single argument, then set the function
+        pointer to NULL so it is not inadvertently called twice.  The
+        cleanup function is responsible for deallocating cleanarg. */
+      if (jobs[job]->j_cleanup)
+       {
+         (*jobs[job]->j_cleanup) (jobs[job]->cleanarg);
+         jobs[job]->j_cleanup = (sh_vptrfunc_t *)NULL;
+       }
+    }
+
+  /*
+   * CLEANUP
+   *
+   * Currently, we just do special things if we got a SIGINT while waiting
+   * for a foreground job to complete
+   */
+
+  if (jobs[job]->state == JDEAD)
+    {
+      /* If we're running a shell script and we get a SIGINT with a
+        SIGINT trap handler, but the foreground job handles it and
+        does not exit due to SIGINT, run the trap handler but do not
+        otherwise act as if we got the interrupt. */
+      if (wait_sigint_received && interactive_shell == 0 &&
+         WIFSIGNALED (child->status) == 0 && IS_FOREGROUND (job) &&
+         signal_is_trapped (SIGINT))
+       {
+         int old_frozen;
+         wait_sigint_received = 0;
+         last_command_exit_value = process_exit_status (child->status);
+
+         old_frozen = jobs_list_frozen;
+         jobs_list_frozen = 1;
+         tstatus = maybe_call_trap_handler (SIGINT);
+         jobs_list_frozen = old_frozen;
+       }
+
+      /* If the foreground job is killed by SIGINT when job control is not
+        active, we need to perform some special handling.
+
+        The check of wait_sigint_received is a way to determine if the
+        SIGINT came from the keyboard (in which case the shell has already
+        seen it, and wait_sigint_received is non-zero, because keyboard
+        signals are sent to process groups) or via kill(2) to the foreground
+        process by another process (or itself).  If the shell did receive the
+        SIGINT, it needs to perform normal SIGINT processing. */
+      else if (wait_sigint_received && (WTERMSIG (child->status) == SIGINT) &&
+             IS_FOREGROUND (job) && IS_JOBCONTROL (job) == 0)
+       {
+         int old_frozen;
+
+         wait_sigint_received = 0;
+
+         /* If SIGINT is trapped, set the exit status so that the trap
+            handler can see it. */
+         if (signal_is_trapped (SIGINT))
+           last_command_exit_value = process_exit_status (child->status);
+
+         /* If the signal is trapped, let the trap handler get it no matter
+            what and simply return if the trap handler returns.
+           maybe_call_trap_handler() may cause dead jobs to be removed from
+           the job table because of a call to execute_command.  We work
+           around this by setting JOBS_LIST_FROZEN. */
+         old_frozen = jobs_list_frozen;
+         jobs_list_frozen = 1;
+         tstatus = maybe_call_trap_handler (SIGINT);
+         jobs_list_frozen = old_frozen;
+         if (tstatus == 0 && old_sigint_handler != INVALID_SIGNAL_HANDLER)
+           {
+             /* wait_sigint_handler () has already seen SIGINT and
+                allowed the wait builtin to jump out.  We need to
+                call the original SIGINT handler, if necessary.  If
+                the original handler is SIG_DFL, we need to resend
+                the signal to ourselves. */
+
+             temp_handler = old_sigint_handler;
+
+             /* Bogus.  If we've reset the signal handler as the result
+                of a trap caught on SIGINT, then old_sigint_handler
+                will point to trap_handler, which now knows nothing about
+                SIGINT (if we reset the sighandler to the default).
+                In this case, we have to fix things up.  What a crock. */
+             if (temp_handler == trap_handler && signal_is_trapped (SIGINT) == 0)
+                 temp_handler = trap_to_sighandler (SIGINT);
+               restore_sigint_handler ();
+               if (temp_handler == SIG_DFL)
+                 termination_unwind_protect (SIGINT);
+               else if (temp_handler != SIG_IGN)
+                 (*temp_handler) (SIGINT);
+           }
+       }
+    }
+
+  return call_set_current;
+}
+
+/* Build the array of values for the $PIPESTATUS variable from the set of
+   exit statuses of all processes in the job J. */
+static void
+setjstatus (j)
+     int j;
+{
+#if defined (ARRAY_VARS)
+  register int i;
+  register PROCESS *p;
+
+  for (i = 1, p = jobs[j]->pipe; p->next != jobs[j]->pipe; p = p->next, i++)
+    ;
+  i++;
+  if (statsize < i)
+    {
+      pstatuses = (int *)xrealloc (pstatuses, i * sizeof (int));
+      statsize = i;
+    }
+  i = 0;
+  p = jobs[j]->pipe;
+  do
+    {
+      pstatuses[i++] = process_exit_status (p->status);
+      p = p->next;
+    }
+  while (p != jobs[j]->pipe);
+
+  pstatuses[i] = -1;   /* sentinel */
+  set_pipestatus_array (pstatuses, i);
+#endif
+}
+
+static void
+run_sigchld_trap (nchild)
+     int nchild;
+{
+  char *trap_command;
+  int i;
+
+  /* Turn off the trap list during the call to parse_and_execute ()
+     to avoid potentially infinite recursive calls.  Preserve the
+     values of last_command_exit_value, last_made_pid, and the_pipeline
+     around the execution of the trap commands. */
+  trap_command = savestring (trap_list[SIGCHLD]);
+
+  begin_unwind_frame ("SIGCHLD trap");
+  unwind_protect_int (last_command_exit_value);
+  unwind_protect_int (last_command_exit_signal);
+  unwind_protect_var (last_made_pid);
+  unwind_protect_int (interrupt_immediately);
+  unwind_protect_int (jobs_list_frozen);
+  unwind_protect_pointer (the_pipeline);
+  unwind_protect_pointer (subst_assign_varlist);
+
+  /* We have to add the commands this way because they will be run
+     in reverse order of adding.  We don't want maybe_set_sigchld_trap ()
+     to reference freed memory. */
+  add_unwind_protect (xfree, trap_command);
+  add_unwind_protect (maybe_set_sigchld_trap, trap_command);
+
+  subst_assign_varlist = (WORD_LIST *)NULL;
+  the_pipeline = (PROCESS *)NULL;
+
+  restore_default_signal (SIGCHLD);
+  jobs_list_frozen = 1;
+  for (i = 0; i < nchild; i++)
+    {
+      interrupt_immediately = 1;
+      parse_and_execute (savestring (trap_command), "trap", SEVAL_NOHIST|SEVAL_RESETLINE);
+    }
+
+  run_unwind_frame ("SIGCHLD trap");
+}
+
+/* Function to call when you want to notify people of changes
+   in job status.  This prints out all jobs which are pending
+   notification to stderr, and marks those printed as already
+   notified, thus making them candidates for cleanup. */
+static void
+notify_of_job_status ()
+{
+  register int job, termsig;
+  char *dir;
+  sigset_t set, oset;
+  WAIT s;
+
+  if (jobs == 0 || job_slots == 0)
+    return;
+
+  if (old_ttou != 0)
+    {
+      sigemptyset (&set);
+      sigaddset (&set, SIGCHLD);
+      sigaddset (&set, SIGTTOU);
+      sigemptyset (&oset);
+      sigprocmask (SIG_BLOCK, &set, &oset);
+    }
+  else
+    queue_sigchld++;
+
+  for (job = 0, dir = (char *)NULL; job < job_slots; job++)
+    {
+      if (jobs[job] && IS_NOTIFIED (job) == 0)
+       {
+         s = raw_job_exit_status (job);
+         termsig = WTERMSIG (s);
+
+         /* POSIX.2 says we have to hang onto the statuses of at most the
+            last CHILD_MAX background processes if the shell is running a
+            script.  If the shell is not interactive, don't print anything
+            unless the job was killed by a signal. */
+         if (startup_state == 0 && WIFSIGNALED (s) == 0 &&
+               ((DEADJOB (job) && IS_FOREGROUND (job) == 0) || STOPPED (job)))
+           continue;
+         
+#if 0
+         /* If job control is disabled, don't print the status messages.
+            Mark dead jobs as notified so that they get cleaned up.  If
+            startup_state == 2, we were started to run `-c command', so
+            don't print anything. */
+         if ((job_control == 0 && interactive_shell) || startup_state == 2)
+#else
+         /* If job control is disabled, don't print the status messages.
+            Mark dead jobs as notified so that they get cleaned up.  If
+            startup_state == 2 and subshell_environment has the
+            SUBSHELL_COMSUB bit turned on, we were started to run a command
+            substitution, so don't print anything. */
+         if ((job_control == 0 && interactive_shell) ||
+             (startup_state == 2 && (subshell_environment & SUBSHELL_COMSUB)))
+#endif
+           {
+             /* POSIX.2 compatibility:  if the shell is not interactive,
+                hang onto the job corresponding to the last asynchronous
+                pid until the user has been notified of its status or does
+                a `wait'. */
+             if (DEADJOB (job) && (interactive_shell || (find_last_pid (job, 0) != last_asynchronous_pid)))
+               jobs[job]->flags |= J_NOTIFIED;
+             continue;
+           }
+
+         /* Print info on jobs that are running in the background,
+            and on foreground jobs that were killed by anything
+            except SIGINT (and possibly SIGPIPE). */
+         switch (JOBSTATE (job))
+           {
+           case JDEAD:
+             if (interactive_shell == 0 && termsig && WIFSIGNALED (s) &&
+                 termsig != SIGINT &&
+#if defined (DONT_REPORT_SIGPIPE)
+                 termsig != SIGPIPE &&
+#endif
+                 signal_is_trapped (termsig) == 0)
+               {
+                 /* Don't print `0' for a line number. */
+                 fprintf (stderr, "%s: line %d: ", get_name_for_error (), (line_number == 0) ? 1 : line_number);
+                 pretty_print_job (job, JLIST_NONINTERACTIVE, stderr);
+               }
+             else if (IS_FOREGROUND (job))
+               {
+#if !defined (DONT_REPORT_SIGPIPE)
+                 if (termsig && WIFSIGNALED (s) && termsig != SIGINT)
+#else
+                 if (termsig && WIFSIGNALED (s) && termsig != SIGINT && termsig != SIGPIPE)
+#endif
+                   {
+                     fprintf (stderr, "%s", j_strsignal (termsig));
+
+                     if (WIFCORED (s))
+                       fprintf (stderr, " (core dumped)");
+
+                     fprintf (stderr, "\n");
+                   }
+               }
+             else
+               {
+                 if (dir == 0)
+                   dir = current_working_directory ();
+                 pretty_print_job (job, JLIST_STANDARD, stderr);
+                 if (dir && strcmp (dir, jobs[job]->wd) != 0)
+                   fprintf (stderr,
+                            "(wd now: %s)\n", polite_directory_format (dir));
+               }
+
+             jobs[job]->flags |= J_NOTIFIED;
+             break;
+
+           case JSTOPPED:
+             fprintf (stderr, "\n");
+             if (dir == 0)
+               dir = current_working_directory ();
+             pretty_print_job (job, JLIST_STANDARD, stderr);
+             if (dir && (strcmp (dir, jobs[job]->wd) != 0))
+               fprintf (stderr,
+                        "(wd now: %s)\n", polite_directory_format (dir));
+             jobs[job]->flags |= J_NOTIFIED;
+             break;
+
+           case JRUNNING:
+           case JMIXED:
+             break;
+
+           default:
+             programming_error ("notify_of_job_status");
+           }
+       }
+    }
+  if (old_ttou != 0)
+    sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL);
+  else
+    queue_sigchld--;
+}
+
+/* Initialize the job control mechanism, and set up the tty stuff. */
+int
+initialize_job_control (force)
+     int force;
+{
+  shell_pgrp = getpgid (0);
+
+  if (shell_pgrp == -1)
+    {
+      sys_error ("initialize_job_control: getpgrp failed");
+      exit (1);
+    }
+
+  /* We can only have job control if we are interactive. */
+  if (interactive == 0)
+    {
+      job_control = 0;
+      original_pgrp = NO_PID;
+      shell_tty = fileno (stderr);
+    }
+  else
+    {
+      /* Get our controlling terminal.  If job_control is set, or
+        interactive is set, then this is an interactive shell no
+        matter where fd 2 is directed. */
+      shell_tty = dup (fileno (stderr));       /* fd 2 */
+
+      shell_tty = move_to_high_fd (shell_tty, 1, -1);
+
+      /* Compensate for a bug in systems that compiled the BSD
+        rlogind with DEBUG defined, like NeXT and Alliant. */
+      if (shell_pgrp == 0)
+       {
+         shell_pgrp = getpid ();
+         setpgid (0, shell_pgrp);
+         tcsetpgrp (shell_tty, shell_pgrp);
+       }
+
+      while ((terminal_pgrp = tcgetpgrp (shell_tty)) != -1)
+       {
+         if (shell_pgrp != terminal_pgrp)
+           {
+             SigHandler *ottin;
+
+             ottin = set_signal_handler(SIGTTIN, SIG_DFL);
+             kill (0, SIGTTIN);
+             set_signal_handler (SIGTTIN, ottin);
+             continue;
+           }
+         break;
+       }
+
+      /* Make sure that we are using the new line discipline. */
+      if (set_new_line_discipline (shell_tty) < 0)
+       {
+         sys_error ("initialize_job_control: line discipline");
+         job_control = 0;
+       }
+      else
+       {
+         original_pgrp = shell_pgrp;
+         shell_pgrp = getpid ();
+
+         if ((original_pgrp != shell_pgrp) && (setpgid (0, shell_pgrp) < 0))
+           {
+             sys_error ("initialize_job_control: setpgid");
+             shell_pgrp = original_pgrp;
+           }
+
+         job_control = 1;
+
+         /* If (and only if) we just set our process group to our pid,
+            thereby becoming a process group leader, and the terminal
+            is not in the same process group as our (new) process group,
+            then set the terminal's process group to our (new) process
+            group.  If that fails, set our process group back to what it
+            was originally (so we can still read from the terminal) and
+            turn off job control.  */
+         if (shell_pgrp != original_pgrp && shell_pgrp != terminal_pgrp)
+           {
+             if (give_terminal_to (shell_pgrp, 0) < 0)
+               {
+                 setpgid (0, original_pgrp);
+                 shell_pgrp = original_pgrp;
+                 job_control = 0;
+               }
+           }
+       }
+      if (job_control == 0)
+       internal_error (_("no job control in this shell"));
+    }
+
+  if (shell_tty != fileno (stderr))
+    SET_CLOSE_ON_EXEC (shell_tty);
+
+  set_signal_handler (SIGCHLD, sigchld_handler);
+
+  change_flag ('m', job_control ? '-' : '+');
+
+  if (interactive)
+    get_tty_state ();
+
+  return job_control;
+}
+
+#ifdef DEBUG
+void
+debug_print_pgrps ()
+{
+  itrace("original_pgrp = %ld shell_pgrp = %ld terminal_pgrp = %ld",
+        (long)original_pgrp, (long)shell_pgrp, (long)terminal_pgrp);
+  itrace("tcgetpgrp(%d) -> %ld, getpgid(0) -> %ld",
+        shell_tty, (long)tcgetpgrp (shell_tty), (long)getpgid(0));
+}
+#endif
+
+/* Set the line discipline to the best this system has to offer.
+   Return -1 if this is not possible. */
+static int
+set_new_line_discipline (tty)
+     int tty;
+{
+#if defined (NEW_TTY_DRIVER)
+  int ldisc;
+
+  if (ioctl (tty, TIOCGETD, &ldisc) < 0)
+    return (-1);
+
+  if (ldisc != NTTYDISC)
+    {
+      ldisc = NTTYDISC;
+
+      if (ioctl (tty, TIOCSETD, &ldisc) < 0)
+       return (-1);
+    }
+  return (0);
+#endif /* NEW_TTY_DRIVER */
+
+#if defined (TERMIO_TTY_DRIVER)
+#  if defined (TERMIO_LDISC) && (NTTYDISC)
+  if (ioctl (tty, TCGETA, &shell_tty_info) < 0)
+    return (-1);
+
+  if (shell_tty_info.c_line != NTTYDISC)
+    {
+      shell_tty_info.c_line = NTTYDISC;
+      if (ioctl (tty, TCSETAW, &shell_tty_info) < 0)
+       return (-1);
+    }
+#  endif /* TERMIO_LDISC && NTTYDISC */
+  return (0);
+#endif /* TERMIO_TTY_DRIVER */
+
+#if defined (TERMIOS_TTY_DRIVER)
+#  if defined (TERMIOS_LDISC) && defined (NTTYDISC)
+  if (tcgetattr (tty, &shell_tty_info) < 0)
+    return (-1);
+
+  if (shell_tty_info.c_line != NTTYDISC)
+    {
+      shell_tty_info.c_line = NTTYDISC;
+      if (tcsetattr (tty, TCSADRAIN, &shell_tty_info) < 0)
+       return (-1);
+    }
+#  endif /* TERMIOS_LDISC && NTTYDISC */
+  return (0);
+#endif /* TERMIOS_TTY_DRIVER */
+
+#if !defined (NEW_TTY_DRIVER) && !defined (TERMIO_TTY_DRIVER) && !defined (TERMIOS_TTY_DRIVER)
+  return (-1);
+#endif
+}
+
+#if defined (TIOCGWINSZ) && defined (SIGWINCH)
+static void
+get_new_window_size (from_sig)
+     int from_sig;
+{
+  struct winsize win;
+
+  if ((ioctl (shell_tty, TIOCGWINSZ, &win) == 0) &&
+      win.ws_row > 0 && win.ws_col > 0)
+    {
+#if defined (aixpc)
+      shell_tty_info.c_winsize = win;  /* structure copying */
+#endif
+      sh_set_lines_and_columns (win.ws_row, win.ws_col);
+#if defined (READLINE)
+      rl_set_screen_size (win.ws_row, win.ws_col);
+#endif
+    }
+}
+
+static sighandler
+sigwinch_sighandler (sig)
+     int sig;
+{
+#if defined (MUST_REINSTALL_SIGHANDLERS)
+  set_signal_handler (SIGWINCH, sigwinch_sighandler);
+#endif /* MUST_REINSTALL_SIGHANDLERS */
+  get_new_window_size (1);
+  SIGRETURN (0);
+}
+#else
+static void
+get_new_window_size (from_sig)
+     int from_sig;
+{
+}
+#endif /* TIOCGWINSZ && SIGWINCH */
+
+void
+set_sigwinch_handler ()
+{
+#if defined (TIOCGWINSZ) && defined (SIGWINCH)
+ old_winch = set_signal_handler (SIGWINCH, sigwinch_sighandler);
+#endif
+}
+
+void
+unset_sigwinch_handler ()
+{
+#if defined (TIOCGWINSZ) && defined (SIGWINCH)
+  set_signal_handler (SIGWINCH, old_winch);
+#endif
+}
+
+/* Setup this shell to handle C-C, etc. */
+void
+initialize_job_signals ()
+{
+  if (interactive)
+    {
+      set_signal_handler (SIGINT, sigint_sighandler);
+      set_signal_handler (SIGTSTP, SIG_IGN);
+      set_signal_handler (SIGTTOU, SIG_IGN);
+      set_signal_handler (SIGTTIN, SIG_IGN);
+      set_sigwinch_handler ();
+    }
+  else if (job_control)
+    {
+      old_tstp = set_signal_handler (SIGTSTP, sigstop_sighandler);
+      old_ttin = set_signal_handler (SIGTTIN, sigstop_sighandler);
+      old_ttou = set_signal_handler (SIGTTOU, sigstop_sighandler);
+    }
+  /* Leave these things alone for non-interactive shells without job
+     control. */
+}
+
+/* Here we handle CONT signals. */
+static sighandler
+sigcont_sighandler (sig)
+     int sig;
+{
+  initialize_job_signals ();
+  set_signal_handler (SIGCONT, old_cont);
+  kill (getpid (), SIGCONT);
+
+  SIGRETURN (0);
+}
+
+/* Here we handle stop signals while we are running not as a login shell. */
+static sighandler
+sigstop_sighandler (sig)
+     int sig;
+{
+  set_signal_handler (SIGTSTP, old_tstp);
+  set_signal_handler (SIGTTOU, old_ttou);
+  set_signal_handler (SIGTTIN, old_ttin);
+
+  old_cont = set_signal_handler (SIGCONT, sigcont_sighandler);
+
+  give_terminal_to (shell_pgrp, 0);
+
+  kill (getpid (), sig);
+
+  SIGRETURN (0);
+}
+
+/* Give the terminal to PGRP.  */
+int
+give_terminal_to (pgrp, force)
+     pid_t pgrp;
+     int force;
+{
+  sigset_t set, oset;
+  int r;
+
+  r = 0;
+  if (job_control || force)
+    {
+      sigemptyset (&set);
+      sigaddset (&set, SIGTTOU);
+      sigaddset (&set, SIGTTIN);
+      sigaddset (&set, SIGTSTP);
+      sigaddset (&set, SIGCHLD);
+      sigemptyset (&oset);
+      sigprocmask (SIG_BLOCK, &set, &oset);
+
+      if (tcsetpgrp (shell_tty, pgrp) < 0)
+       {
+         /* Maybe we should print an error message? */
+#if 0
+         sys_error ("tcsetpgrp(%d) failed: pid %ld to pgrp %ld",
+           shell_tty, (long)getpid(), (long)pgrp);
+#endif
+         r = -1;
+       }
+      else
+       terminal_pgrp = pgrp;
+      sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL);
+    }
+
+  return r;
+}
+
+/* Clear out any jobs in the job array.  This is intended to be used by
+   children of the shell, who should not have any job structures as baggage
+   when they start executing (forking subshells for parenthesized execution
+   and functions with pipes are the two that spring to mind).  If RUNNING_ONLY
+   is nonzero, only running jobs are removed from the table. */
+void
+delete_all_jobs (running_only)
+     int running_only;
+{
+  register int i;
+  sigset_t set, oset;
+
+  BLOCK_CHILD (set, oset);
+
+  if (job_slots)
+    {
+      current_job = previous_job = NO_JOB;
+
+      for (i = 0; i < job_slots; i++)
+       if (jobs[i] && (running_only == 0 || (running_only && RUNNING(i))))
+         delete_job (i, 1);
+
+      if (running_only == 0)
+       {
+         free ((char *)jobs);
+         job_slots = 0;
+       }
+    }
+
+  UNBLOCK_CHILD (oset);
+}
+
+/* Mark all jobs in the job array so that they don't get a SIGHUP when the
+   shell gets one.  If RUNNING_ONLY is nonzero, mark only running jobs. */
+void
+nohup_all_jobs (running_only)
+     int running_only;
+{
+  register int i;
+  sigset_t set, oset;
+
+  BLOCK_CHILD (set, oset);
+
+  if (job_slots)
+    {
+      for (i = 0; i < job_slots; i++)
+       if (jobs[i] && (running_only == 0 || (running_only && RUNNING(i))))
+         nohup_job (i);
+    }
+
+  UNBLOCK_CHILD (oset);
+}
+
+int
+count_all_jobs ()
+{
+  int i, n;
+  sigset_t set, oset;
+
+  BLOCK_CHILD (set, oset);
+  for (i = n = 0; i < job_slots; i++)
+    if (jobs[i] && DEADJOB(i) == 0)
+      n++;
+  UNBLOCK_CHILD (oset);
+  return n;
+}
+
+static void
+mark_all_jobs_as_dead ()
+{
+  register int i;
+  sigset_t set, oset;
+
+  if (job_slots == 0)
+    return;
+
+  BLOCK_CHILD (set, oset);
+
+  for (i = 0; i < job_slots; i++)
+    if (jobs[i])
+      jobs[i]->state = JDEAD;
+
+  UNBLOCK_CHILD (oset);
+}
+
+/* Mark all dead jobs as notified, so delete_job () cleans them out
+   of the job table properly.  POSIX.2 says we need to save the
+   status of the last CHILD_MAX jobs, so we count the number of dead
+   jobs and mark only enough as notified to save CHILD_MAX statuses. */
+static void
+mark_dead_jobs_as_notified (force)
+     int force;
+{
+  register int i, ndead;
+  sigset_t set, oset;
+
+  if (job_slots == 0)
+    return;
+
+  BLOCK_CHILD (set, oset);
+
+  /* If FORCE is non-zero, we don't have to keep CHILD_MAX statuses
+     around; just run through the array. */
+  if (force)
+    {
+      for (i = 0; i < job_slots; i++)
+       {
+         if (jobs[i] && DEADJOB (i) && (interactive_shell || (find_last_pid (i, 0) != last_asynchronous_pid)))
+           jobs[i]->flags |= J_NOTIFIED;
+       }
+      UNBLOCK_CHILD (oset);
+      return;
+    }
+
+  /* Mark enough dead jobs as notified to keep CHILD_MAX jobs left in the
+     array not marked as notified. */
+          
+  /* Count the number of dead jobs */
+  for (i = ndead = 0; i < job_slots; i++)
+    {
+      if (jobs[i] && DEADJOB (i))
+       ndead++;
+    }
+
+  if (child_max < 0)
+    child_max = getmaxchild ();
+  if (child_max < 0)
+    child_max = DEFAULT_CHILD_MAX;
+
+  /* Don't do anything if the number of dead jobs is less than CHILD_MAX and
+     we're not forcing a cleanup. */
+  if (ndead <= child_max)
+    {
+      UNBLOCK_CHILD (oset);
+      return;
+    }
+
+  /* Mark enough dead jobs as notified that we keep CHILD_MAX jobs in
+     the list.  This isn't exactly right yet; changes need to be made
+     to stop_pipeline so we don't mark the newer jobs after we've
+     created CHILD_MAX slots in the jobs array. */
+  for (i = 0; i < job_slots; i++)
+    {
+      if (jobs[i] && DEADJOB (i) && (interactive_shell || (find_last_pid (i, 0) != last_asynchronous_pid)))
+       {
+         jobs[i]->flags |= J_NOTIFIED;
+         if (--ndead <= child_max)
+           break;
+       }
+    }
+
+  UNBLOCK_CHILD (oset);
+}
+
+/* Here to allow other parts of the shell (like the trap stuff) to
+   unfreeze the jobs list. */
+void
+unfreeze_jobs_list ()
+{
+  jobs_list_frozen = 0;
+}
+
+/* Allow or disallow job control to take place.  Returns the old value
+   of job_control. */
+int
+set_job_control (arg)
+     int arg;
+{
+  int old;
+
+  old = job_control;
+  job_control = arg;
+
+  /* If we're turning on job control, reset pipeline_pgrp so make_child will
+     put new child processes into the right pgrp */
+  if (job_control != old && job_control)
+    pipeline_pgrp = 0;
+
+  return (old);
+}
+
+/* Turn off all traces of job control.  This is run by children of the shell
+   which are going to do shellsy things, like wait (), etc. */
+void
+without_job_control ()
+{
+  stop_making_children ();
+  start_pipeline ();
+  delete_all_jobs (0);
+  set_job_control (0);
+}
+
+/* If this shell is interactive, terminate all stopped jobs and
+   restore the original terminal process group.  This is done
+   before the `exec' builtin calls shell_execve. */
+void
+end_job_control ()
+{
+  if (interactive_shell)               /* XXX - should it be interactive? */
+    {
+      terminate_stopped_jobs ();
+
+      if (original_pgrp >= 0)
+       give_terminal_to (original_pgrp, 1);
+    }
+
+  if (original_pgrp >= 0)
+    setpgid (0, original_pgrp);
+}
+
+/* Restart job control by closing shell tty and reinitializing.  This is
+   called after an exec fails in an interactive shell and we do not exit. */
+void
+restart_job_control ()
+{
+  if (shell_tty != -1)
+    close (shell_tty);
+  initialize_job_control (0);
+}
+
+/* Set the handler to run when the shell receives a SIGCHLD signal. */
+void
+set_sigchld_handler ()
+{
+  set_signal_handler (SIGCHLD, sigchld_handler);
+}
+
+#if defined (PGRP_PIPE)
+/* Read from the read end of a pipe.  This is how the process group leader
+   blocks until all of the processes in a pipeline have been made. */
+static void
+pipe_read (pp)
+     int *pp;
+{
+  char ch;
+
+  if (pp[1] >= 0)
+    {
+      close (pp[1]);
+      pp[1] = -1;
+    }
+
+  if (pp[0] >= 0)
+    {
+      while (read (pp[0], &ch, 1) == -1 && errno == EINTR)
+       ;
+    }
+}
+
+/* Close the read and write ends of PP, an array of file descriptors. */
+static void
+pipe_close (pp)
+     int *pp;
+{
+  if (pp[0] >= 0)
+    close (pp[0]);
+
+  if (pp[1] >= 0)
+    close (pp[1]);
+
+  pp[0] = pp[1] = -1;
+}
+
+/* Functional interface closes our local-to-job-control pipes. */
+void
+close_pgrp_pipe ()
+{
+  pipe_close (pgrp_pipe);
+}
+
+#endif /* PGRP_PIPE */
index f78f460..c8c7757 100644 (file)
@@ -347,6 +347,37 @@ indirection_level_string ()
   return (indirection_string);
 }
 
+void
+xtrace_print_assignment (name, value, assign_list, xflags)
+     char *name, *value;
+     int assign_list, xflags;
+{
+  char *nval;
+
+  if (xflags)
+    fprintf (stderr, "%s", indirection_level_string ());
+
+  /* VALUE should not be NULL when this is called. */
+  if (*value == '\0' || assign_list)
+    nval = value;
+  else if (sh_contains_shell_metas (value))
+    nval = sh_single_quote (value);
+  else if (ansic_shouldquote (value))
+    nval = ansic_quote (value, 0, (int *)0);
+  else
+    nval = value;
+
+  if (assign_list)
+    fprintf (stderr, "%s=(%s)\n", name, nval);
+  else
+    fprintf (stderr, "%s=%s\n", name, nval);
+
+  if (nval != value)
+    FREE (nval);
+
+  fflush (stderr);
+}
+
 /* A function to print the words of a simple command when set -x is on. */
 void
 xtrace_print_word_list (list, xtflags)
diff --git a/print_cmd.c~ b/print_cmd.c~
new file mode 100644 (file)
index 0000000..e63c457
--- /dev/null
@@ -0,0 +1,1282 @@
+/* print_command -- A way to make readable commands from a command tree. */
+
+/* Copyright (C) 1989-2004 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 2, or (at your option) any later
+version.
+
+Bash is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License along
+with Bash; see the file COPYING.  If not, write to the Free Software
+Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+
+#include "config.h"
+
+#include <stdio.h>
+
+#if defined (HAVE_UNISTD_H)
+#  ifdef _MINIX
+#    include <sys/types.h>
+#  endif
+#  include <unistd.h>
+#endif
+
+#if defined (PREFER_STDARG)
+#  include <stdarg.h>
+#else
+#  include <varargs.h>
+#endif
+
+#include "bashansi.h"
+#include "bashintl.h"
+
+#include "shell.h"
+#include "flags.h"
+#include <y.tab.h>     /* use <...> so we pick it up from the build directory */
+#include "builtins/common.h"
+
+#if !HAVE_DECL_PRINTF
+extern int printf __P((const char *, ...));    /* Yuck.  Double yuck. */
+#endif
+
+extern int indirection_level;
+
+static int indentation;
+static int indentation_amount = 4;
+
+#if defined (PREFER_STDARG)
+typedef void PFUNC __P((const char *, ...));
+
+static void cprintf __P((const char *, ...))  __attribute__((__format__ (printf, 1, 2)));
+static void xprintf __P((const char *, ...))  __attribute__((__format__ (printf, 1, 2)));
+#else
+#define PFUNC VFunction
+static void cprintf ();
+static void xprintf ();
+#endif
+
+static void reset_locals __P((void));
+static void newline __P((char *));
+static void indent __P((int));
+static void semicolon __P((void));
+static void the_printed_command_resize __P((int));
+
+static void make_command_string_internal __P((COMMAND *));
+static void _print_word_list __P((WORD_LIST *, char *, PFUNC *));
+static void command_print_word_list __P((WORD_LIST *, char *));
+static void print_case_clauses __P((PATTERN_LIST *));
+static void print_redirection_list __P((REDIRECT *));
+static void print_redirection __P((REDIRECT *));
+
+static void print_for_command __P((FOR_COM *));
+#if defined (ARITH_FOR_COMMAND)
+static void print_arith_for_command __P((ARITH_FOR_COM *));
+#endif
+#if defined (SELECT_COMMAND)
+static void print_select_command __P((SELECT_COM *));
+#endif
+static void print_group_command __P((GROUP_COM *));
+static void print_case_command __P((CASE_COM *));
+static void print_while_command __P((WHILE_COM *));
+static void print_until_command __P((WHILE_COM *));
+static void print_until_or_while __P((WHILE_COM *, char *));
+static void print_if_command __P((IF_COM *));
+#if defined (COND_COMMAND)
+static void print_cond_node __P((COND_COM *));
+#endif
+static void print_function_def __P((FUNCTION_DEF *));
+
+#define PRINTED_COMMAND_INITIAL_SIZE 64
+#define PRINTED_COMMAND_GROW_SIZE 128
+
+char *the_printed_command = (char *)NULL;
+int the_printed_command_size = 0;
+int command_string_index = 0;
+
+/* Non-zero means the stuff being printed is inside of a function def. */
+static int inside_function_def;
+static int skip_this_indent;
+static int was_heredoc;
+
+/* The depth of the group commands that we are currently printing.  This
+   includes the group command that is a function body. */
+static int group_command_nesting;
+
+/* A buffer to indicate the indirection level (PS4) when set -x is enabled. */
+static char indirection_string[100];
+
+/* Print COMMAND (a command tree) on standard output. */
+void
+print_command (command)
+     COMMAND *command;
+{
+  command_string_index = 0;
+  printf ("%s", make_command_string (command));
+}
+
+/* Make a string which is the printed representation of the command
+   tree in COMMAND.  We return this string.  However, the string is
+   not consed, so you have to do that yourself if you want it to
+   remain around. */
+char *
+make_command_string (command)
+     COMMAND *command;
+{
+  command_string_index = was_heredoc = 0;
+  make_command_string_internal (command);
+  return (the_printed_command);
+}
+
+/* The internal function.  This is the real workhorse. */
+static void
+make_command_string_internal (command)
+     COMMAND *command;
+{
+  if (command == 0)
+    cprintf ("");
+  else
+    {
+      if (skip_this_indent)
+       skip_this_indent--;
+      else
+       indent (indentation);
+
+      if (command->flags & CMD_TIME_PIPELINE)
+       {
+         cprintf ("time ");
+         if (command->flags & CMD_TIME_POSIX)
+           cprintf ("-p ");
+       }
+
+      if (command->flags & CMD_INVERT_RETURN)
+       cprintf ("! ");
+
+      switch (command->type)
+       {
+       case cm_for:
+         print_for_command (command->value.For);
+         break;
+
+#if defined (ARITH_FOR_COMMAND)
+       case cm_arith_for:
+         print_arith_for_command (command->value.ArithFor);
+         break;
+#endif
+
+#if defined (SELECT_COMMAND)
+       case cm_select:
+         print_select_command (command->value.Select);
+         break;
+#endif
+
+       case cm_case:
+         print_case_command (command->value.Case);
+         break;
+
+       case cm_while:
+         print_while_command (command->value.While);
+         break;
+
+       case cm_until:
+         print_until_command (command->value.While);
+         break;
+
+       case cm_if:
+         print_if_command (command->value.If);
+         break;
+
+#if defined (DPAREN_ARITHMETIC)
+       case cm_arith:
+         print_arith_command (command->value.Arith->exp);
+         break;
+#endif
+
+#if defined (COND_COMMAND)
+       case cm_cond:
+         print_cond_command (command->value.Cond);
+         break;
+#endif
+
+       case cm_simple:
+         print_simple_command (command->value.Simple);
+         break;
+
+       case cm_connection:
+
+         skip_this_indent++;
+         make_command_string_internal (command->value.Connection->first);
+
+         switch (command->value.Connection->connector)
+           {
+           case '&':
+           case '|':
+             {
+               char c = command->value.Connection->connector;
+               cprintf (" %c", c);
+               if (c != '&' || command->value.Connection->second)
+                 {
+                   cprintf (" ");
+                   skip_this_indent++;
+                 }
+             }
+             break;
+
+           case AND_AND:
+             cprintf (" && ");
+             if (command->value.Connection->second)
+               skip_this_indent++;
+             break;
+
+           case OR_OR:
+             cprintf (" || ");
+             if (command->value.Connection->second)
+               skip_this_indent++;
+             break;
+
+           case ';':
+             if (was_heredoc == 0)
+               cprintf (";");
+             else
+               was_heredoc = 0;
+
+             if (inside_function_def)
+               cprintf ("\n");
+             else
+               {
+                 cprintf (" ");
+                 if (command->value.Connection->second)
+                   skip_this_indent++;
+               }
+             break;
+
+           default:
+             cprintf (_("print_command: bad connector `%d'"),
+                      command->value.Connection->connector);
+             break;
+           }
+
+         make_command_string_internal (command->value.Connection->second);
+         break;
+
+       case cm_function_def:
+         print_function_def (command->value.Function_def);
+         break;
+
+       case cm_group:
+         print_group_command (command->value.Group);
+         break;
+
+       case cm_subshell:
+         cprintf ("( ");
+         skip_this_indent++;
+         make_command_string_internal (command->value.Subshell->command);
+         cprintf (" )");
+         break;
+
+       default:
+         command_error ("print_command", CMDERR_BADTYPE, command->type, 0);
+         break;
+       }
+
+
+      if (command->redirects)
+       {
+         cprintf (" ");
+         print_redirection_list (command->redirects);
+       }
+    }
+}
+
+static void
+_print_word_list (list, separator, pfunc)
+     WORD_LIST *list;
+     char *separator;
+     PFUNC *pfunc;
+{
+  WORD_LIST *w;
+
+  for (w = list; w; w = w->next)
+    (*pfunc) ("%s%s", w->word->word, w->next ? separator : "");
+}
+
+void
+print_word_list (list, separator)
+     WORD_LIST *list;
+     char *separator;
+{
+  _print_word_list (list, separator, xprintf);
+}
+
+/* Return a string denoting what our indirection level is. */
+
+char *
+indirection_level_string ()
+{
+  register int i, j;
+  char *ps4;
+
+  indirection_string[0] = '\0';
+  ps4 = get_string_value ("PS4");
+
+  if (ps4 == 0 || *ps4 == '\0')
+    return (indirection_string);
+
+  change_flag ('x', FLAG_OFF);
+  ps4 = decode_prompt_string (ps4);
+  change_flag ('x', FLAG_ON);
+
+  if (ps4 == 0 || *ps4 == '\0')
+    return (indirection_string);
+
+  for (i = 0; *ps4 && i < indirection_level && i < 99; i++)
+    indirection_string[i] = *ps4;
+
+  for (j = 1; *ps4 && ps4[j] && i < 99; i++, j++)
+    indirection_string[i] = ps4[j];
+
+  indirection_string[i] = '\0';
+  free (ps4);
+  return (indirection_string);
+}
+
+void
+xtrace_print_assignment (name, value, assign_list, xflags)
+     char *name, *value;
+     int assign_list, xflags;
+{
+  char *nval;
+
+  if (xflags)
+    fprintf (stderr, "%s", indirection_level_string ());
+
+  /* VALUE should not be NULL when this is called. */
+  if (*value == '\0 || assign_list)
+    nval = value;
+  else if (sh_contains_shell_metas (value))
+    nval = sh_single_quote (value);
+  else if (ansic_shouldquote (value))
+    nval = ansic_quote (value, 0, (int *)0);
+  else
+    nval = value;
+
+  if (assign_list)
+    fprintf (stderr, "%s=(%s)\n", name, nval);
+  else
+    fprintf (stderr, "%s=%s\n", name, nval);
+
+  if (nval != value)
+    FREE (nval);
+
+  fflush (stderr);
+}
+
+/* A function to print the words of a simple command when set -x is on. */
+void
+xtrace_print_word_list (list, xtflags)
+     WORD_LIST *list;
+     int xtflags;
+{
+  WORD_LIST *w;
+  char *t, *x;
+
+  if (xtflags)
+    fprintf (stderr, "%s", indirection_level_string ());
+
+  for (w = list; w; w = w->next)
+    {
+      t = w->word->word;
+      if (t == 0 || *t == '\0')
+       fprintf (stderr, "''%s", w->next ? " " : "");
+      else if (sh_contains_shell_metas (t))
+       {
+         x = sh_single_quote (t);
+         fprintf (stderr, "%s%s", x, w->next ? " " : "");
+         free (x);
+       }
+      else if (ansic_shouldquote (t))
+       {
+         x = ansic_quote (t, 0, (int *)0);
+         fprintf (stderr, "%s%s", x, w->next ? " " : "");
+         free (x);
+       }
+      else
+       fprintf (stderr, "%s%s", t, w->next ? " " : "");
+    }
+  fprintf (stderr, "\n");
+}
+
+static void
+command_print_word_list (list, separator)
+     WORD_LIST *list;
+     char *separator;
+{
+  _print_word_list (list, separator, cprintf);
+}
+
+void
+print_for_command_head (for_command)
+     FOR_COM *for_command;
+{
+  cprintf ("for %s in ", for_command->name->word);
+  command_print_word_list (for_command->map_list, " ");
+}
+
+void
+xtrace_print_for_command_head (for_command)
+     FOR_COM *for_command;
+{
+  fprintf (stderr, "%s", indirection_level_string ());
+  fprintf (stderr, "for %s in ", for_command->name->word);
+  xtrace_print_word_list (for_command->map_list, 0);
+}
+
+static void
+print_for_command (for_command)
+     FOR_COM *for_command;
+{
+  print_for_command_head (for_command);
+
+  cprintf (";");
+  newline ("do\n");
+  indentation += indentation_amount;
+  make_command_string_internal (for_command->action);
+  semicolon ();
+  indentation -= indentation_amount;
+  newline ("done");
+}
+
+#if defined (ARITH_FOR_COMMAND)
+static void
+print_arith_for_command (arith_for_command)
+     ARITH_FOR_COM *arith_for_command;
+{
+  cprintf ("for ((");
+  command_print_word_list (arith_for_command->init, " ");
+  cprintf (" ; ");
+  command_print_word_list (arith_for_command->test, " ");
+  cprintf (" ; ");
+  command_print_word_list (arith_for_command->step, " ");
+  cprintf ("))");
+  newline ("do\n");
+  indentation += indentation_amount;
+  make_command_string_internal (arith_for_command->action);
+  semicolon ();
+  indentation -= indentation_amount;
+  newline ("done");
+}
+#endif /* ARITH_FOR_COMMAND */
+
+#if defined (SELECT_COMMAND)
+void
+print_select_command_head (select_command)
+     SELECT_COM *select_command;
+{
+  cprintf ("select %s in ", select_command->name->word);
+  command_print_word_list (select_command->map_list, " ");
+}
+
+void
+xtrace_print_select_command_head (select_command)
+     SELECT_COM *select_command;
+{
+  fprintf (stderr, "%s", indirection_level_string ());
+  fprintf (stderr, "select %s in ", select_command->name->word);
+  xtrace_print_word_list (select_command->map_list, 0);
+}
+
+static void
+print_select_command (select_command)
+     SELECT_COM *select_command;
+{
+  print_select_command_head (select_command);
+
+  cprintf (";");
+  newline ("do\n");
+  indentation += indentation_amount;
+  make_command_string_internal (select_command->action);
+  semicolon ();
+  indentation -= indentation_amount;
+  newline ("done");
+}
+#endif /* SELECT_COMMAND */
+
+static void
+print_group_command (group_command)
+     GROUP_COM *group_command;
+{
+  group_command_nesting++;
+  cprintf ("{ ");
+
+  if (inside_function_def == 0)
+    skip_this_indent++;
+  else
+    {
+      /* This is a group command { ... } inside of a function
+        definition, and should be printed as a multiline group
+        command, using the current indentation. */
+      cprintf ("\n");
+      indentation += indentation_amount;
+    }
+
+  make_command_string_internal (group_command->command);
+
+  if (inside_function_def)
+    {
+      cprintf ("\n");
+      indentation -= indentation_amount;
+      indent (indentation);
+    }
+  else
+    {
+      semicolon ();
+      cprintf (" ");
+    }
+
+  cprintf ("}");
+
+  group_command_nesting--;
+}
+
+void
+print_case_command_head (case_command)
+     CASE_COM *case_command;
+{
+  cprintf ("case %s in ", case_command->word->word);
+}
+
+void
+xtrace_print_case_command_head (case_command)
+     CASE_COM *case_command;
+{
+  fprintf (stderr, "%s", indirection_level_string ());
+  fprintf (stderr, "case %s in\n", case_command->word->word);
+}
+
+static void
+print_case_command (case_command)
+     CASE_COM *case_command;
+{
+  print_case_command_head (case_command);
+
+  if (case_command->clauses)
+    print_case_clauses (case_command->clauses);
+  newline ("esac");
+}
+
+static void
+print_case_clauses (clauses)
+     PATTERN_LIST *clauses;
+{
+  indentation += indentation_amount;
+  while (clauses)
+    {
+      newline ("");
+      command_print_word_list (clauses->patterns, " | ");
+      cprintf (")\n");
+      indentation += indentation_amount;
+      make_command_string_internal (clauses->action);
+      indentation -= indentation_amount;
+      newline (";;");
+      clauses = clauses->next;
+    }
+  indentation -= indentation_amount;
+}
+
+static void
+print_while_command (while_command)
+     WHILE_COM *while_command;
+{
+  print_until_or_while (while_command, "while");
+}
+
+static void
+print_until_command (while_command)
+     WHILE_COM *while_command;
+{
+  print_until_or_while (while_command, "until");
+}
+
+static void
+print_until_or_while (while_command, which)
+     WHILE_COM *while_command;
+     char *which;
+{
+  cprintf ("%s ", which);
+  skip_this_indent++;
+  make_command_string_internal (while_command->test);
+  semicolon ();
+  cprintf (" do\n");   /* was newline ("do\n"); */
+  indentation += indentation_amount;
+  make_command_string_internal (while_command->action);
+  indentation -= indentation_amount;
+  semicolon ();
+  newline ("done");
+}
+
+static void
+print_if_command (if_command)
+     IF_COM *if_command;
+{
+  cprintf ("if ");
+  skip_this_indent++;
+  make_command_string_internal (if_command->test);
+  semicolon ();
+  cprintf (" then\n");
+  indentation += indentation_amount;
+  make_command_string_internal (if_command->true_case);
+  indentation -= indentation_amount;
+
+  if (if_command->false_case)
+    {
+      semicolon ();
+      newline ("else\n");
+      indentation += indentation_amount;
+      make_command_string_internal (if_command->false_case);
+      indentation -= indentation_amount;
+    }
+  semicolon ();
+  newline ("fi");
+}
+
+#if defined (DPAREN_ARITHMETIC)
+void
+print_arith_command (arith_cmd_list)
+     WORD_LIST *arith_cmd_list;
+{
+  cprintf ("((");
+  command_print_word_list (arith_cmd_list, " ");
+  cprintf ("))");
+}
+#endif
+
+#if defined (COND_COMMAND)
+static void
+print_cond_node (cond)
+     COND_COM *cond;
+{
+  if (cond->flags & CMD_INVERT_RETURN)
+    cprintf ("! ");
+
+  if (cond->type == COND_EXPR)
+    {
+      cprintf ("( ");
+      print_cond_node (cond->left);
+      cprintf (" )");
+    }
+  else if (cond->type == COND_AND)
+    {
+      print_cond_node (cond->left);
+      cprintf (" && ");
+      print_cond_node (cond->right);
+    }
+  else if (cond->type == COND_OR)
+    {
+      print_cond_node (cond->left);
+      cprintf (" || ");
+      print_cond_node (cond->right);
+    }
+  else if (cond->type == COND_UNARY)
+    {
+      cprintf ("%s", cond->op->word);
+      cprintf (" ");
+      print_cond_node (cond->left);
+    }
+  else if (cond->type == COND_BINARY)
+    {
+      print_cond_node (cond->left);
+      cprintf (" ");
+      cprintf ("%s", cond->op->word);
+      cprintf (" ");
+      print_cond_node (cond->right);
+    }
+  else if (cond->type == COND_TERM)
+    {
+      cprintf ("%s", cond->op->word);          /* need to add quoting here */
+    }
+}
+
+void
+print_cond_command (cond)
+     COND_COM *cond;
+{
+  cprintf ("[[ ");
+  print_cond_node (cond);
+  cprintf (" ]]");
+}
+
+#ifdef DEBUG
+void
+debug_print_cond_command (cond)
+     COND_COM *cond;
+{
+  fprintf (stderr, "DEBUG: ");
+  command_string_index = 0;
+  print_cond_command (cond);
+  fprintf (stderr, "%s\n", the_printed_command);
+}
+#endif
+
+void
+xtrace_print_cond_term (type, invert, op, arg1, arg2)
+     int type, invert;
+     WORD_DESC *op;
+     char *arg1, *arg2;
+{
+  command_string_index = 0;
+  fprintf (stderr, "%s", indirection_level_string ());
+  fprintf (stderr, "[[ ");
+  if (invert)
+    fprintf (stderr, "! ");
+
+  if (type == COND_UNARY)
+    {
+      fprintf (stderr, "%s ", op->word);
+      fprintf (stderr, "%s", (arg1 && *arg1) ? arg1 : "''");
+    }
+  else if (type == COND_BINARY)
+    {
+      fprintf (stderr, "%s", (arg1 && *arg1) ? arg1 : "''");
+      fprintf (stderr, " %s ", op->word);
+      fprintf (stderr, "%s", (arg2 && *arg2) ? arg2 : "''");
+    }
+
+  fprintf (stderr, " ]]\n");
+}        
+#endif /* COND_COMMAND */
+
+#if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND)
+/* A function to print the words of an arithmetic command when set -x is on. */
+void
+xtrace_print_arith_cmd (list)
+     WORD_LIST *list;
+{
+  WORD_LIST *w;
+
+  fprintf (stderr, "%s", indirection_level_string ());
+  fprintf (stderr, "(( ");
+  for (w = list; w; w = w->next)
+    fprintf (stderr, "%s%s", w->word->word, w->next ? " " : "");
+  fprintf (stderr, " ))\n");
+}
+#endif
+
+void
+print_simple_command (simple_command)
+     SIMPLE_COM *simple_command;
+{
+  command_print_word_list (simple_command->words, " ");
+
+  if (simple_command->redirects)
+    {
+      cprintf (" ");
+      print_redirection_list (simple_command->redirects);
+    }
+}
+
+static void
+print_redirection_list (redirects)
+     REDIRECT *redirects;
+{
+  REDIRECT *heredocs, *hdtail, *newredir;
+
+  heredocs = (REDIRECT *)NULL;
+  hdtail = heredocs;
+
+  was_heredoc = 0;
+  while (redirects)
+    {
+      /* Defer printing the here documents until we've printed the
+        rest of the redirections. */
+      if (redirects->instruction == r_reading_until || redirects->instruction == r_deblank_reading_until)
+       {
+         newredir = copy_redirect (redirects);
+         newredir->next = (REDIRECT *)NULL;
+         if (heredocs)
+           {
+             hdtail->next = newredir;
+             hdtail = newredir;
+           }
+         else
+           hdtail = heredocs = newredir;
+       }
+      else if (redirects->instruction == r_duplicating_output_word && redirects->redirector == 1)
+       {
+         /* Temporarily translate it as the execution code does. */
+         redirects->instruction = r_err_and_out;
+         print_redirection (redirects);
+         redirects->instruction = r_duplicating_output_word;
+       }
+      else
+       print_redirection (redirects);
+
+      redirects = redirects->next;
+      if (redirects)
+       cprintf (" ");
+    }
+
+  /* Now that we've printed all the other redirections (on one line),
+     print the here documents. */
+  if (heredocs)
+    {
+      cprintf (" "); 
+      for (hdtail = heredocs; hdtail; hdtail = hdtail->next)
+       {
+         print_redirection (hdtail);
+         cprintf ("\n");
+       }
+      dispose_redirects (heredocs);
+      was_heredoc = 1;
+    }
+}
+
+static void
+print_redirection (redirect)
+     REDIRECT *redirect;
+{
+  int kill_leading, redirector, redir_fd;
+  WORD_DESC *redirectee;
+
+  kill_leading = 0;
+  redirectee = redirect->redirectee.filename;
+  redirector = redirect->redirector;
+  redir_fd = redirect->redirectee.dest;
+
+  switch (redirect->instruction)
+    {
+    case r_output_direction:
+      if (redirector != 1)
+       cprintf ("%d", redirector);
+      cprintf (">%s", redirectee->word);
+      break;
+
+    case r_input_direction:
+      if (redirector != 0)
+       cprintf ("%d", redirector);
+      cprintf ("<%s", redirectee->word);
+      break;
+
+    case r_inputa_direction:   /* Redirection created by the shell. */
+      cprintf ("&");
+      break;
+
+    case r_appending_to:
+      if (redirector != 1)
+       cprintf ("%d", redirector);
+      cprintf (">>%s", redirectee->word);
+      break;
+
+    case r_deblank_reading_until:
+      kill_leading++;
+      /* ... */
+    case r_reading_until:
+      if (redirector != 0)
+       cprintf ("%d", redirector);
+      /* If the here document delimiter is quoted, single-quote it. */
+      if (redirect->redirectee.filename->flags & W_QUOTED)
+       {
+         char *x;
+         x = sh_single_quote (redirect->here_doc_eof);
+         cprintf ("<<%s%s\n", kill_leading? "-" : "", x);
+         free (x);
+       }
+      else
+       cprintf ("<<%s%s\n", kill_leading? "-" : "", redirect->here_doc_eof);
+      cprintf ("%s%s",
+              redirect->redirectee.filename->word, redirect->here_doc_eof);
+      break;
+
+    case r_reading_string:
+      if (redirector != 0)
+       cprintf ("%d", redirector);
+      if (ansic_shouldquote (redirect->redirectee.filename->word))
+       {
+         char *x;
+         x = ansic_quote (redirect->redirectee.filename->word, 0, (int *)0);
+         cprintf ("<<< %s", x);
+         free (x);
+       }
+      else
+       cprintf ("<<< %s", redirect->redirectee.filename->word);
+      break;
+
+    case r_duplicating_input:
+      cprintf ("%d<&%d", redirector, redir_fd);
+      break;
+
+    case r_duplicating_output:
+      cprintf ("%d>&%d", redirector, redir_fd);
+      break;
+
+    case r_duplicating_input_word:
+      cprintf ("%d<&%s", redirector, redirectee->word);
+      break;
+
+    case r_duplicating_output_word:
+      cprintf ("%d>&%s", redirector, redirectee->word);
+      break;
+
+    case r_move_input:
+      cprintf ("%d<&%d-", redirector, redir_fd);
+      break;
+
+    case r_move_output:
+      cprintf ("%d>&%d-", redirector, redir_fd);
+      break;
+
+    case r_move_input_word:
+      cprintf ("%d<&%s-", redirector, redirectee->word);
+      break;
+
+    case r_move_output_word:
+      cprintf ("%d>&%s-", redirector, redirectee->word);
+      break;
+
+    case r_close_this:
+      cprintf ("%d>&-", redirector);
+      break;
+
+    case r_err_and_out:
+      cprintf (">&%s", redirectee->word);
+      break;
+
+    case r_input_output:
+      if (redirector != 1)
+       cprintf ("%d", redirector);
+      cprintf ("<>%s", redirectee->word);
+      break;
+
+    case r_output_force:
+      if (redirector != 1)
+       cprintf ("%d", redirector);
+      cprintf (">|%s", redirectee->word);
+      break;
+    }
+}
+
+static void
+reset_locals ()
+{
+  inside_function_def = 0;
+  indentation = 0;
+}
+
+static void
+print_function_def (func)
+     FUNCTION_DEF *func;
+{
+  COMMAND *cmdcopy;
+  REDIRECT *func_redirects;
+
+  func_redirects = NULL;
+  cprintf ("function %s () \n", func->name->word);
+  add_unwind_protect (reset_locals, 0);
+
+  indent (indentation);
+  cprintf ("{ \n");
+
+  inside_function_def++;
+  indentation += indentation_amount;
+
+  cmdcopy = copy_command (func->command);
+  if (cmdcopy->type == cm_group)
+    {
+      func_redirects = cmdcopy->redirects;
+      cmdcopy->redirects = (REDIRECT *)NULL;
+    }
+  make_command_string_internal (cmdcopy->type == cm_group
+                                       ? cmdcopy->value.Group->command
+                                       : cmdcopy);
+
+  remove_unwind_protect ();
+  indentation -= indentation_amount;
+  inside_function_def--;
+
+  if (func_redirects)
+    { /* { */
+      newline ("} ");
+      print_redirection_list (func_redirects);
+      cmdcopy->redirects = func_redirects;
+    }
+  else
+    newline ("}");
+
+  dispose_command (cmdcopy);
+}
+
+/* Return the string representation of the named function.
+   NAME is the name of the function.
+   COMMAND is the function body.  It should be a GROUP_COM.
+   MULTI_LINE is non-zero to pretty-print, or zero for all on one line.
+  */
+char *
+named_function_string (name, command, multi_line)
+     char *name;
+     COMMAND *command;
+     int multi_line;
+{
+  char *result;
+  int old_indent, old_amount;
+  COMMAND *cmdcopy;
+  REDIRECT *func_redirects;
+
+  old_indent = indentation;
+  old_amount = indentation_amount;
+  command_string_index = was_heredoc = 0;
+
+  if (name && *name)
+    cprintf ("%s ", name);
+
+  cprintf ("() ");
+
+  if (multi_line == 0)
+    {
+      indentation = 1;
+      indentation_amount = 0;
+    }
+  else
+    {
+      cprintf ("\n");
+      indentation += indentation_amount;
+    }
+
+  inside_function_def++;
+
+  cprintf (multi_line ? "{ \n" : "{ ");
+
+  cmdcopy = copy_command (command);
+  /* Take any redirections specified in the function definition (which should
+     apply to the function as a whole) and save them for printing later. */
+  func_redirects = (REDIRECT *)NULL;
+  if (cmdcopy->type == cm_group)
+    {
+      func_redirects = cmdcopy->redirects;
+      cmdcopy->redirects = (REDIRECT *)NULL;
+    }
+  make_command_string_internal (cmdcopy->type == cm_group
+                                       ? cmdcopy->value.Group->command
+                                       : cmdcopy);
+
+  indentation = old_indent;
+  indentation_amount = old_amount;
+  inside_function_def--;
+
+  if (func_redirects)
+    { /* { */
+      newline ("} ");
+      print_redirection_list (func_redirects);
+      cmdcopy->redirects = func_redirects;
+    }
+  else
+    newline ("}");
+
+  result = the_printed_command;
+
+  if (!multi_line)
+    {
+#if 0
+      register int i;
+      for (i = 0; result[i]; i++)
+       if (result[i] == '\n')
+         {
+           strcpy (result + i, result + i + 1);
+           --i;
+         }
+#else
+      if (result[2] == '\n')   /* XXX -- experimental */
+       strcpy (result + 2, result + 3);
+#endif
+    }
+
+  dispose_command (cmdcopy);
+
+  return (result);
+}
+
+static void
+newline (string)
+     char *string;
+{
+  cprintf ("\n");
+  indent (indentation);
+  if (string && *string)
+    cprintf ("%s", string);
+}
+
+static char *indentation_string;
+static int indentation_size;
+
+static void
+indent (amount)
+     int amount;
+{
+  register int i;
+
+  RESIZE_MALLOCED_BUFFER (indentation_string, 0, amount, indentation_size, 16);
+
+  for (i = 0; amount > 0; amount--)
+    indentation_string[i++] = ' ';
+  indentation_string[i] = '\0';
+  cprintf (indentation_string);
+}
+
+static void
+semicolon ()
+{
+  if (command_string_index > 0 &&
+       (the_printed_command[command_string_index - 1] == '&' ||
+        the_printed_command[command_string_index - 1] == '\n'))
+    return;
+  cprintf (";");
+}
+
+/* How to make the string. */
+static void
+#if defined (PREFER_STDARG)
+cprintf (const char *control, ...)
+#else
+cprintf (control, va_alist)
+     const char *control;
+     va_dcl
+#endif
+{
+  register const char *s;
+  char char_arg[2], *argp, intbuf[INT_STRLEN_BOUND (int) + 1];
+  int digit_arg, arg_len, c;
+  va_list args;
+
+  SH_VA_START (args, control);
+
+  arg_len = strlen (control);
+  the_printed_command_resize (arg_len + 1);
+
+  char_arg[1] = '\0';
+  s = control;
+  while (s && *s)
+    {
+      c = *s++;
+      argp = (char *)NULL;
+      if (c != '%' || !*s)
+       {
+         char_arg[0] = c;
+         argp = char_arg;
+         arg_len = 1;
+       }
+      else
+       {
+         c = *s++;
+         switch (c)
+           {
+           case '%':
+             char_arg[0] = c;
+             argp = char_arg;
+             arg_len = 1;
+             break;
+
+           case 's':
+             argp = va_arg (args, char *);
+             arg_len = strlen (argp);
+             break;
+
+           case 'd':
+             /* Represent an out-of-range file descriptor with an out-of-range
+                integer value.  We can do this because the only use of `%d' in
+                the calls to cprintf is to output a file descriptor number for
+                a redirection. */
+             digit_arg = va_arg (args, int);
+             if (digit_arg < 0)
+               {
+                 sprintf (intbuf, "%u", (unsigned)-1);
+                 argp = intbuf;
+               }
+             else
+               argp = inttostr (digit_arg, intbuf, sizeof (intbuf));
+             arg_len = strlen (argp);
+             break;
+
+           case 'c':
+             char_arg[0] = va_arg (args, int);
+             argp = char_arg;
+             arg_len = 1;
+             break;
+
+           default:
+             programming_error (_("cprintf: `%c': invalid format character"), c);
+             /*NOTREACHED*/
+           }
+       }
+
+      if (argp && arg_len)
+       {
+         the_printed_command_resize (arg_len + 1);
+         FASTCOPY (argp, the_printed_command + command_string_index, arg_len);
+         command_string_index += arg_len;
+       }
+    }
+
+  the_printed_command[command_string_index] = '\0';
+}
+
+/* Ensure that there is enough space to stuff LENGTH characters into
+   THE_PRINTED_COMMAND. */
+static void
+the_printed_command_resize (length)
+     int length;
+{
+  if (the_printed_command == 0)
+    {
+      the_printed_command_size = (length + PRINTED_COMMAND_INITIAL_SIZE - 1) & ~(PRINTED_COMMAND_INITIAL_SIZE - 1);
+      the_printed_command = (char *)xmalloc (the_printed_command_size);
+      command_string_index = 0;
+    }
+  else if ((command_string_index + length) >= the_printed_command_size)
+    {
+      int new;
+      new = command_string_index + length + 1;
+
+      /* Round up to the next multiple of PRINTED_COMMAND_GROW_SIZE. */
+      new = (new + PRINTED_COMMAND_GROW_SIZE - 1) & ~(PRINTED_COMMAND_GROW_SIZE - 1);
+      the_printed_command_size = new;
+
+      the_printed_command = (char *)xrealloc (the_printed_command, the_printed_command_size);
+    }
+}
+
+#if defined (HAVE_VPRINTF)
+/* ``If vprintf is available, you may assume that vfprintf and vsprintf are
+     also available.'' */
+
+static void
+#if defined (PREFER_STDARG)
+xprintf (const char *format, ...)
+#else
+xprintf (format, va_alist)
+     const char *format;
+     va_dcl
+#endif
+{
+  va_list args;
+
+  SH_VA_START (args, format);
+
+  vfprintf (stdout, format, args);
+  va_end (args);
+}
+
+#else
+
+static void
+xprintf (format, arg1, arg2, arg3, arg4, arg5)
+     const char *format;
+{
+  printf (format, arg1, arg2, arg3, arg4, arg5);
+}
+
+#endif /* !HAVE_VPRINTF */
diff --git a/subst.c b/subst.c
index 401c2ca..e33a1dd 100644 (file)
--- a/subst.c
+++ b/subst.c
@@ -1977,8 +1977,9 @@ do_assignment_internal (string, expand)
   SHELL_VAR *entry;
 #if defined (ARRAY_VARS)
   char *t;
-  int ni, assign_list = 0;
+  int ni;
 #endif
+  int assign_list = 0;
 
   offset = assignment (string, 0);
   name = savestring (string);
@@ -2021,14 +2022,7 @@ do_assignment_internal (string, expand)
     }
 
   if (echo_command_at_execute)
-    {
-#if defined (ARRAY_VARS)
-      if (assign_list)
-       fprintf (stderr, "%s%s=(%s)\n", indirection_level_string (), name, value);
-      else
-#endif
-      fprintf (stderr, "%s%s=%s\n", indirection_level_string (), name, value);
-    }
+     xtrace_print_assignment (name, value, assign_list, 1);
 
 #define ASSIGN_RETURN(r)       do { FREE (value); free (name); return (r); } while (0)
 
index 479df1a..4071350 100644 (file)
--- a/subst.c~
+++ b/subst.c~
@@ -259,7 +259,7 @@ static intmax_t parameter_brace_expand_length __P((char *));
 
 static char *skiparith __P((char *, int));
 static int verify_substring_values __P((char *, char *, int, intmax_t *, intmax_t *));
-static int get_var_and_type __P((char *, char *, SHELL_VAR **, char **));
+static int get_var_and_type __P((char *, char *, int, SHELL_VAR **, char **));
 static char *mb_substring __P((char *, int, int));
 static char *parameter_brace_substring __P((char *, char *, char *, int));
 
@@ -1977,8 +1977,9 @@ do_assignment_internal (string, expand)
   SHELL_VAR *entry;
 #if defined (ARRAY_VARS)
   char *t;
-  int ni, assign_list = 0;
+  int ni;
 #endif
+  int assign_list = 0;
 
   offset = assignment (string, 0);
   name = savestring (string);
@@ -2022,12 +2023,14 @@ do_assignment_internal (string, expand)
 
   if (echo_command_at_execute)
     {
-#if defined (ARRAY_VARS)
+#if 0
       if (assign_list)
        fprintf (stderr, "%s%s=(%s)\n", indirection_level_string (), name, value);
       else
+       fprintf (stderr, "%s%s=%s\n", indirection_level_string (), name, value);
+#else
+       xtrace_print_assignment (name, value, assign_list, 1);
 #endif
-      fprintf (stderr, "%s%s=%s\n", indirection_level_string (), name, value);
     }
 
 #define ASSIGN_RETURN(r)       do { FREE (value); free (name); return (r); } while (0)
@@ -3530,7 +3533,7 @@ parameter_brace_remove_pattern (varname, value, patstr, rtype, quoted)
 
   this_command_name = varname;
 
-  vtype = get_var_and_type (varname, value, &v, &val);
+  vtype = get_var_and_type (varname, value, quoted, &v, &val);
   if (vtype == -1)
     return ((char *)NULL);
 
@@ -4446,7 +4449,12 @@ parameter_brace_expand_word (name, var_is_special, quoted)
   if (legal_number (name, &arg_index))
     {
       tt = get_dollar_var_value (arg_index);
-      temp = tt ? quote_escapes (tt) : (char *)NULL;
+      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)      /* ${@} */
@@ -4465,7 +4473,9 @@ parameter_brace_expand_word (name, var_is_special, quoted)
     {
       temp = array_value (name, quoted, &atype);
       if (atype == 0 && temp)
-       temp = quote_escapes (temp);
+       temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+                 ? quote_string (temp)
+                 : quote_escapes (temp);
     }
 #endif
   else if (var = find_variable (name))
@@ -4479,7 +4489,9 @@ parameter_brace_expand_word (name, var_is_special, quoted)
 #endif
 
          if (temp)
-           temp = quote_escapes (temp);
+           temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+                     ? quote_string (temp)
+                     : quote_escapes (temp);
        }
       else
        temp = (char *)NULL;
@@ -4501,6 +4513,15 @@ parameter_brace_expand_indir (name, var_is_special, quoted, quoted_dollar_atp, c
   char *temp, *t;
 
   t = parameter_brace_expand_word (name, var_is_special, quoted);
+  /* 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;
+    }
   chk_atstar (t, quoted, quoted_dollar_atp, contains_dollar_at);
   if (t == 0)
     return (t);
@@ -4895,8 +4916,9 @@ verify_substring_values (value, substr, vtype, e1p, e2p)
    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, varp, valp)
+get_var_and_type (varname, value, quoted, varp, valp)
      char *varname, *value;
+     int quoted;
      SHELL_VAR **varp;
      char **valp;
 {
@@ -4943,7 +4965,21 @@ get_var_and_type (varname, value, varp, valp)
     }
   else
 #endif
+#if 1
+    {
+      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;
+    }
+#else
   *valp = (value && vtype == VT_VARIABLE) ? dequote_escapes (value) : value;
+#endif
 
   return vtype;
 }
@@ -5002,7 +5038,7 @@ parameter_brace_substring (varname, value, substr, quoted)
 
   this_command_name = varname;
 
-  vtype = get_var_and_type (varname, value, &v, &val);
+  vtype = get_var_and_type (varname, value, quoted, &v, &val);
   if (vtype == -1)
     return ((char *)NULL);
 
@@ -5201,7 +5237,7 @@ parameter_brace_patsub (varname, value, patsub, quoted)
 
   this_command_name = varname;
 
-  vtype = get_var_and_type (varname, value, &v, &val);
+  vtype = get_var_and_type (varname, value, quoted, &v, &val);
   if (vtype == -1)
     return ((char *)NULL);
 
@@ -5736,7 +5772,16 @@ param_expand (string, sindex, quoted, expanded_something,
          last_command_exit_value = EXECUTION_FAILURE;
          return (interactive_shell ? &expand_param_error : &expand_param_fatal);
        }
+#if 1
+      if (temp1)
+       temp = (*temp1 && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+                 ? quote_string (temp1)
+                 : quote_escapes (temp1);
+      else
+       temp = (char *)NULL;
+#else
       temp = temp1 ? quote_escapes (temp1) : (char *)NULL;
+#endif
       break;
 
     /* $$ -- pid of the invoking shell. */
@@ -5827,13 +5872,10 @@ param_expand (string, sindex, quoted, expanded_something,
         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. */
-if (list && list->next)
-  {
       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.
@@ -5976,13 +6018,22 @@ comsub:
            {
              temp = array_reference (array_cell (var), 0);
              if (temp)
-               temp = quote_escapes (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 = quote_escapes (value_cell (var));
+           {
+             temp = value_cell (var);
+
+             temp = (*temp && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+                       ? quote_string (temp)
+                       : quote_escapes (temp);
+           }
+
          free (temp1);
 
          goto return0;
index 7af17ab..8bf1548 100644 (file)
--- a/syntax.h
+++ b/syntax.h
@@ -71,6 +71,8 @@ extern int sh_syntabsiz;
 #define shellbreak(c)  (sh_syntaxtab[(unsigned char)(c)] & CSHBRK)
 #define shellquote(c)  (sh_syntaxtab[(unsigned char)(c)] & CQUOTE)
 
+#define shellxquote(c) (sh_syntaxtab[(unsigned char)(c)] & CXQUOTE)
+
 #define issyntype(c, t)        ((sh_syntaxtab[(unsigned char)(c)] & (t)) != 0)
 #define notsyntype(c,t) ((sh_syntaxtab[(unsigned char)(c)] & (t)) == 0)
 
diff --git a/syntax.h~ b/syntax.h~
new file mode 100644 (file)
index 0000000..7af17ab
--- /dev/null
+++ b/syntax.h~
@@ -0,0 +1,100 @@
+/* syntax.h -- Syntax definitions for the shell */
+
+/* Copyright (C) 2000 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 2, or (at your option) any later
+   version.
+
+   Bash is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or
+   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+   for more details.
+
+   You should have received a copy of the GNU General Public License along
+   with Bash; see the file COPYING.  If not, write to the Free Software
+   Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+
+#ifndef _SYNTAX_H_
+#define _SYNTAX_H_
+
+/* Defines for use by mksyntax.c */
+
+#define slashify_in_quotes "\\`$\"\n"
+#define slashify_in_here_document "\\`$"
+
+#define shell_meta_chars   "()<>;&|"
+#define shell_break_chars  "()<>;&| \t\n"
+
+#define shell_quote_chars      "\"`'"
+
+#if defined (PROCESS_SUBSTITUTION)
+#  define shell_exp_chars              "$<>"
+#else
+#  define shell_exp_chars              "$"
+#endif
+
+#if defined (EXTENDED_GLOB)
+#  define ext_glob_chars       "@*+?!"
+#else
+#  define ext_glob_chars       ""
+#endif
+#define shell_glob_chars       "*?[]^"
+
+/* Defines shared by mksyntax.c and the rest of the shell code. */
+
+/* Values for character flags in syntax tables */
+
+#define CWORD          0x0000  /* nothing special; an ordinary character */
+#define CSHMETA                0x0001  /* shell meta character */
+#define CSHBRK         0x0002  /* shell break character */
+#define CBACKQ         0x0004  /* back quote */
+#define CQUOTE         0x0008  /* shell quote character */
+#define CSPECL         0x0010  /* special character that needs quoting */
+#define CEXP           0x0020  /* shell expansion character */
+#define CBSDQUOTE      0x0040  /* characters escaped by backslash in double quotes */
+#define CBSHDOC                0x0080  /* characters escaped by backslash in here doc */
+#define CGLOB          0x0100  /* globbing characters */
+#define CXGLOB         0x0200  /* extended globbing characters */
+#define CXQUOTE                0x0400  /* cquote + backslash */
+#define CSPECVAR       0x0800  /* single-character shell variable name */
+#define CSUBSTOP       0x1000  /* values of OP for ${word[:]OPstuff} */
+
+/* Defines for use by the rest of the shell. */
+extern int sh_syntaxtab[];
+extern int sh_syntabsiz;
+
+#define shellmeta(c)   (sh_syntaxtab[(unsigned char)(c)] & CSHMETA)
+#define shellbreak(c)  (sh_syntaxtab[(unsigned char)(c)] & CSHBRK)
+#define shellquote(c)  (sh_syntaxtab[(unsigned char)(c)] & CQUOTE)
+
+#define issyntype(c, t)        ((sh_syntaxtab[(unsigned char)(c)] & (t)) != 0)
+#define notsyntype(c,t) ((sh_syntaxtab[(unsigned char)(c)] & (t)) == 0)
+
+#if defined (PROCESS_SUBSTITUTION)
+#  define shellexp(c)  ((c) == '$' || (c) == '<' || (c) == '>')
+#else
+#  define shellexp(c)  ((c) == '$')
+#endif
+
+#if defined (EXTENDED_GLOB)
+#  define PATTERN_CHAR(c) \
+       ((c) == '@' || (c) == '*' || (c) == '+' || (c) == '?' || (c) == '!')
+#else
+#  define PATTERN_CHAR(c) 0
+#endif
+
+#define GLOB_CHAR(c) \
+       ((c) == '*' || (c) == '?' || (c) == '[' || (c) == ']' || (c) == '^')
+
+#define CTLESC '\001'
+#define CTLNUL '\177'
+
+#if !defined (HAVE_ISBLANK) && !defined (isblank)
+#  define isblank(x)   ((x) == ' ' || (x) == '\t')
+#endif
+
+#endif /* _SYNTAX_H_ */
index 3efcf32..72ec06a 100755 (executable)
@@ -1,4 +1,4 @@
-BUILD_DIR=/usr/local/build/chet/bash/bash-current
+BUILD_DIR=/usr/local/build/bash/bash-current
 THIS_SH=$BUILD_DIR/bash
 PATH=$PATH:$BUILD_DIR
 
index b00101c..9054fd0 100644 (file)
@@ -1,7 +1,7 @@
 ./errors.tests: line 17: alias: -x: invalid option
 alias: usage: alias [-p] [name[=value] ... ]
 ./errors.tests: line 18: unalias: -x: invalid option
-unalias: usage: unalias [-a] [name ...]
+unalias: usage: unalias [-a] name [name ...]
 ./errors.tests: line 19: alias: hoowah: not found
 ./errors.tests: line 20: unalias: hoowah: not found
 ./errors.tests: line 23: `1': not a valid identifier
diff --git a/tests/errors.right~ b/tests/errors.right~
new file mode 100644 (file)
index 0000000..b00101c
--- /dev/null
@@ -0,0 +1,100 @@
+./errors.tests: line 17: alias: -x: invalid option
+alias: usage: alias [-p] [name[=value] ... ]
+./errors.tests: line 18: unalias: -x: invalid option
+unalias: usage: unalias [-a] [name ...]
+./errors.tests: line 19: alias: hoowah: not found
+./errors.tests: line 20: unalias: hoowah: not found
+./errors.tests: line 23: `1': not a valid identifier
+declare -fr func
+./errors.tests: line 36: func: readonly function
+./errors.tests: line 39: unset: -x: invalid option
+unset: usage: unset [-f] [-v] [name ...]
+./errors.tests: line 42: unset: func: cannot unset: readonly function
+./errors.tests: line 45: declare: func: readonly function
+./errors.tests: line 49: unset: XPATH: cannot unset: readonly variable
+./errors.tests: line 52: unset: `/bin/sh': not a valid identifier
+./errors.tests: line 55: unset: cannot simultaneously unset a function and a variable
+./errors.tests: line 58: declare: -z: invalid option
+declare: usage: declare [-afFirtx] [-p] [name[=value] ...]
+./errors.tests: line 60: declare: `-z': not a valid identifier
+./errors.tests: line 61: declare: `/bin/sh': not a valid identifier
+./errors.tests: line 65: declare: cannot use `-f' to make functions
+./errors.tests: line 68: exec: -i: invalid option
+exec: usage: exec [-cl] [-a name] file [redirection ...]
+./errors.tests: line 72: export: XPATH: not a function
+./errors.tests: line 75: break: only meaningful in a `for', `while', or `until' loop
+./errors.tests: line 76: continue: only meaningful in a `for', `while', or `until' loop
+./errors.tests: line 79: shift: label: numeric argument required
+./errors.tests: line 84: shift: too many arguments
+./errors.tests: line 90: let: expression expected
+./errors.tests: line 93: local: can only be used in a function
+./errors.tests: line 96: logout: not login shell: use `exit'
+./errors.tests: line 99: hash: notthere: not found
+./errors.tests: line 102: hash: -v: invalid option
+hash: usage: hash [-lr] [-p pathname] [-dt] [name ...]
+./errors.tests: line 106: hash: hashing disabled
+./errors.tests: line 109: export: `AA[4]': not a valid identifier
+./errors.tests: line 110: readonly: `AA[4]': not a valid identifier
+./errors.tests: line 113: [-2]: bad array subscript
+./errors.tests: line 117: AA: readonly variable
+./errors.tests: line 121: AA: readonly variable
+./errors.tests: line 129: shift: 5: shift count out of range
+./errors.tests: line 130: shift: -2: shift count out of range
+./errors.tests: line 133: shopt: no_such_option: invalid shell option name
+./errors.tests: line 134: shopt: no_such_option: invalid shell option name
+./errors.tests: line 137: umask: 09: octal number out of range
+./errors.tests: line 138: umask: `:': invalid symbolic mode character
+./errors.tests: line 139: umask: `:': invalid symbolic mode operator
+./errors.tests: line 142: umask: -i: invalid option
+umask: usage: umask [-p] [-S] [mode]
+./errors.tests: line 146: umask: `u': invalid symbolic mode character
+./errors.tests: line 155: VAR: readonly variable
+./errors.tests: line 158: declare: VAR: readonly variable
+./errors.tests: line 159: declare: VAR: readonly variable
+./errors.tests: line 161: declare: unset: not found
+./errors.tests: line 164: VAR: readonly variable
+./errors.tests: command substitution: line 168: syntax error: unexpected end of file
+./errors.tests: command substitution: line 168: syntax error near unexpected token `done'
+./errors.tests: command substitution: line 168: ` for z in 1 2 3; done '
+./errors.tests: line 171: cd: HOME not set
+./errors.tests: line 172: cd: /tmp/xyz.bash: No such file or directory
+./errors.tests: line 174: cd: OLDPWD not set
+./errors.tests: line 175: cd: /bin/sh: Not a directory
+./errors.tests: line 177: cd: /tmp/cd-notthere: No such file or directory
+./errors.tests: line 180: .: filename argument required
+.: usage: . filename [arguments]
+./errors.tests: line 181: source: filename argument required
+source: usage: source filename [arguments]
+./errors.tests: line 184: .: -i: invalid option
+.: usage: . filename [arguments]
+./errors.tests: line 187: set: -q: invalid option
+set: usage: set [--abefhkmnptuvxBCHP] [-o option] [arg ...]
+./errors.tests: line 190: enable: sh: not a shell builtin
+./errors.tests: line 190: enable: bash: not a shell builtin
+./errors.tests: line 193: shopt: cannot set and unset shell options simultaneously
+./errors.tests: line 196: read: var: invalid timeout specification
+./errors.tests: line 199: read: `/bin/sh': not a valid identifier
+./errors.tests: line 202: VAR: readonly variable
+./errors.tests: line 205: readonly: -x: invalid option
+readonly: usage: readonly [-af] [name[=value] ...] or readonly -p
+./errors.tests: line 208: eval: -i: invalid option
+eval: usage: eval [arg ...]
+./errors.tests: line 209: command: -i: invalid option
+command: usage: command [-pVv] command [arg ...]
+./errors.tests: line 212: /bin/sh + 0: syntax error: operand expected (error token is "/bin/sh + 0")
+./errors.tests: line 213: /bin/sh + 0: syntax error: operand expected (error token is "/bin/sh + 0")
+./errors.tests: line 216: trap: NOSIG: invalid signal specification
+./errors.tests: line 219: trap: -s: invalid option
+trap: usage: trap [arg] [signal_spec ...] or trap -l
+./errors.tests: line 225: return: can only `return' from a function or sourced script
+./errors.tests: line 229: break: 0: loop count out of range
+./errors.tests: line 233: continue: 0: loop count out of range
+./errors.tests: line 238: builtin: bash: not a shell builtin
+./errors.tests: line 242: bg: no job control
+./errors.tests: line 243: fg: no job control
+./errors.tests: line 246: kill: -s: option requires an argument
+./errors.tests: line 248: kill: S: invalid signal specification
+./errors.tests: line 250: kill: `': not a pid or valid job spec
+kill: usage: kill [-s sigspec | -n signum | -sigspec] [pid | job]... or kill -l [sigspec]
+./errors.tests: line 255: set: trackall: invalid option name
+./errors.tests: line 262: `!!': not a valid identifier
index c62c0d8..d99140b 100644 (file)
@@ -2118,12 +2118,9 @@ assign_in_env (string)
     setifs (var);
 
   if (echo_command_at_execute)
-    {
-      /* The Korn shell prints the `+ ' in front of assignment statements,
-        so we do too. */
-      fprintf (stderr, "%s%s=%s\n", indirection_level_string (), name, value);
-      fflush (stderr);
-    }
+    /* The Korn shell prints the `+ ' in front of assignment statements,
+       so we do too. */
+    xtrace_print_assignment (name, value, 0, 1);
 
   free (name);
   return 1;
diff --git a/variables.c~ b/variables.c~
new file mode 100644 (file)
index 0000000..592602d
--- /dev/null
@@ -0,0 +1,4092 @@
+/* variables.c -- Functions for hacking shell variables. */
+
+/* Copyright (C) 1987-2004 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 2, or (at your option)
+   any later version.
+
+   Bash is distributed in the hope that it will be useful, but WITHOUT
+   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
+   License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with Bash; see the file COPYING.  If not, write to the Free
+   Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+
+#include "config.h"
+
+#include "bashtypes.h"
+#include "posixstat.h"
+#include "posixtime.h"
+
+#if defined (qnx)
+#  include <sys/vc.h>
+#endif
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include "chartypes.h"
+#include <pwd.h>
+#include "bashansi.h"
+#include "bashintl.h"
+
+#include "shell.h"
+#include "flags.h"
+#include "execute_cmd.h"
+#include "findcmd.h"
+#include "mailcheck.h"
+#include "input.h"
+#include "hashcmd.h"
+#include "pathexp.h"
+
+#include "builtins/getopt.h"
+#include "builtins/common.h"
+
+#if defined (READLINE)
+#  include "bashline.h"
+#  include <readline/readline.h>
+#else
+#  include <tilde/tilde.h>
+#endif
+
+#if defined (HISTORY)
+#  include "bashhist.h"
+#  include <readline/history.h>
+#endif /* HISTORY */
+
+#if defined (PROGRAMMABLE_COMPLETION)
+#  include "pcomplete.h"
+#endif
+
+#define TEMPENV_HASH_BUCKETS   4       /* must be power of two */
+
+#define ifsname(s)     ((s)[0] == 'I' && (s)[1] == 'F' && (s)[2] == 'S' && (s)[3] == '\0')
+
+/* Variables used here and defined in other files. */
+extern int posixly_correct;
+extern int line_number;
+extern int subshell_environment, indirection_level, subshell_level;
+extern int build_version, patch_level;
+extern int expanding_redir;
+extern char *dist_version, *release_status;
+extern char *shell_name;
+extern char *primary_prompt, *secondary_prompt;
+extern char *current_host_name;
+extern sh_builtin_func_t *this_shell_builtin;
+extern SHELL_VAR *this_shell_function;
+extern char *the_printed_command_except_trap;
+extern char *this_command_name;
+extern char *command_execution_string;
+extern time_t shell_start_time;
+
+#if defined (READLINE)
+extern int perform_hostname_completion;
+#endif
+
+/* The list of shell variables that the user has created at the global
+   scope, or that came from the environment. */
+VAR_CONTEXT *global_variables = (VAR_CONTEXT *)NULL;
+
+/* The current list of shell variables, including function scopes */
+VAR_CONTEXT *shell_variables = (VAR_CONTEXT *)NULL;
+
+/* The list of shell functions that the user has created, or that came from
+   the environment. */
+HASH_TABLE *shell_functions = (HASH_TABLE *)NULL;
+
+#if defined (DEBUGGER)
+/* The table of shell function definitions that the user defined or that
+   came from the environment. */
+HASH_TABLE *shell_function_defs = (HASH_TABLE *)NULL;
+#endif
+
+/* The current variable context.  This is really a count of how deep into
+   executing functions we are. */
+int variable_context = 0;
+
+/* The set of shell assignments which are made only in the environment
+   for a single command. */
+HASH_TABLE *temporary_env = (HASH_TABLE *)NULL;
+
+/* Some funky variables which are known about specially.  Here is where
+   "$*", "$1", and all the cruft is kept. */
+char *dollar_vars[10];
+WORD_LIST *rest_of_args = (WORD_LIST *)NULL;
+
+/* The value of $$. */
+pid_t dollar_dollar_pid;
+
+/* An array which is passed to commands as their environment.  It is
+   manufactured from the union of the initial environment and the
+   shell variables that are marked for export. */
+char **export_env = (char **)NULL;
+static int export_env_index;
+static int export_env_size;
+
+/* Non-zero means that we have to remake EXPORT_ENV. */
+int array_needs_making = 1;
+
+/* The number of times BASH has been executed.  This is set
+   by initialize_variables (). */
+int shell_level = 0;
+
+/* Some forward declarations. */
+static void set_machine_vars __P((void));
+static void set_home_var __P((void));
+static void set_shell_var __P((void));
+static char *get_bash_name __P((void));
+static void initialize_shell_level __P((void));
+static void uidset __P((void));
+#if defined (ARRAY_VARS)
+static void make_vers_array __P((void));
+#endif
+
+static SHELL_VAR *null_assign __P((SHELL_VAR *, char *, arrayind_t));
+#if defined (ARRAY_VARS)
+static SHELL_VAR *null_array_assign __P((SHELL_VAR *, char *, arrayind_t));
+#endif
+static SHELL_VAR *get_self __P((SHELL_VAR *));
+
+#if defined (ARRAY_VARS)
+static SHELL_VAR *init_dynamic_array_var __P((char *, sh_var_value_func_t *, sh_var_assign_func_t *, int));
+#endif
+
+static SHELL_VAR *assign_seconds __P((SHELL_VAR *, char *, arrayind_t));
+static SHELL_VAR *get_seconds __P((SHELL_VAR *));
+static SHELL_VAR *init_seconds_var __P((void));
+
+static int brand __P((void));
+static void sbrand __P((unsigned long));               /* set bash random number generator. */
+static SHELL_VAR *assign_random __P((SHELL_VAR *, char *, arrayind_t));
+static SHELL_VAR *get_random __P((SHELL_VAR *));
+
+static SHELL_VAR *assign_lineno __P((SHELL_VAR *, char *, arrayind_t));
+static SHELL_VAR *get_lineno __P((SHELL_VAR *));
+
+static SHELL_VAR *assign_subshell __P((SHELL_VAR *, char *, arrayind_t));
+static SHELL_VAR *get_subshell __P((SHELL_VAR *));
+
+#if defined (HISTORY)
+static SHELL_VAR *get_histcmd __P((SHELL_VAR *));
+#endif
+
+#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS)
+static SHELL_VAR *assign_dirstack __P((SHELL_VAR *, char *, arrayind_t));
+static SHELL_VAR *get_dirstack __P((SHELL_VAR *));
+#endif
+
+#if defined (ARRAY_VARS)
+static SHELL_VAR *get_groupset __P((SHELL_VAR *));
+#endif
+
+static SHELL_VAR *get_funcname __P((SHELL_VAR *));
+static SHELL_VAR *init_funcname_var __P((void));
+
+static void initialize_dynamic_variables __P((void));
+
+static SHELL_VAR *hash_lookup __P((const char *, HASH_TABLE *));
+static SHELL_VAR *new_shell_variable __P((const char *));
+static SHELL_VAR *make_new_variable __P((const char *, HASH_TABLE *));
+static SHELL_VAR *bind_variable_internal __P((const char *, char *, HASH_TABLE *, int));
+
+static void free_variable_hash_data __P((PTR_T));
+
+static VARLIST *vlist_alloc __P((int));
+static VARLIST *vlist_realloc __P((VARLIST *, int));
+static void vlist_add __P((VARLIST *, SHELL_VAR *, int));
+
+static void flatten __P((HASH_TABLE *, sh_var_map_func_t *, VARLIST *, int));
+
+static int qsort_var_comp __P((SHELL_VAR **, SHELL_VAR **));
+
+static SHELL_VAR **vapply __P((sh_var_map_func_t *));
+static SHELL_VAR **fapply __P((sh_var_map_func_t *));
+
+static int visible_var __P((SHELL_VAR *));
+static int visible_and_exported __P((SHELL_VAR *));
+static int local_and_exported __P((SHELL_VAR *));
+static int variable_in_context __P((SHELL_VAR *));
+#if defined (ARRAY_VARS)
+static int visible_array_vars __P((SHELL_VAR *));
+#endif
+
+static SHELL_VAR *bind_tempenv_variable __P((const char *, char *));
+static void push_temp_var __P((PTR_T));
+static void propagate_temp_var __P((PTR_T));
+static void dispose_temporary_env __P((sh_free_func_t *));     
+
+static inline char *mk_env_string __P((const char *, const char *));
+static char **make_env_array_from_var_list __P((SHELL_VAR **));
+static char **make_var_export_array __P((VAR_CONTEXT *));
+static char **make_func_export_array __P((void));
+static void add_temp_array_to_env __P((char **, int, int));
+
+static int n_shell_variables __P((void));
+static int set_context __P((SHELL_VAR *));
+
+static void push_func_var __P((PTR_T));
+static void push_exported_var __P((PTR_T));
+
+static inline int find_special_var __P((const char *));
+              
+/* Initialize the shell variables from the current environment.
+   If PRIVMODE is nonzero, don't import functions from ENV or
+   parse $SHELLOPTS. */
+void
+initialize_shell_variables (env, privmode)
+     char **env;
+     int privmode;
+{
+  char *name, *string, *temp_string;
+  int c, char_index, string_index, string_length;
+  SHELL_VAR *temp_var;
+
+  if (shell_variables == 0)
+    {
+      shell_variables = global_variables = new_var_context ((char *)NULL, 0);
+      shell_variables->scope = 0;
+      shell_variables->table = hash_create (0);
+    }
+
+  if (shell_functions == 0)
+    shell_functions = hash_create (0);
+
+#if defined (DEBUGGER)
+  if (shell_function_defs == 0)
+    shell_function_defs = hash_create (0);
+#endif
+
+  for (string_index = 0; string = env[string_index++]; )
+    {
+      char_index = 0;
+      name = string;
+      while ((c = *string++) && c != '=')
+       ;
+      if (string[-1] == '=')
+       char_index = string - name - 1;
+
+      /* If there are weird things in the environment, like `=xxx' or a
+        string without an `=', just skip them. */
+      if (char_index == 0)
+       continue;
+
+      /* ASSERT(name[char_index] == '=') */
+      name[char_index] = '\0';
+      /* Now, name = env variable name, string = env variable value, and
+        char_index == strlen (name) */
+
+      /* If exported function, define it now.  Don't import functions from
+        the environment in privileged mode. */
+      if (privmode == 0 && read_but_dont_execute == 0 && STREQN ("() {", string, 4))
+       {
+         string_length = strlen (string);
+         temp_string = (char *)xmalloc (3 + string_length + char_index);
+
+         strcpy (temp_string, name);
+         temp_string[char_index] = ' ';
+         strcpy (temp_string + char_index + 1, string);
+
+         parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST);
+
+         /* Ancient backwards compatibility.  Old versions of bash exported
+            functions like name()=() {...} */
+         if (name[char_index - 1] == ')' && name[char_index - 2] == '(')
+           name[char_index - 2] = '\0';
+
+         if (temp_var = find_function (name))
+           {
+             VSETATTR (temp_var, (att_exported|att_imported));
+             array_needs_making = 1;
+           }
+         else
+           report_error (_("error importing function definition for `%s'"), name);
+
+         /* ( */
+         if (name[char_index - 1] == ')' && name[char_index - 2] == '\0')
+           name[char_index - 2] = '(';         /* ) */
+       }
+#if defined (ARRAY_VARS)
+#  if 0
+      /* Array variables may not yet be exported. */
+      else if (*string == '(' && string[1] == '[' && xstrchr (string, ')'))
+       {
+         string_length = 1;
+         temp_string = extract_array_assignment_list (string, &string_length);
+         temp_var = assign_array_from_string (name, temp_string);
+         FREE (temp_string);
+         VSETATTR (temp_var, (att_exported | att_imported));
+         array_needs_making = 1;
+       }
+#  endif
+#endif
+      else
+       {
+         temp_var = bind_variable (name, string);
+         VSETATTR (temp_var, (att_exported | att_imported));
+         array_needs_making = 1;
+       }
+
+      name[char_index] = '=';
+      /* temp_var can be NULL if it was an exported function with a syntax
+        error (a different bug, but it still shouldn't dump core). */
+      if (temp_var && function_p (temp_var) == 0)      /* XXX not yet */
+       {
+         CACHE_IMPORTSTR (temp_var, name);
+       }
+    }
+
+  set_pwd ();
+
+  /* Set up initial value of $_ */
+  temp_var = bind_variable ("_", dollar_vars[0]);
+
+  /* Remember this pid. */
+  dollar_dollar_pid = getpid ();
+
+  /* Now make our own defaults in case the vars that we think are
+     important are missing. */
+  temp_var = set_if_not ("PATH", DEFAULT_PATH_VALUE);
+#if 0
+  set_auto_export (temp_var);  /* XXX */
+#endif
+
+  temp_var = set_if_not ("TERM", "dumb");
+#if 0
+  set_auto_export (temp_var);  /* XXX */
+#endif
+
+#if defined (qnx)
+  /* set node id -- don't import it from the environment */
+  {
+    char node_name[22];
+    qnx_nidtostr (getnid (), node_name, sizeof (node_name));
+    temp_var = bind_variable ("NODE", node_name);
+    set_auto_export (temp_var);
+  }
+#endif
+
+  /* set up the prompts. */
+  if (interactive_shell)
+    {
+#if defined (PROMPT_STRING_DECODE)
+      set_if_not ("PS1", primary_prompt);
+#else
+      if (current_user.uid == -1)
+       get_current_user_info ();
+      set_if_not ("PS1", current_user.euid == 0 ? "# " : primary_prompt);
+#endif
+      set_if_not ("PS2", secondary_prompt);
+    }
+  set_if_not ("PS4", "+ ");
+
+  /* Don't allow IFS to be imported from the environment. */
+  temp_var = bind_variable ("IFS", " \t\n");
+  setifs (temp_var);
+
+  /* Magic machine types.  Pretty convenient. */
+  set_machine_vars ();
+
+  /* Default MAILCHECK for interactive shells.  Defer the creation of a
+     default MAILPATH until the startup files are read, because MAIL
+     names a mail file if MAILPATH is not set, and we should provide a
+     default only if neither is set. */
+  if (interactive_shell)
+    set_if_not ("MAILCHECK", posixly_correct ? "600" : "60");
+
+  /* Do some things with shell level. */
+  initialize_shell_level ();
+
+  set_ppid ();
+
+  /* Initialize the `getopts' stuff. */
+  bind_variable ("OPTIND", "1");
+  getopts_reset (0);
+  bind_variable ("OPTERR", "1");
+  sh_opterr = 1;
+
+  if (login_shell == 1)
+    set_home_var ();
+
+  /* Get the full pathname to THIS shell, and set the BASH variable
+     to it. */
+  name = get_bash_name ();
+  temp_var = bind_variable ("BASH", name);
+  free (name);
+
+  /* Make the exported environment variable SHELL be the user's login
+     shell.  Note that the `tset' command looks at this variable
+     to determine what style of commands to output; if it ends in "csh",
+     then C-shell commands are output, else Bourne shell commands. */
+  set_shell_var ();
+
+  /* Make a variable called BASH_VERSION which contains the version info. */
+  bind_variable ("BASH_VERSION", shell_version_string ());
+#if defined (ARRAY_VARS)
+  make_vers_array ();
+#endif
+
+  if (command_execution_string)
+    bind_variable ("BASH_EXECUTION_STRING", command_execution_string);
+
+  /* Find out if we're supposed to be in Posix.2 mode via an
+     environment variable. */
+  temp_var = find_variable ("POSIXLY_CORRECT");
+  if (!temp_var)
+    temp_var = find_variable ("POSIX_PEDANTIC");
+  if (temp_var && imported_p (temp_var))
+    sv_strict_posix (temp_var->name);
+
+#if defined (HISTORY)
+  /* Set history variables to defaults, and then do whatever we would
+     do if the variable had just been set.  Do this only in the case
+     that we are remembering commands on the history list. */
+  if (remember_on_history)
+    {
+      name = bash_tilde_expand (posixly_correct ? "~/.sh_history" : "~/.bash_history", 0);
+
+      set_if_not ("HISTFILE", name);
+      free (name);
+
+      set_if_not ("HISTSIZE", "500");
+      sv_histsize ("HISTSIZE");
+    }
+#endif /* HISTORY */
+
+  /* Seed the random number generator. */
+  sbrand (dollar_dollar_pid + shell_start_time);
+
+  /* Handle some "special" variables that we may have inherited from a
+     parent shell. */
+  if (interactive_shell)
+    {
+      temp_var = find_variable ("IGNOREEOF");
+      if (!temp_var)
+       temp_var = find_variable ("ignoreeof");
+      if (temp_var && imported_p (temp_var))
+       sv_ignoreeof (temp_var->name);
+    }
+
+#if defined (HISTORY)
+  if (interactive_shell && remember_on_history)
+    {
+      sv_history_control ("HISTCONTROL");
+      sv_histignore ("HISTIGNORE");
+    }
+#endif /* HISTORY */
+
+     /*
+      * 24 October 2001
+      *
+      * I'm tired of the arguing and bug reports.  Bash now leaves SSH_CLIENT
+      * and SSH2_CLIENT alone.  I'm going to rely on the shell_level check in
+      * isnetconn() to avoid running the startup files more often than wanted.
+      * That will, of course, only work if the user's login shell is bash, so
+      * I've made that behavior conditional on SSH_SOURCE_BASHRC being defined
+      * in config-top.h.
+      */
+#if 0
+  temp_var = find_variable ("SSH_CLIENT");
+  if (temp_var && imported_p (temp_var))
+    {
+      VUNSETATTR (temp_var, att_exported);
+      array_needs_making = 1;
+    }
+  temp_var = find_variable ("SSH2_CLIENT");
+  if (temp_var && imported_p (temp_var))
+    {
+      VUNSETATTR (temp_var, att_exported);
+      array_needs_making = 1;
+    }
+#endif
+
+  /* Get the user's real and effective user ids. */
+  uidset ();
+
+  /* Initialize the dynamic variables, and seed their values. */
+  initialize_dynamic_variables ();
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*          Setting values for special shell variables             */
+/*                                                                 */
+/* **************************************************************** */
+
+static void
+set_machine_vars ()
+{
+  SHELL_VAR *temp_var;
+
+  temp_var = set_if_not ("HOSTTYPE", HOSTTYPE);
+  temp_var = set_if_not ("OSTYPE", OSTYPE);
+  temp_var = set_if_not ("MACHTYPE", MACHTYPE);
+
+  temp_var = set_if_not ("HOSTNAME", current_host_name);
+}
+
+/* Set $HOME to the information in the password file if we didn't get
+   it from the environment. */
+
+/* This function is not static so the tilde and readline libraries can
+   use it. */
+char *
+sh_get_home_dir ()
+{
+  if (current_user.home_dir == 0)
+    get_current_user_info ();
+  return current_user.home_dir;
+}
+
+static void
+set_home_var ()
+{
+  SHELL_VAR *temp_var;
+
+  temp_var = find_variable ("HOME");
+  if (temp_var == 0)
+    temp_var = bind_variable ("HOME", sh_get_home_dir ());
+#if 0
+  VSETATTR (temp_var, att_exported);
+#endif
+}
+
+/* Set $SHELL to the user's login shell if it is not already set.  Call
+   get_current_user_info if we haven't already fetched the shell. */
+static void
+set_shell_var ()
+{
+  SHELL_VAR *temp_var;
+
+  temp_var = find_variable ("SHELL");
+  if (temp_var == 0)
+    {
+      if (current_user.shell == 0)
+       get_current_user_info ();
+      temp_var = bind_variable ("SHELL", current_user.shell);
+    }
+#if 0
+  VSETATTR (temp_var, att_exported);
+#endif
+}
+
+static char *
+get_bash_name ()
+{
+  char *name;
+
+  if ((login_shell == 1) && RELPATH(shell_name))
+    {
+      if (current_user.shell == 0)
+       get_current_user_info ();
+      name = savestring (current_user.shell);
+    }
+  else if (ABSPATH(shell_name))
+    name = savestring (shell_name);
+  else if (shell_name[0] == '.' && shell_name[1] == '/')
+    {
+      /* Fast path for common case. */
+      char *cdir;
+      int len;
+
+      cdir = get_string_value ("PWD");
+      if (cdir)
+       {
+         len = strlen (cdir);
+         name = (char *)xmalloc (len + strlen (shell_name) + 1);
+         strcpy (name, cdir);
+         strcpy (name + len, shell_name + 1);
+       }
+      else
+       name = savestring (shell_name);
+    }
+  else
+    {
+      char *tname;
+      int s;
+
+      tname = find_user_command (shell_name);
+
+      if (tname == 0)
+       {
+         /* Try the current directory.  If there is not an executable
+            there, just punt and use the login shell. */
+         s = file_status (shell_name);
+         if (s & FS_EXECABLE)
+           {
+             tname = make_absolute (shell_name, get_string_value ("PWD"));
+             if (*shell_name == '.')
+               {
+                 name = sh_canonpath (tname, PATH_CHECKDOTDOT|PATH_CHECKEXISTS);
+                 if (name == 0)
+                   name = tname;
+                 else
+                   free (tname);
+               }
+            else
+               name = tname;
+           }
+         else
+           {
+             if (current_user.shell == 0)
+               get_current_user_info ();
+             name = savestring (current_user.shell);
+           }
+       }
+      else
+       {
+         name = full_pathname (tname);
+         free (tname);
+       }
+    }
+
+  return (name);
+}
+
+void
+adjust_shell_level (change)
+     int change;
+{
+  char new_level[5], *old_SHLVL;
+  intmax_t old_level;
+  SHELL_VAR *temp_var;
+
+  old_SHLVL = get_string_value ("SHLVL");
+  if (old_SHLVL == 0 || *old_SHLVL == '\0' || legal_number (old_SHLVL, &old_level) == 0)
+    old_level = 0;
+
+  shell_level = old_level + change;
+  if (shell_level < 0)
+    shell_level = 0;
+  else if (shell_level > 1000)
+    {
+      internal_warning (_("shell level (%d) too high, resetting to 1"), shell_level);
+      shell_level = 1;
+    }
+
+  /* We don't need the full generality of itos here. */
+  if (shell_level < 10)
+    {
+      new_level[0] = shell_level + '0';
+      new_level[1] = '\0';
+    }
+  else if (shell_level < 100)
+    {
+      new_level[0] = (shell_level / 10) + '0';
+      new_level[1] = (shell_level % 10) + '0';
+      new_level[2] = '\0';
+    }
+  else if (shell_level < 1000)
+    {
+      new_level[0] = (shell_level / 100) + '0';
+      old_level = shell_level % 100;
+      new_level[1] = (old_level / 10) + '0';
+      new_level[2] = (old_level % 10) + '0';
+      new_level[3] = '\0';
+    }
+
+  temp_var = bind_variable ("SHLVL", new_level);
+  set_auto_export (temp_var);
+}
+
+static void
+initialize_shell_level ()
+{
+  adjust_shell_level (1);
+}
+
+/* If we got PWD from the environment, update our idea of the current
+   working directory.  In any case, make sure that PWD exists before
+   checking it.  It is possible for getcwd () to fail on shell startup,
+   and in that case, PWD would be undefined.  If this is an interactive
+   login shell, see if $HOME is the current working directory, and if
+   that's not the same string as $PWD, set PWD=$HOME. */
+
+void
+set_pwd ()
+{
+  SHELL_VAR *temp_var, *home_var;
+  char *temp_string, *home_string;
+
+  home_var = find_variable ("HOME");
+  home_string = home_var ? value_cell (home_var) : (char *)NULL;
+
+  temp_var = find_variable ("PWD");
+  if (temp_var && imported_p (temp_var) &&
+      (temp_string = value_cell (temp_var)) &&
+      same_file (temp_string, ".", (struct stat *)NULL, (struct stat *)NULL))
+    set_working_directory (temp_string);
+  else if (home_string && interactive_shell && login_shell &&
+          same_file (home_string, ".", (struct stat *)NULL, (struct stat *)NULL))
+    {
+      set_working_directory (home_string);
+      temp_var = bind_variable ("PWD", home_string);
+      set_auto_export (temp_var);
+    }
+  else
+    {
+      temp_string = get_working_directory ("shell-init");
+      if (temp_string)
+       {
+         temp_var = bind_variable ("PWD", temp_string);
+         set_auto_export (temp_var);
+         free (temp_string);
+       }
+    }
+
+  /* According to the Single Unix Specification, v2, $OLDPWD is an
+     `environment variable' and therefore should be auto-exported.
+     Make a dummy invisible variable for OLDPWD, and mark it as exported. */
+  temp_var = bind_variable ("OLDPWD", (char *)NULL);
+  VSETATTR (temp_var, (att_exported | att_invisible));
+}
+
+/* Make a variable $PPID, which holds the pid of the shell's parent.  */
+void
+set_ppid ()
+{
+  char namebuf[INT_STRLEN_BOUND(pid_t) + 1], *name;
+  SHELL_VAR *temp_var;
+
+  name = inttostr (getppid (), namebuf, sizeof(namebuf));
+  temp_var = find_variable ("PPID");
+  if (temp_var)
+    VUNSETATTR (temp_var, (att_readonly | att_exported));
+  temp_var = bind_variable ("PPID", name);
+  VSETATTR (temp_var, (att_readonly | att_integer));
+}
+
+static void
+uidset ()
+{
+  char buff[INT_STRLEN_BOUND(uid_t) + 1], *b;
+  register SHELL_VAR *v;
+
+  b = inttostr (current_user.uid, buff, sizeof (buff));
+  v = find_variable ("UID");
+  if (v == 0)
+    {
+      v = bind_variable ("UID", b);
+      VSETATTR (v, (att_readonly | att_integer));
+    }
+
+  if (current_user.euid != current_user.uid)
+    b = inttostr (current_user.euid, buff, sizeof (buff));
+
+  v = find_variable ("EUID");
+  if (v == 0)
+    {
+      v = bind_variable ("EUID", b);
+      VSETATTR (v, (att_readonly | att_integer));
+    }
+}
+
+#if defined (ARRAY_VARS)
+static void
+make_vers_array ()
+{
+  SHELL_VAR *vv;
+  ARRAY *av;
+  char *s, d[32], b[INT_STRLEN_BOUND(int) + 1];
+
+  unbind_variable ("BASH_VERSINFO");
+
+  vv = make_new_array_variable ("BASH_VERSINFO");
+  av = array_cell (vv);
+  strcpy (d, dist_version);
+  s = xstrchr (d, '.');
+  if (s)
+    *s++ = '\0';
+  array_insert (av, 0, d);
+  array_insert (av, 1, s);
+  s = inttostr (patch_level, b, sizeof (b));
+  array_insert (av, 2, s);
+  s = inttostr (build_version, b, sizeof (b));
+  array_insert (av, 3, s);
+  array_insert (av, 4, release_status);
+  array_insert (av, 5, MACHTYPE);
+
+  VSETATTR (vv, att_readonly);
+}
+#endif /* ARRAY_VARS */
+
+/* Set the environment variables $LINES and $COLUMNS in response to
+   a window size change. */
+void
+sh_set_lines_and_columns (lines, cols)
+     int lines, cols;
+{
+  char val[INT_STRLEN_BOUND(int) + 1], *v;
+
+  v = inttostr (lines, val, sizeof (val));
+  bind_variable ("LINES", v);
+
+  v = inttostr (cols, val, sizeof (val));
+  bind_variable ("COLUMNS", v);
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                Printing variables and values                    */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Print LIST (a list of shell variables) to stdout in such a way that
+   they can be read back in. */
+void
+print_var_list (list)
+     register SHELL_VAR **list;
+{
+  register int i;
+  register SHELL_VAR *var;
+
+  for (i = 0; list && (var = list[i]); i++)
+    if (invisible_p (var) == 0)
+      print_assignment (var);
+}
+
+/* Print LIST (a list of shell functions) to stdout in such a way that
+   they can be read back in. */
+void
+print_func_list (list)
+     register SHELL_VAR **list;
+{
+  register int i;
+  register SHELL_VAR *var;
+
+  for (i = 0; list && (var = list[i]); i++)
+    {
+      printf ("%s ", var->name);
+      print_var_function (var);
+      printf ("\n");
+    }
+}
+      
+/* Print the value of a single SHELL_VAR.  No newline is
+   output, but the variable is printed in such a way that
+   it can be read back in. */
+void
+print_assignment (var)
+     SHELL_VAR *var;
+{
+  if (var_isset (var) == 0)
+    return;
+
+  if (function_p (var))
+    {
+      printf ("%s", var->name);
+      print_var_function (var);
+      printf ("\n");
+    }
+#if defined (ARRAY_VARS)
+  else if (array_p (var))
+    print_array_assignment (var, 0);
+#endif /* ARRAY_VARS */
+  else
+    {
+      printf ("%s=", var->name);
+      print_var_value (var, 1);
+      printf ("\n");
+    }
+}
+
+/* Print the value cell of VAR, a shell variable.  Do not print
+   the name, nor leading/trailing newline.  If QUOTE is non-zero,
+   and the value contains shell metacharacters, quote the value
+   in such a way that it can be read back in. */
+void
+print_var_value (var, quote)
+     SHELL_VAR *var;
+     int quote;
+{
+  char *t;
+
+  if (var_isset (var) == 0)
+    return;
+
+  if (quote && posixly_correct == 0 && ansic_shouldquote (value_cell (var)))
+    {
+      t = ansic_quote (value_cell (var), 0, (int *)0);
+      printf ("%s", t);
+      free (t);
+    }
+  else if (quote && sh_contains_shell_metas (value_cell (var)))
+    {
+      t = sh_single_quote (value_cell (var));
+      printf ("%s", t);
+      free (t);
+    }
+  else
+    printf ("%s", value_cell (var));
+}
+
+/* Print the function cell of VAR, a shell variable.  Do not
+   print the name, nor leading/trailing newline. */
+void
+print_var_function (var)
+     SHELL_VAR *var;
+{
+  if (function_p (var) && var_isset (var))
+    printf ("%s", named_function_string ((char *)NULL, function_cell(var), 1));
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                     Dynamic Variables                           */
+/*                                                                 */
+/* **************************************************************** */
+
+/* DYNAMIC VARIABLES
+
+   These are variables whose values are generated anew each time they are
+   referenced.  These are implemented using a pair of function pointers
+   in the struct variable: assign_func, which is called from bind_variable
+   and, if arrays are compiled into the shell, some of the functions in
+   arrayfunc.c, and dynamic_value, which is called from find_variable.
+
+   assign_func is called from bind_variable_internal, if
+   bind_variable_internal discovers that the variable being assigned to
+   has such a function.  The function is called as
+       SHELL_VAR *temp = (*(entry->assign_func)) (entry, value, ind)
+   and the (SHELL_VAR *)temp is returned as the value of bind_variable.  It
+   is usually ENTRY (self).  IND is an index for an array variable, and
+   unused otherwise.
+
+   dynamic_value is called from find_variable_internal to return a `new'
+   value for the specified dynamic varible.  If this function is NULL,
+   the variable is treated as a `normal' shell variable.  If it is not,
+   however, then this function is called like this:
+       tempvar = (*(var->dynamic_value)) (var);
+
+   Sometimes `tempvar' will replace the value of `var'.  Other times, the
+   shell will simply use the string value.  Pretty object-oriented, huh?
+
+   Be warned, though: if you `unset' a special variable, it loses its
+   special meaning, even if you subsequently set it.
+
+   The special assignment code would probably have been better put in
+   subst.c: do_assignment_internal, in the same style as
+   stupidly_hack_special_variables, but I wanted the changes as
+   localized as possible.  */
+
+#define INIT_DYNAMIC_VAR(var, val, gfunc, afunc) \
+  do \
+    { \
+      v = bind_variable (var, (val)); \
+      v->dynamic_value = gfunc; \
+      v->assign_func = afunc; \
+    } \
+  while (0)
+
+#define INIT_DYNAMIC_ARRAY_VAR(var, gfunc, afunc) \
+  do \
+    { \
+      v = make_new_array_variable (var); \
+      v->dynamic_value = gfunc; \
+      v->assign_func = afunc; \
+    } \
+  while (0)
+
+static SHELL_VAR *
+null_assign (self, value, unused)
+     SHELL_VAR *self;
+     char *value;
+     arrayind_t unused;
+{
+  return (self);
+}
+
+#if defined (ARRAY_VARS)
+static SHELL_VAR *
+null_array_assign (self, value, ind)
+     SHELL_VAR *self;
+     char *value;
+     arrayind_t ind;
+{
+  return (self);
+}
+#endif
+
+/* Degenerate `dynamic_value' function; just returns what's passed without
+   manipulation. */
+static SHELL_VAR *
+get_self (self)
+     SHELL_VAR *self;
+{
+  return (self);
+}
+
+#if defined (ARRAY_VARS)
+/* A generic dynamic array variable initializer.  Intialize array variable
+   NAME with dynamic value function GETFUNC and assignment function SETFUNC. */
+static SHELL_VAR *
+init_dynamic_array_var (name, getfunc, setfunc, attrs)
+     char *name;
+     sh_var_value_func_t *getfunc;
+     sh_var_assign_func_t *setfunc;
+     int attrs;
+{
+  SHELL_VAR *v;
+
+  v = find_variable (name);
+  if (v)
+    return (v);
+  INIT_DYNAMIC_ARRAY_VAR (name, getfunc, setfunc);
+  if (attrs)
+    VSETATTR (v, attrs);
+  return v;
+}
+#endif
+
+
+/* The value of $SECONDS.  This is the number of seconds since shell
+   invocation, or, the number of seconds since the last assignment + the
+   value of the last assignment. */
+static intmax_t seconds_value_assigned;
+
+static SHELL_VAR *
+assign_seconds (self, value, unused)
+     SHELL_VAR *self;
+     char *value;
+     arrayind_t unused;
+{
+  if (legal_number (value, &seconds_value_assigned) == 0)
+    seconds_value_assigned = 0;
+  shell_start_time = NOW;
+  return (self);
+}
+
+static SHELL_VAR *
+get_seconds (var)
+     SHELL_VAR *var;
+{
+  time_t time_since_start;
+  char *p;
+
+  time_since_start = NOW - shell_start_time;
+  p = itos(seconds_value_assigned + time_since_start);
+
+  FREE (value_cell (var));
+
+  VSETATTR (var, att_integer);
+  var_setvalue (var, p);
+  return (var);
+}
+
+static SHELL_VAR *
+init_seconds_var ()
+{
+  SHELL_VAR *v;
+
+  v = find_variable ("SECONDS");
+  if (v)
+    {
+      if (legal_number (value_cell(v), &seconds_value_assigned) == 0)
+       seconds_value_assigned = 0;
+    }
+  INIT_DYNAMIC_VAR ("SECONDS", (v ? value_cell (v) : (char *)NULL), get_seconds, assign_seconds);
+  return v;      
+}
+     
+/* The random number seed.  You can change this by setting RANDOM. */
+static unsigned long rseed = 1;
+static int last_random_value;
+
+/* A linear congruential random number generator based on the example
+   one in the ANSI C standard.  This one isn't very good, but a more
+   complicated one is overkill. */
+
+/* Returns a pseudo-random number between 0 and 32767. */
+static int
+brand ()
+{
+  rseed = rseed * 1103515245 + 12345;
+  return ((unsigned int)((rseed >> 16) & 32767));      /* was % 32768 */
+}
+
+/* Set the random number generator seed to SEED. */
+static void
+sbrand (seed)
+     unsigned long seed;
+{
+  rseed = seed;
+  last_random_value = 0;
+}
+
+static SHELL_VAR *
+assign_random (self, value, unused)
+     SHELL_VAR *self;
+     char *value;
+     arrayind_t unused;
+{
+  sbrand (strtoul (value, (char **)NULL, 10));
+  return (self);
+}
+
+int
+get_random_number ()
+{
+  int rv;
+
+  /* Reset for command and process substitution. */
+  if (subshell_environment)
+    sbrand (rseed + getpid() + NOW);
+
+  do
+    rv = brand ();
+  while (rv == last_random_value);
+  return rv;
+}
+
+static SHELL_VAR *
+get_random (var)
+     SHELL_VAR *var;
+{
+  int rv;
+  char *p;
+
+  rv = get_random_number ();
+  last_random_value = rv;
+  p = itos (rv);
+
+  FREE (value_cell (var));
+
+  VSETATTR (var, att_integer);
+  var_setvalue (var, p);
+  return (var);
+}
+
+static SHELL_VAR *
+assign_lineno (var, value, unused)
+     SHELL_VAR *var;
+     char *value;
+     arrayind_t unused;
+{
+  intmax_t new_value;
+
+  if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0)
+    new_value = 0;
+  line_number = new_value;
+  return var;
+}
+
+/* Function which returns the current line number. */
+static SHELL_VAR *
+get_lineno (var)
+     SHELL_VAR *var;
+{
+  char *p;
+  int ln;
+
+  ln = executing_line_number ();
+  p = itos (ln);
+  FREE (value_cell (var));
+  var_setvalue (var, p);
+  return (var);
+}
+
+static SHELL_VAR *
+assign_subshell (var, value, unused)
+     SHELL_VAR *var;
+     char *value;
+     arrayind_t unused;
+{
+  intmax_t new_value;
+
+  if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0)
+    new_value = 0;
+  subshell_level = new_value;
+  return var;
+}
+
+static SHELL_VAR *
+get_subshell (var)
+     SHELL_VAR *var;
+{
+  char *p;
+
+  p = itos (subshell_level);
+  FREE (value_cell (var));
+  var_setvalue (var, p);
+  return (var);
+}
+
+static SHELL_VAR *
+get_bash_command (var)
+     SHELL_VAR *var;
+{
+  char *p;
+
+  p = savestring (the_printed_command_except_trap);
+  FREE (value_cell (var));
+  var_setvalue (var, p);
+  return (var);
+}
+
+#if defined (HISTORY)
+static SHELL_VAR *
+get_histcmd (var)
+     SHELL_VAR *var;
+{
+  char *p;
+
+  p = itos (history_number ());
+  FREE (value_cell (var));
+  var_setvalue (var, p);
+  return (var);
+}
+#endif
+
+#if defined (READLINE)
+/* When this function returns, VAR->value points to malloced memory. */
+static SHELL_VAR *
+get_comp_wordbreaks (var)
+     SHELL_VAR *var;
+{
+  char *p;
+
+  /* If we don't have anything yet, assign a default value. */
+  if (rl_completer_word_break_characters == 0 && bash_readline_initialized == 0)
+    enable_hostname_completion (perform_hostname_completion);
+
+#if 0
+  FREE (value_cell (var));
+  p = savestring (rl_completer_word_break_characters);
+  
+  var_setvalue (var, p);
+#else
+  var_setvalue (var, rl_completer_word_break_characters);
+#endif
+
+  return (var);
+}
+
+/* When this function returns, rl_completer_word_break_characters points to
+   malloced memory. */
+static SHELL_VAR *
+assign_comp_wordbreaks (self, value, unused)
+     SHELL_VAR *self;
+     char *value;
+     arrayind_t unused;
+{
+  if (rl_completer_word_break_characters &&
+      rl_completer_word_break_characters != rl_basic_word_break_characters)
+    free (rl_completer_word_break_characters);
+
+  rl_completer_word_break_characters = savestring (value);
+  return self;
+}
+#endif /* READLINE */
+
+#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS)
+static SHELL_VAR *
+assign_dirstack (self, value, ind)
+     SHELL_VAR *self;
+     char *value;
+     arrayind_t ind;
+{
+  set_dirstack_element (ind, 1, value);
+  return self;
+}
+
+static SHELL_VAR *
+get_dirstack (self)
+     SHELL_VAR *self;
+{
+  ARRAY *a;
+  WORD_LIST *l;
+
+  l = get_directory_stack ();
+  a = array_from_word_list (l);
+  array_dispose (array_cell (self));
+  dispose_words (l);
+  var_setarray (self, a);
+  return self;
+}
+#endif /* PUSHD AND POPD && ARRAY_VARS */
+
+#if defined (ARRAY_VARS)
+/* We don't want to initialize the group set with a call to getgroups()
+   unless we're asked to, but we only want to do it once. */
+static SHELL_VAR *
+get_groupset (self)
+     SHELL_VAR *self;
+{
+  register int i;
+  int ng;
+  ARRAY *a;
+  static char **group_set = (char **)NULL;
+
+  if (group_set == 0)
+    {
+      group_set = get_group_list (&ng);
+      a = array_cell (self);
+      for (i = 0; i < ng; i++)
+       array_insert (a, i, group_set[i]);
+    }
+  return (self);
+}
+#endif /* ARRAY_VARS */
+
+/* If ARRAY_VARS is not defined, this just returns the name of any
+   currently-executing function.  If we have arrays, it's a call stack. */
+static SHELL_VAR *
+get_funcname (self)
+     SHELL_VAR *self;
+{
+#if ! defined (ARRAY_VARS)
+  char *t;
+  if (variable_context && this_shell_function)
+    {
+      FREE (value_cell (self));
+      t = savestring (this_shell_function->name);
+      var_setvalue (self, t);
+    }
+#endif
+  return (self);
+}
+
+void
+make_funcname_visible (on_or_off)
+     int on_or_off;
+{
+  SHELL_VAR *v;
+
+  v = find_variable ("FUNCNAME");
+  if (v == 0 || v->dynamic_value == 0)
+    return;
+
+  if (on_or_off)
+    VUNSETATTR (v, att_invisible);
+  else
+    VSETATTR (v, att_invisible);
+}
+
+static SHELL_VAR *
+init_funcname_var ()
+{
+  SHELL_VAR *v;
+
+  v = find_variable ("FUNCNAME");
+  if (v)
+    return v;
+#if defined (ARRAY_VARS)
+  INIT_DYNAMIC_ARRAY_VAR ("FUNCNAME", get_funcname, null_array_assign);
+#else
+  INIT_DYNAMIC_VAR ("FUNCNAME", (char *)NULL, get_funcname, null_assign);
+#endif
+  VSETATTR (v, att_invisible|att_noassign);
+  return v;
+}
+
+static void
+initialize_dynamic_variables ()
+{
+  SHELL_VAR *v;
+
+  v = init_seconds_var ();
+
+  INIT_DYNAMIC_VAR ("BASH_COMMAND", (char *)NULL, get_bash_command, (sh_var_assign_func_t *)NULL);
+  INIT_DYNAMIC_VAR ("BASH_SUBSHELL", (char *)NULL, get_subshell, assign_subshell);
+
+  INIT_DYNAMIC_VAR ("RANDOM", (char *)NULL, get_random, assign_random);
+  INIT_DYNAMIC_VAR ("LINENO", (char *)NULL, get_lineno, assign_lineno);
+
+#if defined (HISTORY)
+  INIT_DYNAMIC_VAR ("HISTCMD", (char *)NULL, get_histcmd, (sh_var_assign_func_t *)NULL);
+#endif
+
+#if defined (READLINE)
+  INIT_DYNAMIC_VAR ("COMP_WORDBREAKS", (char *)NULL, get_comp_wordbreaks, assign_comp_wordbreaks);
+#endif
+
+#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS)
+  v = init_dynamic_array_var ("DIRSTACK", get_dirstack, assign_dirstack, 0);
+#endif /* PUSHD_AND_POPD && ARRAY_VARS */
+
+#if defined (ARRAY_VARS)
+  v = init_dynamic_array_var ("GROUPS", get_groupset, null_array_assign, att_noassign);
+
+#  if defined (DEBUGGER)
+  v = init_dynamic_array_var ("BASH_ARGC", get_self, null_array_assign, (att_invisible|att_noassign));
+  v = init_dynamic_array_var ("BASH_ARGV", get_self, null_array_assign, (att_invisible|att_noassign));
+#  endif /* DEBUGGER */
+  v = init_dynamic_array_var ("BASH_SOURCE", get_self, null_array_assign, (att_invisible|att_noassign));
+  v = init_dynamic_array_var ("BASH_LINENO", get_self, null_array_assign, (att_invisible|att_noassign));
+#endif
+
+  v = init_funcname_var ();
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*             Retrieving variables and values                     */
+/*                                                                 */
+/* **************************************************************** */
+
+/* How to get a pointer to the shell variable or function named NAME.
+   HASHED_VARS is a pointer to the hash table containing the list
+   of interest (either variables or functions). */
+
+static SHELL_VAR *
+hash_lookup (name, hashed_vars)
+     const char *name;
+     HASH_TABLE *hashed_vars;
+{
+  BUCKET_CONTENTS *bucket;
+
+  bucket = hash_search (name, hashed_vars, 0);
+  return (bucket ? (SHELL_VAR *)bucket->data : (SHELL_VAR *)NULL);
+}
+
+SHELL_VAR *
+var_lookup (name, vcontext)
+     const char *name;
+     VAR_CONTEXT *vcontext;
+{
+  VAR_CONTEXT *vc;
+  SHELL_VAR *v;
+
+  v = (SHELL_VAR *)NULL;
+  for (vc = vcontext; vc; vc = vc->down)
+    if (v = hash_lookup (name, vc->table))
+      break;
+
+  return v;
+}
+
+/* Look up the variable entry named NAME.  If SEARCH_TEMPENV is non-zero,
+   then also search the temporarily built list of exported variables.
+   The lookup order is:
+       temporary_env
+        shell_variables list
+*/
+
+SHELL_VAR *
+find_variable_internal (name, force_tempenv)
+     const char *name;
+     int force_tempenv;
+{
+  SHELL_VAR *var;
+  int search_tempenv;
+
+  var = (SHELL_VAR *)NULL;
+
+  /* If explicitly requested, first look in the temporary environment for
+     the variable.  This allows constructs such as "foo=x eval 'echo $foo'"
+     to get the `exported' value of $foo.  This happens if we are executing
+     a function or builtin, or if we are looking up a variable in a
+     "subshell environment". */
+  search_tempenv = force_tempenv || (expanding_redir == 0 && subshell_environment);
+
+  if (search_tempenv && temporary_env)         
+    var = hash_lookup (name, temporary_env);
+
+  if (var == 0)
+    var = var_lookup (name, shell_variables);
+
+  if (var == 0)
+    return ((SHELL_VAR *)NULL);
+
+  return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var);
+}
+
+/* Look up the variable entry named NAME.  Returns the entry or NULL. */
+SHELL_VAR *
+find_variable (name)
+     const char *name;
+{
+  return (find_variable_internal (name, (expanding_redir == 0 && this_shell_builtin != 0)));
+}
+
+/* Look up the function entry whose name matches STRING.
+   Returns the entry or NULL. */
+SHELL_VAR *
+find_function (name)
+     const char *name;
+{
+  return (hash_lookup (name, shell_functions));
+}
+
+/* Find the function definition for the shell function named NAME.  Returns
+   the entry or NULL. */
+FUNCTION_DEF *
+find_function_def (name)
+     const char *name;
+{
+  return ((FUNCTION_DEF *)hash_lookup (name, shell_function_defs));
+}
+
+/* Return the value of VAR.  VAR is assumed to have been the result of a
+   lookup without any subscript, if arrays are compiled into the shell. */
+char *
+get_variable_value (var)
+     SHELL_VAR *var;
+{
+  if (var == 0)
+    return ((char *)NULL);
+#if defined (ARRAY_VARS)
+  else if (array_p (var))
+    return (array_reference (array_cell (var), 0));
+#endif
+  else
+    return (value_cell (var));
+}
+
+/* Return the string value of a variable.  Return NULL if the variable
+   doesn't exist.  Don't cons a new string.  This is a potential memory
+   leak if the variable is found in the temporary environment.  Since
+   functions and variables have separate name spaces, returns NULL if
+   var_name is a shell function only. */
+char *
+get_string_value (var_name)
+     const char *var_name;
+{
+  SHELL_VAR *var;
+
+  var = find_variable (var_name);
+  return ((var) ? get_variable_value (var) : (char *)NULL);
+}
+
+/* This is present for use by the tilde and readline libraries. */
+char *
+sh_get_env_value (v)
+     const char *v;
+{
+  return get_string_value (v);
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*               Creating and setting variables                    */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Set NAME to VALUE if NAME has no value. */
+SHELL_VAR *
+set_if_not (name, value)
+     char *name, *value;
+{
+  SHELL_VAR *v;
+
+  v = find_variable (name);
+  if (v == 0)
+    v = bind_variable_internal (name, value, global_variables->table, HASH_NOSRCH);
+  return (v);
+}
+
+/* Create a local variable referenced by NAME. */
+SHELL_VAR *
+make_local_variable (name)
+     const char *name;
+{
+  SHELL_VAR *new_var, *old_var;
+  VAR_CONTEXT *vc;
+  int was_tmpvar;
+  char *tmp_value;
+
+  /* local foo; local foo;  is a no-op. */
+  old_var = find_variable (name);
+  if (old_var && local_p (old_var) && old_var->context == variable_context)
+    return (old_var);
+
+  was_tmpvar = old_var && tempvar_p (old_var);
+  if (was_tmpvar)
+    tmp_value = value_cell (old_var);
+
+  for (vc = shell_variables; vc; vc = vc->down)
+    if (vc_isfuncenv (vc) && vc->scope == variable_context)
+      break;
+
+  if (vc == 0)
+    {
+      internal_error (_("make_local_variable: no function context at current scope"));
+      return ((SHELL_VAR *)NULL);
+    }
+  else if (vc->table == 0)
+    vc->table = hash_create (TEMPENV_HASH_BUCKETS);
+
+  /* Since this is called only from the local/declare/typeset code, we can
+     call builtin_error here without worry (of course, it will also work
+     for anything that sets this_command_name).  Variables with the `noassign'
+     attribute may not be made local.  The test against old_var's context
+     level is to disallow local copies of readonly global variables (since I
+     believe that this could be a security hole).  Readonly copies of calling
+     function local variables are OK. */
+  if (old_var && (noassign_p (old_var) ||
+                (readonly_p (old_var) && old_var->context == 0)))
+    {
+      if (readonly_p (old_var))
+       sh_readonly (name);
+      return ((SHELL_VAR *)NULL);
+    }
+
+  if (old_var == 0)
+    new_var = bind_variable_internal (name, "", vc->table, HASH_NOSRCH);
+  else
+    {
+      new_var = make_new_variable (name, vc->table);
+
+      /* If we found this variable in one of the temporary environments,
+        inherit its value.  Watch to see if this causes problems with
+        things like `x=4 local x'. */
+      if (was_tmpvar)
+        var_setvalue (new_var, savestring (tmp_value));
+
+      new_var->attributes = exported_p (old_var) ? att_exported : 0;
+    }
+
+  vc->flags |= VC_HASLOCAL;
+
+  new_var->context = variable_context;
+  VSETATTR (new_var, att_local);
+
+  if (ifsname (name))
+    setifs (new_var);
+
+  return (new_var);
+}
+
+#if defined (ARRAY_VARS)
+SHELL_VAR *
+make_local_array_variable (name)
+     char *name;
+{
+  SHELL_VAR *var;
+  ARRAY *array;
+
+  var = make_local_variable (name);
+  if (var == 0)
+    return var;
+  array = array_create ();
+
+  FREE (value_cell(var));
+  var_setarray (var, array);
+  VSETATTR (var, att_array);
+  return var;
+}
+#endif /* ARRAY_VARS */
+
+/* Create a new shell variable with name NAME. */
+static SHELL_VAR *
+new_shell_variable (name)
+     const char *name;
+{
+  SHELL_VAR *entry;
+
+  entry = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
+
+  entry->name = savestring (name);
+  var_setvalue (entry, (char *)NULL);
+  CLEAR_EXPORTSTR (entry);
+
+  entry->dynamic_value = (sh_var_value_func_t *)NULL;
+  entry->assign_func = (sh_var_assign_func_t *)NULL;
+
+  entry->attributes = 0;
+
+  /* Always assume variables are to be made at toplevel!
+     make_local_variable has the responsibilty of changing the
+     variable context. */
+  entry->context = 0;
+
+  return (entry);
+}
+
+/* Create a new shell variable with name NAME and add it to the hash table
+   TABLE. */
+static SHELL_VAR *
+make_new_variable (name, table)
+     const char *name;
+     HASH_TABLE *table;
+{
+  SHELL_VAR *entry;
+  BUCKET_CONTENTS *elt;
+
+  entry = new_shell_variable (name);
+
+  /* Make sure we have a shell_variables hash table to add to. */
+  if (shell_variables == 0)
+    {
+      shell_variables = global_variables = new_var_context ((char *)NULL, 0);
+      shell_variables->scope = 0;
+      shell_variables->table = hash_create (0);
+    }
+
+  elt = hash_insert (savestring (name), table, HASH_NOSRCH);
+  elt->data = (PTR_T)entry;
+
+  return entry;
+}
+
+#if defined (ARRAY_VARS)
+SHELL_VAR *
+make_new_array_variable (name)
+     char *name;
+{
+  SHELL_VAR *entry;
+  ARRAY *array;
+
+  entry = make_new_variable (name, global_variables->table);
+  array = array_create ();
+  var_setarray (entry, array);
+  VSETATTR (entry, att_array);
+  return entry;
+}
+#endif
+
+char *
+make_variable_value (var, value)
+     SHELL_VAR *var;
+     char *value;
+{
+  char *retval;
+  intmax_t lval;
+  int expok;
+
+  /* If this variable has had its type set to integer (via `declare -i'),
+     then do expression evaluation on it and store the result.  The
+     functions in expr.c (evalexp()) and bind_int_variable() are responsible
+     for turning off the integer flag if they don't want further
+     evaluation done. */
+  if (integer_p (var))
+    {
+      lval = evalexp (value, &expok);
+      if (expok == 0)
+       jump_to_top_level (DISCARD);
+      retval = itos (lval);
+    }
+  else if (value)
+    {
+      if (*value)
+       retval = savestring (value);
+      else
+       {
+         retval = (char *)xmalloc (1);
+         retval[0] = '\0';
+       }
+    }
+  else
+    retval = (char *)NULL;
+
+  return retval;
+}
+
+/* Bind a variable NAME to VALUE in the HASH_TABLE TABLE, which may be the
+   temporary environment (but usually is not). */
+static SHELL_VAR *
+bind_variable_internal (name, value, table, hflags)
+     const char *name;
+     char *value;
+     HASH_TABLE *table;
+     int hflags;
+{
+  char *newval;
+  SHELL_VAR *entry;
+
+  entry = (hflags & HASH_NOSRCH) ? (SHELL_VAR *)NULL : hash_lookup (name, table);
+
+  if (entry == 0)
+    {
+      entry = make_new_variable (name, table);
+      var_setvalue (entry, make_variable_value (entry, value));
+    }
+  else if (entry->assign_func) /* array vars have assign functions now */
+    {
+      INVALIDATE_EXPORTSTR (entry);
+      return ((*(entry->assign_func)) (entry, value, -1));
+    }
+  else
+    {
+      if (readonly_p (entry) || noassign_p (entry))
+       {
+         if (readonly_p (entry))
+           err_readonly (name);
+         return (entry);
+       }
+
+      /* Variables which are bound are visible. */
+      VUNSETATTR (entry, att_invisible);
+
+      newval = make_variable_value (entry, value);
+
+      /* Invalidate any cached export string */
+      INVALIDATE_EXPORTSTR (entry);
+
+#if defined (ARRAY_VARS)
+      /* XXX -- this bears looking at again -- XXX */
+      /* If an existing array variable x is being assigned to with x=b or
+        `read x' or something of that nature, silently convert it to
+        x[0]=b or `read x[0]'. */
+      if (array_p (entry))
+       {
+         array_insert (array_cell (entry), 0, newval);
+         free (newval);
+       }
+      else
+#endif
+       {
+         FREE (value_cell (entry));
+         var_setvalue (entry, newval);
+       }
+    }
+
+  if (mark_modified_vars)
+    VSETATTR (entry, att_exported);
+
+  if (exported_p (entry))
+    array_needs_making = 1;
+
+  return (entry);
+}
+       
+/* Bind a variable NAME to VALUE.  This conses up the name
+   and value strings.  If we have a temporary environment, we bind there
+   first, then we bind into shell_variables. */
+
+SHELL_VAR *
+bind_variable (name, value)
+     const char *name;
+     char *value;
+{
+  SHELL_VAR *v;
+  VAR_CONTEXT *vc;
+
+  if (shell_variables == 0)
+    {
+      shell_variables = global_variables = new_var_context ((char *)NULL, 0);
+      shell_variables->scope = 0;
+      shell_variables->table = hash_create (0);
+    }
+
+  /* If we have a temporary environment, look there first for the variable,
+     and, if found, modify the value there before modifying it in the
+     shell_variables table.  This allows sourced scripts to modify values
+     given to them in a temporary environment while modifying the variable
+     value that the caller sees. */
+  if (temporary_env)
+    bind_tempenv_variable (name, value);
+
+  /* XXX -- handle local variables here. */
+  for (vc = shell_variables; vc; vc = vc->down)
+    {
+      if (vc_isfuncenv (vc) || vc_isbltnenv (vc))
+        {
+          v = hash_lookup (name, vc->table);
+          if (v)
+           return (bind_variable_internal (name, value, vc->table, 0));
+        }
+    }
+  return (bind_variable_internal (name, value, global_variables->table, 0));
+}
+
+/* Make VAR, a simple shell variable, have value VALUE.  Once assigned a
+   value, variables are no longer invisible.  This is a duplicate of part
+   of the internals of bind_variable.  If the variable is exported, or
+   all modified variables should be exported, mark the variable for export
+   and note that the export environment needs to be recreated. */
+SHELL_VAR *
+bind_variable_value (var, value)
+     SHELL_VAR *var;
+     char *value;
+{
+  char *t;
+
+  VUNSETATTR (var, att_invisible);
+
+  t = make_variable_value (var, value);
+  FREE (value_cell (var));
+  var_setvalue (var, t);
+
+  INVALIDATE_EXPORTSTR (var);
+
+  if (mark_modified_vars)
+    VSETATTR (var, att_exported);
+
+  if (exported_p (var))
+    array_needs_making = 1;
+
+  return (var);
+}
+
+/* Bind/create a shell variable with the name LHS to the RHS.
+   This creates or modifies a variable such that it is an integer.
+
+   This used to be in expr.c, but it is here so that all of the
+   variable binding stuff is localized.  Since we don't want any
+   recursive evaluation from bind_variable() (possible without this code,
+   since bind_variable() calls the evaluator for variables with the integer
+   attribute set), we temporarily turn off the integer attribute for each
+   variable we set here, then turn it back on after binding as necessary. */
+
+SHELL_VAR *
+bind_int_variable (lhs, rhs)
+     char *lhs, *rhs;
+{
+  register SHELL_VAR *v;
+  char *t;
+  int isint, isarr;
+
+  isint = isarr = 0;
+#if defined (ARRAY_VARS)
+#  if 0
+  if (t = xstrchr (lhs, '['))  /*]*/
+#  else
+  if (valid_array_reference (lhs))
+#  endif
+    {
+      isarr = 1;
+      v = array_variable_part (lhs, (char **)0, (int *)0);
+    }
+  else
+#endif
+    v = find_variable (lhs);
+
+  if (v)
+    {
+      isint = integer_p (v);
+      VUNSETATTR (v, att_integer);
+    }
+
+#if defined (ARRAY_VARS)
+  if (isarr)
+    v = assign_array_element (lhs, rhs);
+  else
+#endif
+    v = bind_variable (lhs, rhs);
+
+  if (isint)
+    VSETATTR (v, att_integer);
+
+  return (v);
+}
+
+SHELL_VAR *
+bind_var_to_int (var, val)
+     char *var;
+     intmax_t val;
+{
+  char ibuf[INT_STRLEN_BOUND (intmax_t) + 1], *p;
+
+  p = fmtulong (val, 10, ibuf, sizeof (ibuf), 0);
+  return (bind_int_variable (var, p));
+}
+
+/* Do a function binding to a variable.  You pass the name and
+   the command to bind to.  This conses the name and command. */
+SHELL_VAR *
+bind_function (name, value)
+     const char *name;
+     COMMAND *value;
+{
+  SHELL_VAR *entry;
+
+  entry = find_function (name);
+  if (entry == 0)
+    {
+      BUCKET_CONTENTS *elt;
+
+      elt = hash_insert (savestring (name), shell_functions, HASH_NOSRCH);
+      entry = new_shell_variable (name);
+      elt->data = (PTR_T)entry;
+    }
+  else
+    INVALIDATE_EXPORTSTR (entry);
+
+  if (var_isset (entry))
+    dispose_command (function_cell (entry));
+
+  if (value)
+    var_setfunc (entry, copy_command (value));
+  else
+    var_setfunc (entry, 0);
+
+  VSETATTR (entry, att_function);
+
+  if (mark_modified_vars)
+    VSETATTR (entry, att_exported);
+
+  VUNSETATTR (entry, att_invisible);           /* Just to be sure */
+
+  if (exported_p (entry))
+    array_needs_making = 1;
+
+#if defined (PROGRAMMABLE_COMPLETION)
+  set_itemlist_dirty (&it_functions);
+#endif
+
+  return (entry);
+}
+
+/* Bind a function definition, which includes source file and line number
+   information in addition to the command, into the FUNCTION_DEF hash table.*/
+void
+bind_function_def (name, value)
+     const char *name;
+     FUNCTION_DEF *value;
+{
+  FUNCTION_DEF *entry;
+  BUCKET_CONTENTS *elt;
+  COMMAND *cmd;
+
+  entry = find_function_def (name);
+  if (entry)
+    {
+      dispose_function_def_contents (entry);
+      entry = copy_function_def_contents (value, entry);
+    }
+  else
+    {
+      cmd = value->command;
+      value->command = 0;
+      entry = copy_function_def (value);
+      value->command = cmd;
+
+      elt = hash_insert (savestring (name), shell_function_defs, HASH_NOSRCH);
+      elt->data = (PTR_T *)entry;
+    }
+}
+
+/* Add STRING, which is of the form foo=bar, to the temporary environment
+   HASH_TABLE (temporary_env).  The functions in execute_cmd.c are
+   responsible for moving the main temporary env to one of the other
+   temporary environments.  The expansion code in subst.c calls this. */
+int
+assign_in_env (string)
+     const char *string;
+{
+  int offset;
+  char *name, *temp, *value;
+  SHELL_VAR *var;
+
+  offset = assignment (string, 0);
+  name = savestring (string);
+  value = (char *)NULL;
+
+  if (name[offset] == '=')
+    {
+      name[offset] = 0;
+
+      var = find_variable (name);
+      if (var && (readonly_p (var) || noassign_p (var)))
+       {
+         if (readonly_p (var))
+           err_readonly (name);
+         free (name);
+         return (0);
+       }
+
+      temp = name + offset + 1;
+      temp = (xstrchr (temp, '~') != 0) ? bash_tilde_expand (temp, 1) : savestring (temp);
+
+      value = expand_string_unsplit_to_string (temp, 0);
+      free (temp);
+    }
+
+  if (temporary_env == 0)
+    temporary_env = hash_create (TEMPENV_HASH_BUCKETS);
+
+  var = hash_lookup (name, temporary_env);
+  if (var == 0)
+    var = make_new_variable (name, temporary_env);
+  else
+    FREE (value_cell (var));
+
+  if (value == 0)
+    {
+      value = (char *)xmalloc (1);     /* like do_assignment_internal */
+      value[0] = '\0';
+    }
+
+  var_setvalue (var, value);
+  var->attributes |= (att_exported|att_tempvar);
+  var->context = variable_context;     /* XXX */
+
+  INVALIDATE_EXPORTSTR (var);
+  var->exportstr = mk_env_string (name, value);
+
+  array_needs_making = 1;
+
+  if (ifsname (name))
+    setifs (var);
+
+  if (echo_command_at_execute)
+    {
+      /* The Korn shell prints the `+ ' in front of assignment statements,
+        so we do too. */
+#if 0
+      fprintf (stderr, "%s%s=%s\n", indirection_level_string (), name, value);
+      fflush (stderr);
+#else
+      xtrace_print_assignment (name, value, 0, 1);
+#endif
+    }
+
+  free (name);
+  return 1;
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                     Copying variables                           */
+/*                                                                 */
+/* **************************************************************** */
+
+#ifdef INCLUDE_UNUSED
+/* Copy VAR to a new data structure and return that structure. */
+SHELL_VAR *
+copy_variable (var)
+     SHELL_VAR *var;
+{
+  SHELL_VAR *copy = (SHELL_VAR *)NULL;
+
+  if (var)
+    {
+      copy = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
+
+      copy->attributes = var->attributes;
+      copy->name = savestring (var->name);
+
+      if (function_p (var))
+       var_setfunc (copy, copy_command (function_cell (var)));
+#if defined (ARRAY_VARS)
+      else if (array_p (var))
+       var_setarray (copy, dup_array (array_cell (var)));
+#endif
+      else if (value_cell (var))
+       var_setvalue (copy, savestring (value_cell (var)));
+      else
+       var_setvalue (copy, (char *)NULL);
+
+      copy->dynamic_value = var->dynamic_value;
+      copy->assign_func = var->assign_func;
+
+      copy->exportstr = COPY_EXPORTSTR (var);
+
+      copy->context = var->context;
+    }
+  return (copy);
+}
+#endif
+
+/* **************************************************************** */
+/*                                                                 */
+/*               Deleting and unsetting variables                  */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Dispose of the information attached to VAR. */
+void
+dispose_variable (var)
+     SHELL_VAR *var;
+{
+  if (var == 0)
+    return;
+
+  if (function_p (var))
+    dispose_command (function_cell (var));
+#if defined (ARRAY_VARS)
+  else if (array_p (var))
+    array_dispose (array_cell (var));
+#endif
+  else
+    FREE (value_cell (var));
+
+  FREE_EXPORTSTR (var);
+
+  free (var->name);
+
+  if (exported_p (var))
+    array_needs_making = 1;
+
+  free (var);
+}
+
+/* Unset the shell variable referenced by NAME. */
+int
+unbind_variable (name)
+     const char *name;
+{
+  return makunbound (name, shell_variables);
+}
+
+/* Unset the shell function named NAME. */
+int
+unbind_func (name)
+     const char *name;
+{
+  BUCKET_CONTENTS *elt;
+  SHELL_VAR *func;
+
+  elt = hash_remove (name, shell_functions, 0);
+
+  if (elt == 0)
+    return -1;
+
+#if defined (PROGRAMMABLE_COMPLETION)
+  set_itemlist_dirty (&it_functions);
+#endif
+
+  func = (SHELL_VAR *)elt->data;
+  if (func)
+    {
+      if (exported_p (func))
+       array_needs_making++;
+      dispose_variable (func);
+    }
+
+  free (elt->key);
+  free (elt);
+
+  return 0;  
+}
+
+int
+unbind_function_def (name)
+     const char *name;
+{
+  BUCKET_CONTENTS *elt;
+  FUNCTION_DEF *funcdef;
+
+  elt = hash_remove (name, shell_function_defs, 0);
+
+  if (elt == 0)
+    return -1;
+
+  funcdef = (FUNCTION_DEF *)elt->data;
+  if (funcdef)
+    dispose_function_def (funcdef);
+
+  free (elt->key);
+  free (elt);
+
+  return 0;  
+}
+
+/* Make the variable associated with NAME go away.  HASH_LIST is the
+   hash table from which this variable should be deleted (either
+   shell_variables or shell_functions).
+   Returns non-zero if the variable couldn't be found. */
+int
+makunbound (name, vc)
+     const char *name;
+     VAR_CONTEXT *vc;
+{
+  BUCKET_CONTENTS *elt, *new_elt;
+  SHELL_VAR *old_var;
+  VAR_CONTEXT *v;
+  char *t;
+
+  for (elt = (BUCKET_CONTENTS *)NULL, v = vc; v; v = v->down)
+    if (elt = hash_remove (name, v->table, 0))
+      break;
+
+  if (elt == 0)
+    return (-1);
+
+  old_var = (SHELL_VAR *)elt->data;
+
+  if (old_var && exported_p (old_var))
+    array_needs_making++;
+
+  /* If we're unsetting a local variable and we're still executing inside
+     the function, just mark the variable as invisible.  The function
+     eventually called by pop_var_context() will clean it up later.  This
+     must be done so that if the variable is subsequently assigned a new
+     value inside the function, the `local' attribute is still present.
+     We also need to add it back into the correct hash table. */
+  if (old_var && local_p (old_var) && variable_context == old_var->context)
+    {
+      /* Reset the attributes.  Preserve the export attribute if the variable
+         came from a temporary environment.  Make sure it stays local, and
+         make it invisible. */ 
+      old_var->attributes = (exported_p (old_var) && tempvar_p (old_var)) ? att_exported : 0;
+      VSETATTR (old_var, att_local);
+      VSETATTR (old_var, att_invisible);
+      FREE (value_cell (old_var));
+      var_setvalue (old_var, (char *)NULL);
+      INVALIDATE_EXPORTSTR (old_var);
+
+      new_elt = hash_insert (savestring (old_var->name), v->table, 0);
+      new_elt->data = (PTR_T)old_var;
+      stupidly_hack_special_variables (old_var->name);
+
+      free (elt->key);
+      free (elt);
+      return (0);
+    }
+
+  /* Have to save a copy of name here, because it might refer to
+     old_var->name.  If so, stupidly_hack_special_variables will
+     reference freed memory. */
+  t = savestring (name);
+
+  free (elt->key);
+  free (elt);
+
+  dispose_variable (old_var);
+  stupidly_hack_special_variables (t);
+  free (t);
+
+  return (0);
+}
+
+/* Get rid of all of the variables in the current context. */
+void
+kill_all_local_variables ()
+{
+  VAR_CONTEXT *vc;
+
+  for (vc = shell_variables; vc; vc = vc->down)
+    if (vc_isfuncenv (vc) && vc->scope == variable_context)
+      break;
+  if (vc == 0)
+    return;            /* XXX */
+
+  if (vc->table && vc_haslocals (vc))
+    {
+      delete_all_variables (vc->table);
+      hash_dispose (vc->table);
+    }
+  vc->table = (HASH_TABLE *)NULL;
+}
+
+static void
+free_variable_hash_data (data)
+     PTR_T data;
+{
+  SHELL_VAR *var;
+
+  var = (SHELL_VAR *)data;
+  dispose_variable (var);
+}
+
+/* Delete the entire contents of the hash table. */
+void
+delete_all_variables (hashed_vars)
+     HASH_TABLE *hashed_vars;
+{
+  hash_flush (hashed_vars, free_variable_hash_data);
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                  Setting variable attributes                    */
+/*                                                                 */
+/* **************************************************************** */
+
+#define FIND_OR_MAKE_VARIABLE(name, entry) \
+  do \
+    { \
+      entry = find_variable (name); \
+      if (!entry) \
+       { \
+         entry = bind_variable (name, ""); \
+         if (!no_invisible_vars) entry->attributes |= att_invisible; \
+       } \
+    } \
+  while (0)
+
+/* Make the variable associated with NAME be readonly.
+   If NAME does not exist yet, create it. */
+void
+set_var_read_only (name)
+     char *name;
+{
+  SHELL_VAR *entry;
+
+  FIND_OR_MAKE_VARIABLE (name, entry);
+  VSETATTR (entry, att_readonly);
+}
+
+#ifdef INCLUDE_UNUSED
+/* Make the function associated with NAME be readonly.
+   If NAME does not exist, we just punt, like auto_export code below. */
+void
+set_func_read_only (name)
+     const char *name;
+{
+  SHELL_VAR *entry;
+
+  entry = find_function (name);
+  if (entry)
+    VSETATTR (entry, att_readonly);
+}
+
+/* Make the variable associated with NAME be auto-exported.
+   If NAME does not exist yet, create it. */
+void
+set_var_auto_export (name)
+     char *name;
+{
+  SHELL_VAR *entry;
+
+  FIND_OR_MAKE_VARIABLE (name, entry);
+  set_auto_export (entry);
+}
+
+/* Make the function associated with NAME be auto-exported. */
+void
+set_func_auto_export (name)
+     const char *name;
+{
+  SHELL_VAR *entry;
+
+  entry = find_function (name);
+  if (entry)
+    set_auto_export (entry);
+}
+#endif
+
+/* **************************************************************** */
+/*                                                                 */
+/*                  Creating lists of variables                    */
+/*                                                                 */
+/* **************************************************************** */
+
+static VARLIST *
+vlist_alloc (nentries)
+     int nentries;
+{
+  VARLIST  *vlist;
+
+  vlist = (VARLIST *)xmalloc (sizeof (VARLIST));
+  vlist->list = (SHELL_VAR **)xmalloc ((nentries + 1) * sizeof (SHELL_VAR *));
+  vlist->list_size = nentries;
+  vlist->list_len = 0;
+  vlist->list[0] = (SHELL_VAR *)NULL;
+
+  return vlist;
+}
+
+static VARLIST *
+vlist_realloc (vlist, n)
+     VARLIST *vlist;
+     int n;
+{
+  if (vlist == 0)
+    return (vlist = vlist_alloc (n));
+  if (n > vlist->list_size)
+    {
+      vlist->list_size = n;
+      vlist->list = (SHELL_VAR **)xrealloc (vlist->list, (vlist->list_size + 1) * sizeof (SHELL_VAR *));
+    }
+  return vlist;
+}
+
+static void
+vlist_add (vlist, var, flags)
+     VARLIST *vlist;
+     SHELL_VAR *var;
+     int flags;
+{
+  register int i;
+
+  for (i = 0; i < vlist->list_len; i++)
+    if (STREQ (var->name, vlist->list[i]->name))
+      break;
+  if (i < vlist->list_len)
+    return;
+
+  if (i >= vlist->list_size)
+    vlist = vlist_realloc (vlist, vlist->list_size + 16);
+
+  vlist->list[vlist->list_len++] = var;
+  vlist->list[vlist->list_len] = (SHELL_VAR *)NULL;
+}
+
+/* Map FUNCTION over the variables in VAR_HASH_TABLE.  Return an array of the
+   variables for which FUNCTION returns a non-zero value.  A NULL value
+   for FUNCTION means to use all variables. */
+SHELL_VAR **
+map_over (function, vc)
+     sh_var_map_func_t *function;
+     VAR_CONTEXT *vc;
+{
+  VAR_CONTEXT *v;
+  VARLIST *vlist;
+  SHELL_VAR **ret;
+  int nentries;
+
+  for (nentries = 0, v = vc; v; v = v->down)
+    nentries += HASH_ENTRIES (v->table);
+
+  if (nentries == 0)
+    return (SHELL_VAR **)NULL;
+
+  vlist = vlist_alloc (nentries);
+
+  for (v = vc; v; v = v->down)
+    flatten (v->table, function, vlist, 0);
+
+  ret = vlist->list;
+  free (vlist);
+  return ret;
+}
+
+SHELL_VAR **
+map_over_funcs (function)
+     sh_var_map_func_t *function;
+{
+  VARLIST *vlist;
+  SHELL_VAR **ret;
+
+  if (shell_functions == 0 || HASH_ENTRIES (shell_functions) == 0)
+    return ((SHELL_VAR **)NULL);
+
+  vlist = vlist_alloc (HASH_ENTRIES (shell_functions));
+
+  flatten (shell_functions, function, vlist, 0);
+
+  ret = vlist->list;
+  free (vlist);
+  return ret;
+}
+
+/* Flatten VAR_HASH_TABLE, applying FUNC to each member and adding those
+   elements for which FUNC succeeds to VLIST->list.  FLAGS is reserved
+   for future use.  Only unique names are added to VLIST.  If FUNC is
+   NULL, each variable in VAR_HASH_TABLE is added to VLIST.  If VLIST is
+   NULL, FUNC is applied to each SHELL_VAR in VAR_HASH_TABLE.  If VLIST
+   and FUNC are both NULL, nothing happens. */
+static void
+flatten (var_hash_table, func, vlist, flags)
+     HASH_TABLE *var_hash_table;
+     sh_var_map_func_t *func;
+     VARLIST *vlist;
+     int flags;
+{
+  register int i;
+  register BUCKET_CONTENTS *tlist;
+  int r;
+  SHELL_VAR *var;
+
+  if (var_hash_table == 0 || (HASH_ENTRIES (var_hash_table) == 0) || (vlist == 0 && func == 0))
+    return;
+
+  for (i = 0; i < var_hash_table->nbuckets; i++)
+    {
+      for (tlist = hash_items (i, var_hash_table); tlist; tlist = tlist->next)
+       {
+         var = (SHELL_VAR *)tlist->data;
+
+         r = func ? (*func) (var) : 1;
+         if (r && vlist)
+           vlist_add (vlist, var, flags);
+       }
+    }
+}
+
+void
+sort_variables (array)
+     SHELL_VAR **array;
+{
+  qsort (array, strvec_len ((char **)array), sizeof (SHELL_VAR *), (QSFUNC *)qsort_var_comp);
+}
+
+static int
+qsort_var_comp (var1, var2)
+     SHELL_VAR **var1, **var2;
+{
+  int result;
+
+  if ((result = (*var1)->name[0] - (*var2)->name[0]) == 0)
+    result = strcmp ((*var1)->name, (*var2)->name);
+
+  return (result);
+}
+
+/* Apply FUNC to each variable in SHELL_VARIABLES, adding each one for
+   which FUNC succeeds to an array of SHELL_VAR *s.  Returns the array. */
+static SHELL_VAR **
+vapply (func)
+     sh_var_map_func_t *func;
+{
+  SHELL_VAR **list;
+
+  list = map_over (func, shell_variables);
+  if (list /* && posixly_correct */)
+    sort_variables (list);
+  return (list);
+}
+
+/* Apply FUNC to each variable in SHELL_FUNCTIONS, adding each one for
+   which FUNC succeeds to an array of SHELL_VAR *s.  Returns the array. */
+static SHELL_VAR **
+fapply (func)
+     sh_var_map_func_t *func;
+{
+  SHELL_VAR **list;
+
+  list = map_over_funcs (func);
+  if (list /* && posixly_correct */)
+    sort_variables (list);
+  return (list);
+}
+
+/* Create a NULL terminated array of all the shell variables. */
+SHELL_VAR **
+all_shell_variables ()
+{
+  return (vapply ((sh_var_map_func_t *)NULL));
+}
+
+/* Create a NULL terminated array of all the shell functions. */
+SHELL_VAR **
+all_shell_functions ()
+{
+  return (fapply ((sh_var_map_func_t *)NULL));
+}
+
+static int
+visible_var (var)
+     SHELL_VAR *var;
+{
+  return (invisible_p (var) == 0);
+}
+
+SHELL_VAR **
+all_visible_functions ()
+{
+  return (fapply (visible_var));
+}
+
+SHELL_VAR **
+all_visible_variables ()
+{
+  return (vapply (visible_var));
+}
+
+/* Return non-zero if the variable VAR is visible and exported.  Array
+   variables cannot be exported. */
+static int
+visible_and_exported (var)
+     SHELL_VAR *var;
+{
+  return (invisible_p (var) == 0 && exported_p (var));
+}
+
+/* Return non-zero if VAR is a local variable in the current context and
+   is exported. */
+static int
+local_and_exported (var)
+     SHELL_VAR *var;
+{
+  return (invisible_p (var) == 0 && local_p (var) && var->context == variable_context && exported_p (var));
+}
+
+SHELL_VAR **
+all_exported_variables ()
+{
+  return (vapply (visible_and_exported));
+}
+
+SHELL_VAR **
+local_exported_variables ()
+{
+  return (vapply (local_and_exported));
+}
+
+static int
+variable_in_context (var)
+     SHELL_VAR *var;
+{
+  return (invisible_p (var) == 0 && local_p (var) && var->context == variable_context);
+}
+
+SHELL_VAR **
+all_local_variables ()
+{
+  VARLIST *vlist;
+  SHELL_VAR **ret;
+  VAR_CONTEXT *vc;
+
+  vc = shell_variables;
+  for (vc = shell_variables; vc; vc = vc->down)
+    if (vc_isfuncenv (vc) && vc->scope == variable_context)
+      break;
+
+  if (vc == 0)
+    {
+      internal_error (_("all_local_variables: no function context at current scope"));
+      return (SHELL_VAR **)NULL;
+    }
+  if (vc->table == 0 || HASH_ENTRIES (vc->table) == 0 || vc_haslocals (vc) == 0)
+    return (SHELL_VAR **)NULL;
+    
+  vlist = vlist_alloc (HASH_ENTRIES (vc->table));
+
+  flatten (vc->table, variable_in_context, vlist, 0);
+
+  ret = vlist->list;
+  free (vlist);
+  if (ret)
+    sort_variables (ret);
+  return ret;
+}
+
+#if defined (ARRAY_VARS)
+/* Return non-zero if the variable VAR is visible and an array. */
+static int
+visible_array_vars (var)
+     SHELL_VAR *var;
+{
+  return (invisible_p (var) == 0 && array_p (var));
+}
+
+SHELL_VAR **
+all_array_variables ()
+{
+  return (vapply (visible_array_vars));
+}
+#endif /* ARRAY_VARS */
+
+char **
+all_variables_matching_prefix (prefix)
+     const char *prefix;
+{
+  SHELL_VAR **varlist;
+  char **rlist;
+  int vind, rind, plen;
+
+  plen = STRLEN (prefix);
+  varlist = all_visible_variables ();
+  for (vind = 0; varlist && varlist[vind]; vind++)
+    ;
+  if (varlist == 0 || vind == 0)
+    return ((char **)NULL);
+  rlist = strvec_create (vind + 1);
+  for (vind = rind = 0; varlist[vind]; vind++)
+    {
+      if (plen == 0 || STREQN (prefix, varlist[vind]->name, plen))
+       rlist[rind++] = savestring (varlist[vind]->name);
+    }
+  rlist[rind] = (char *)0;
+  free (varlist);
+
+  return rlist;
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*              Managing temporary variable scopes                 */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Make variable NAME have VALUE in the temporary environment. */
+static SHELL_VAR *
+bind_tempenv_variable (name, value)
+     const char *name;
+     char *value;
+{
+  SHELL_VAR *var;
+
+  var = temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL;
+
+  if (var)
+    {
+      FREE (value_cell (var));
+      var_setvalue (var, savestring (value));
+      INVALIDATE_EXPORTSTR (var);
+    }
+
+  return (var);
+}
+
+/* Find a variable in the temporary environment that is named NAME.
+   Return the SHELL_VAR *, or NULL if not found. */
+SHELL_VAR *
+find_tempenv_variable (name)
+     const char *name;
+{
+  return (temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL);
+}
+
+/* Push the variable described by (SHELL_VAR *)DATA down to the next
+   variable context from the temporary environment. */
+static void
+push_temp_var (data)
+     PTR_T data;
+{
+  SHELL_VAR *var, *v;
+  HASH_TABLE *binding_table;
+
+  var = (SHELL_VAR *)data;
+
+  binding_table = shell_variables->table;
+  if (binding_table == 0)
+    {
+      if (shell_variables == global_variables)
+       /* shouldn't happen */
+       binding_table = shell_variables->table = global_variables->table = hash_create (0);
+      else
+       binding_table = shell_variables->table = hash_create (TEMPENV_HASH_BUCKETS);
+    }
+
+  v = bind_variable_internal (var->name, value_cell (var), binding_table, 0);
+
+  /* XXX - should we set the context here?  It shouldn't matter because of how
+     assign_in_env works, but might want to check. */
+  if (binding_table == global_variables->table)                /* XXX */
+    var->attributes &= ~(att_tempvar|att_propagate);
+  else
+    {
+      var->attributes |= att_propagate;
+      if  (binding_table == shell_variables->table)
+       shell_variables->flags |= VC_HASTMPVAR;
+    }
+  v->attributes |= var->attributes;
+
+  dispose_variable (var);
+}
+
+static void
+propagate_temp_var (data)
+     PTR_T data;
+{
+  SHELL_VAR *var;
+
+  var = (SHELL_VAR *)data;
+  if (tempvar_p (var) && (var->attributes & att_propagate))
+    push_temp_var (data);
+  else
+    dispose_variable (var);
+}
+
+/* Free the storage used in the hash table for temporary
+   environment variables.  PUSHF is a function to be called
+   to free each hash table entry.  It takes care of pushing variables
+   to previous scopes if appropriate. */
+static void
+dispose_temporary_env (pushf)
+     sh_free_func_t *pushf;
+{
+  hash_flush (temporary_env, pushf);
+  hash_dispose (temporary_env);
+  temporary_env  = (HASH_TABLE *)NULL;
+
+  array_needs_making = 1;
+
+  sv_ifs ("IFS");              /* XXX here for now */
+}
+
+void
+dispose_used_env_vars ()
+{
+  if (temporary_env)
+    dispose_temporary_env (propagate_temp_var);
+}
+
+/* Take all of the shell variables in the temporary environment HASH_TABLE
+   and make shell variables from them at the current variable context. */
+void
+merge_temporary_env ()
+{
+  if (temporary_env)
+    dispose_temporary_env (push_temp_var);
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*          Creating and manipulating the environment              */
+/*                                                                 */
+/* **************************************************************** */
+
+static inline char *
+mk_env_string (name, value)
+     const char *name, *value;
+{
+  int name_len, value_len;
+  char *p;
+
+  name_len = strlen (name);
+  value_len = STRLEN (value);
+  p = (char *)xmalloc (2 + name_len + value_len);
+  strcpy (p, name);
+  p[name_len] = '=';
+  if (value && *value)
+    strcpy (p + name_len + 1, value);
+  else
+    p[name_len + 1] = '\0';
+  return (p);
+}
+
+#ifdef DEBUG
+/* Debugging */
+static int
+valid_exportstr (v)
+     SHELL_VAR *v;
+{
+  char *s;
+
+  s = v->exportstr;
+  if (legal_variable_starter ((unsigned char)*s) == 0)
+    {
+      internal_error (_("invalid character %d in exportstr for %s"), *s, v->name);
+      return (0);
+    }
+  for (s = v->exportstr + 1; s && *s; s++)
+    {
+      if (*s == '=')
+       break;
+      if (legal_variable_char ((unsigned char)*s) == 0)
+       {
+         internal_error (_("invalid character %d in exportstr for %s"), *s, v->name);
+         return (0);
+       }
+    }
+  if (*s != '=')
+    {
+      internal_error (_("no `=' in exportstr for %s"), v->name);
+      return (0);
+    }
+  return (1);
+}
+#endif
+
+static char **
+make_env_array_from_var_list (vars)
+     SHELL_VAR **vars;
+{
+  register int i, list_index;
+  register SHELL_VAR *var;
+  char **list, *value;
+
+  list = strvec_create ((1 + strvec_len ((char **)vars)));
+
+#define USE_EXPORTSTR (value == var->exportstr)
+
+  for (i = 0, list_index = 0; var = vars[i]; i++)
+    {
+#if defined (__CYGWIN__)
+      /* We don't use the exportstr stuff on Cygwin at all. */
+      INVALIDATE_EXPORTSTR (var);
+#endif
+      if (var->exportstr)
+       value = var->exportstr;
+      else if (function_p (var))
+       value = named_function_string ((char *)NULL, function_cell (var), 0);
+#if defined (ARRAY_VARS)
+      else if (array_p (var))
+#  if 0
+       value = array_to_assignment_string (array_cell (var));
+#  else
+       continue;       /* XXX array vars cannot yet be exported */
+#  endif
+#endif
+      else
+       value = value_cell (var);
+
+      if (value)
+       {
+         /* Gee, I'd like to get away with not using savestring() if we're
+            using the cached exportstr... */
+         list[list_index] = USE_EXPORTSTR ? savestring (value)
+                                          : mk_env_string (var->name, value);
+
+         if (USE_EXPORTSTR == 0)
+           SAVE_EXPORTSTR (var, list[list_index]);
+
+         list_index++;
+#undef USE_EXPORTSTR
+
+#if 0  /* not yet */
+#if defined (ARRAY_VARS)
+         if (array_p (var))
+           free (value);
+#endif
+#endif
+       }
+    }
+
+  list[list_index] = (char *)NULL;
+  return (list);
+}
+
+/* Make an array of assignment statements from the hash table
+   HASHED_VARS which contains SHELL_VARs.  Only visible, exported
+   variables are eligible. */
+static char **
+make_var_export_array (vcxt)
+     VAR_CONTEXT *vcxt;
+{
+  char **list;
+  SHELL_VAR **vars;
+
+  vars = map_over (visible_and_exported, vcxt);
+
+  if (vars == 0)
+    return (char **)NULL;
+
+  list = make_env_array_from_var_list (vars);
+
+  free (vars);
+  return (list);
+}
+
+static char **
+make_func_export_array ()
+{
+  char **list;
+  SHELL_VAR **vars;
+
+  vars = map_over_funcs (visible_and_exported);
+  if (vars == 0)
+    return (char **)NULL;
+
+  list = make_env_array_from_var_list (vars);
+
+  free (vars);
+  return (list);
+}
+
+/* Add ENVSTR to the end of the exported environment, EXPORT_ENV. */
+#define add_to_export_env(envstr,do_alloc) \
+do \
+  { \
+    if (export_env_index >= (export_env_size - 1)) \
+      { \
+       export_env_size += 16; \
+       export_env = strvec_resize (export_env, export_env_size); \
+      } \
+    export_env[export_env_index++] = (do_alloc) ? savestring (envstr) : envstr; \
+    export_env[export_env_index] = (char *)NULL; \
+  } while (0)
+
+/* Add ASSIGN to EXPORT_ENV, or supercede a previous assignment in the
+   array with the same left-hand side.  Return the new EXPORT_ENV. */
+char **
+add_or_supercede_exported_var (assign, do_alloc)
+     char *assign;
+     int do_alloc;
+{
+  register int i;
+  int equal_offset;
+
+  equal_offset = assignment (assign, 0);
+  if (equal_offset == 0)
+    return (export_env);
+
+  /* If this is a function, then only supersede the function definition.
+     We do this by including the `=() {' in the comparison, like
+     initialize_shell_variables does. */
+  if (assign[equal_offset + 1] == '(' &&
+     strncmp (assign + equal_offset + 2, ") {", 3) == 0)               /* } */
+    equal_offset += 4;
+
+  for (i = 0; i < export_env_index; i++)
+    {
+      if (STREQN (assign, export_env[i], equal_offset + 1))
+       {
+         free (export_env[i]);
+         export_env[i] = do_alloc ? savestring (assign) : assign;
+         return (export_env);
+       }
+    }
+  add_to_export_env (assign, do_alloc);
+  return (export_env);
+}
+
+static void
+add_temp_array_to_env (temp_array, do_alloc, do_supercede)
+     char **temp_array;
+     int do_alloc, do_supercede;
+{
+  register int i;
+
+  if (temp_array == 0)
+    return;
+
+  for (i = 0; temp_array[i]; i++)
+    {
+      if (do_supercede)
+       export_env = add_or_supercede_exported_var (temp_array[i], do_alloc);
+      else
+       add_to_export_env (temp_array[i], do_alloc);
+    }
+
+  free (temp_array);
+}
+
+/* Make the environment array for the command about to be executed, if the
+   array needs making.  Otherwise, do nothing.  If a shell action could
+   change the array that commands receive for their environment, then the
+   code should `array_needs_making++'.
+
+   The order to add to the array is:
+       temporary_env
+       list of var contexts whose head is shell_variables
+       shell_functions
+
+  This is the shell variable lookup order.  We add only new variable
+  names at each step, which allows local variables and variables in
+  the temporary environments to shadow variables in the global (or
+  any previous) scope.
+*/
+
+static int
+n_shell_variables ()
+{
+  VAR_CONTEXT *vc;
+  int n;
+
+  for (n = 0, vc = shell_variables; vc; vc = vc->down)
+    n += HASH_ENTRIES (vc->table);
+  return n;
+}
+
+void
+maybe_make_export_env ()
+{
+  register char **temp_array;
+  int new_size;
+  VAR_CONTEXT *tcxt;
+
+  if (array_needs_making)
+    {
+      if (export_env)
+        strvec_flush (export_env);
+
+      /* Make a guess based on how many shell variables and functions we
+        have.  Since there will always be array variables, and array
+        variables are not (yet) exported, this will always be big enough
+        for the exported variables and functions. */
+      new_size = n_shell_variables () + HASH_ENTRIES (shell_functions) + 1 +
+                HASH_ENTRIES (temporary_env);
+      if (new_size > export_env_size)
+       {
+         export_env_size = new_size;
+         export_env = strvec_resize (export_env, export_env_size);
+       }
+      export_env[export_env_index = 0] = (char *)NULL;
+
+      /* Make a dummy variable context from the  temporary_env, stick it on
+         the front of shell_variables, call make_var_export_array on the
+         whole thing to flatten it, and convert the list of SHELL_VAR *s
+         to the form needed by the environment. */
+      if (temporary_env)
+        {
+          tcxt = new_var_context ((char *)NULL, 0);
+          tcxt->table = temporary_env;
+          tcxt->down = shell_variables;
+        }
+      else
+        tcxt = shell_variables;
+      
+      temp_array = make_var_export_array (tcxt);
+      if (temp_array)
+       add_temp_array_to_env (temp_array, 0, 0);
+
+      if (tcxt != shell_variables)
+        free (tcxt);
+
+#if defined (RESTRICTED_SHELL)
+      /* Restricted shells may not export shell functions. */
+      temp_array = restricted ? (char **)0 : make_func_export_array ();
+#else
+      temp_array = make_func_export_array ();
+#endif
+      if (temp_array)
+       add_temp_array_to_env (temp_array, 0, 0);
+
+      array_needs_making = 0;
+    }
+}
+
+/* This is an efficiency hack.  PWD and OLDPWD are auto-exported, so
+   we will need to remake the exported environment every time we
+   change directories.  `_' is always put into the environment for
+   every external command, so without special treatment it will always
+   cause the environment to be remade.
+
+   If there is no other reason to make the exported environment, we can
+   just update the variables in place and mark the exported environment
+   as no longer needing a remake. */
+void
+update_export_env_inplace (env_prefix, preflen, value)
+     char *env_prefix;
+     int preflen;
+     char *value;
+{
+  char *evar;
+
+  evar = (char *)xmalloc (STRLEN (value) + preflen + 1);
+  strcpy (evar, env_prefix);
+  if (value)
+    strcpy (evar + preflen, value);
+  export_env = add_or_supercede_exported_var (evar, 0);
+}
+
+/* We always put _ in the environment as the name of this command. */
+void
+put_command_name_into_env (command_name)
+     char *command_name;
+{
+  update_export_env_inplace ("_=", 2, command_name);
+}
+
+#if 0  /* UNUSED -- it caused too many problems */
+void
+put_gnu_argv_flags_into_env (pid, flags_string)
+     intmax_t pid;
+     char *flags_string;
+{
+  char *dummy, *pbuf;
+  int l, fl;
+
+  pbuf = itos (pid);
+  l = strlen (pbuf);
+
+  fl = strlen (flags_string);
+
+  dummy = (char *)xmalloc (l + fl + 30);
+  dummy[0] = '_';
+  strcpy (dummy + 1, pbuf);
+  strcpy (dummy + 1 + l, "_GNU_nonoption_argv_flags_");
+  dummy[l + 27] = '=';
+  strcpy (dummy + l + 28, flags_string);
+
+  free (pbuf);
+
+  export_env = add_or_supercede_exported_var (dummy, 0);
+}
+#endif
+
+/* **************************************************************** */
+/*                                                                 */
+/*                   Managing variable contexts                    */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Allocate and return a new variable context with NAME and FLAGS.
+   NAME can be NULL. */
+
+VAR_CONTEXT *
+new_var_context (name, flags)
+     char *name;
+     int flags;
+{
+  VAR_CONTEXT *vc;
+
+  vc = (VAR_CONTEXT *)xmalloc (sizeof (VAR_CONTEXT));
+  vc->name = name ? savestring (name) : (char *)NULL;
+  vc->scope = variable_context;
+  vc->flags = flags;
+
+  vc->up = vc->down = (VAR_CONTEXT *)NULL;
+  vc->table = (HASH_TABLE *)NULL;
+
+  return vc;
+}
+
+/* Free a variable context and its data, including the hash table.  Dispose
+   all of the variables. */
+void
+dispose_var_context (vc)
+     VAR_CONTEXT *vc;
+{
+  FREE (vc->name);
+
+  if (vc->table)
+    {
+      delete_all_variables (vc->table);
+      hash_dispose (vc->table);
+    }
+
+  free (vc);
+}
+
+/* Set VAR's scope level to the current variable context. */
+static int
+set_context (var)
+     SHELL_VAR *var;
+{
+  return (var->context = variable_context);
+}
+
+/* Make a new variable context with NAME and FLAGS and a HASH_TABLE of
+   temporary variables, and push it onto shell_variables.  This is
+   for shell functions. */
+VAR_CONTEXT *
+push_var_context (name, flags, tempvars)
+     char *name;
+     int flags;
+     HASH_TABLE *tempvars;
+{
+  VAR_CONTEXT *vc;
+
+  vc = new_var_context (name, flags);
+  vc->table = tempvars;
+  if (tempvars)
+    {
+      /* Have to do this because the temp environment was created before
+        variable_context was incremented. */
+      flatten (tempvars, set_context, (VARLIST *)NULL, 0);
+      vc->flags |= VC_HASTMPVAR;
+    }
+  vc->down = shell_variables;
+  shell_variables->up = vc;
+
+  return (shell_variables = vc);
+}
+
+static void
+push_func_var (data)
+     PTR_T data;
+{
+  SHELL_VAR *var, *v;
+
+  var = (SHELL_VAR *)data;
+
+  if (tempvar_p (var) && (posixly_correct || (var->attributes & att_propagate)))
+    {
+      /* XXX - should we set v->context here? */
+      v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0);
+      if (shell_variables == global_variables)
+       var->attributes &= ~(att_tempvar|att_propagate);
+      else
+        shell_variables->flags |= VC_HASTMPVAR;
+      v->attributes |= var->attributes;
+    }
+
+  dispose_variable (var);
+}
+
+/* Pop the top context off of VCXT and dispose of it, returning the rest of
+   the stack. */
+void
+pop_var_context ()
+{
+  VAR_CONTEXT *ret, *vcxt;
+
+  vcxt = shell_variables;
+  if (vc_isfuncenv (vcxt) == 0)
+    {
+      internal_error (_("pop_var_context: head of shell_variables not a function context"));
+      return;
+    }
+
+  if (ret = vcxt->down)
+    {
+      ret->up = (VAR_CONTEXT *)NULL;
+      shell_variables = ret;
+      if (vcxt->table)
+       hash_flush (vcxt->table, push_func_var);
+      dispose_var_context (vcxt);
+    }
+  else
+    internal_error (_("pop_var_context: no global_variables context"));
+}
+
+/* Delete the HASH_TABLEs for all variable contexts beginning at VCXT, and
+   all of the VAR_CONTEXTs except GLOBAL_VARIABLES. */
+void
+delete_all_contexts (vcxt)
+     VAR_CONTEXT *vcxt;
+{
+  VAR_CONTEXT *v, *t;
+
+  for (v = vcxt; v != global_variables; v = t)
+    {
+      t = v->down;
+      dispose_var_context (v);
+    }            
+
+  delete_all_variables (global_variables->table);
+  shell_variables = global_variables;
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*        Pushing and Popping temporary variable scopes            */
+/*                                                                 */
+/* **************************************************************** */
+
+VAR_CONTEXT *
+push_scope (flags, tmpvars)
+     int flags;
+     HASH_TABLE *tmpvars;
+{
+  return (push_var_context ((char *)NULL, flags, tmpvars));
+}
+
+static void
+push_exported_var (data)
+     PTR_T data;
+{
+  SHELL_VAR *var, *v;
+
+  var = (SHELL_VAR *)data;
+
+  /* If a temp var had its export attribute set, or it's marked to be
+     propagated, bind it in the previous scope before disposing it. */
+  if (exported_p (var) || (var->attributes & att_propagate))
+    {
+      var->attributes &= ~att_tempvar;         /* XXX */
+      v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0);
+      if (shell_variables == global_variables)
+       var->attributes &= ~att_propagate;
+      v->attributes |= var->attributes;
+    }
+
+  dispose_variable (var);
+}
+
+void
+pop_scope (is_special)
+     int is_special;
+{
+  VAR_CONTEXT *vcxt, *ret;
+
+  vcxt = shell_variables;
+  if (vc_istempscope (vcxt) == 0)
+    {
+      internal_error (_("pop_scope: head of shell_variables not a temporary environment scope"));
+      return;
+    }
+
+  ret = vcxt->down;
+  if (ret)
+    ret->up = (VAR_CONTEXT *)NULL;
+
+  shell_variables = ret;
+
+  /* Now we can take care of merging variables in VCXT into set of scopes
+     whose head is RET (shell_variables). */
+  FREE (vcxt->name);
+  if (vcxt->table)
+    {
+      if (is_special)
+       hash_flush (vcxt->table, push_func_var);
+      else
+       hash_flush (vcxt->table, push_exported_var);
+      hash_dispose (vcxt->table);
+    }
+  free (vcxt);
+
+  sv_ifs ("IFS");      /* XXX here for now */
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*              Pushing and Popping function contexts              */
+/*                                                                 */
+/* **************************************************************** */
+
+static WORD_LIST **dollar_arg_stack = (WORD_LIST **)NULL;
+static int dollar_arg_stack_slots;
+static int dollar_arg_stack_index;
+
+/* XXX - we might want to consider pushing and popping the `getopts' state
+   when we modify the positional parameters. */
+void
+push_context (name, is_subshell, tempvars)
+     char *name;       /* function name */
+     int is_subshell;
+     HASH_TABLE *tempvars;
+{
+  if (is_subshell == 0)
+    push_dollar_vars ();
+  variable_context++;
+  push_var_context (name, VC_FUNCENV, tempvars);
+}
+
+/* Only called when subshell == 0, so we don't need to check, and can
+   unconditionally pop the dollar vars off the stack. */
+void
+pop_context ()
+{
+  pop_dollar_vars ();
+  variable_context--;
+  pop_var_context ();
+
+  sv_ifs ("IFS");              /* XXX here for now */
+}
+
+/* Save the existing positional parameters on a stack. */
+void
+push_dollar_vars ()
+{
+  if (dollar_arg_stack_index + 2 > dollar_arg_stack_slots)
+    {
+      dollar_arg_stack = (WORD_LIST **)
+       xrealloc (dollar_arg_stack, (dollar_arg_stack_slots += 10)
+                 * sizeof (WORD_LIST **));
+    }
+  dollar_arg_stack[dollar_arg_stack_index++] = list_rest_of_args ();
+  dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL;
+}
+
+/* Restore the positional parameters from our stack. */
+void
+pop_dollar_vars ()
+{
+  if (!dollar_arg_stack || dollar_arg_stack_index == 0)
+    return;
+
+  remember_args (dollar_arg_stack[--dollar_arg_stack_index], 1);
+  dispose_words (dollar_arg_stack[dollar_arg_stack_index]);
+  dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL;
+  set_dollar_vars_unchanged ();
+}
+
+void
+dispose_saved_dollar_vars ()
+{
+  if (!dollar_arg_stack || dollar_arg_stack_index == 0)
+    return;
+
+  dispose_words (dollar_arg_stack[dollar_arg_stack_index]);
+  dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL;
+}
+
+/* Manipulate the special BASH_ARGV and BASH_ARGC variables. */
+
+void
+push_args (list)
+     WORD_LIST *list;
+{
+#if defined (ARRAY_VARS) && defined (DEBUGGER)
+  SHELL_VAR *bash_argv_v, *bash_argc_v;
+  ARRAY *bash_argv_a, *bash_argc_a;
+  WORD_LIST *l;
+  arrayind_t i;
+  char *t;
+
+  GET_ARRAY_FROM_VAR ("BASH_ARGV", bash_argv_v, bash_argv_a);
+  GET_ARRAY_FROM_VAR ("BASH_ARGC", bash_argc_v, bash_argc_a);
+
+  for (l = list, i = 0; l; l = l->next, i++)
+    array_push (bash_argv_a, l->word->word);
+
+  t = itos (i);
+  array_push (bash_argc_a, t);
+  free (t);
+#endif /* ARRAY_VARS && DEBUGGER */
+}
+
+/* Remove arguments from BASH_ARGV array.  Pop top element off BASH_ARGC
+   array and use that value as the count of elements to remove from
+   BASH_ARGV. */
+void
+pop_args ()
+{
+#if defined (ARRAY_VARS) && defined (DEBUGGER)
+  SHELL_VAR *bash_argv_v, *bash_argc_v;
+  ARRAY *bash_argv_a, *bash_argc_a;
+  ARRAY_ELEMENT *ce;
+  intmax_t i;
+
+  GET_ARRAY_FROM_VAR ("BASH_ARGV", bash_argv_v, bash_argv_a);
+  GET_ARRAY_FROM_VAR ("BASH_ARGC", bash_argc_v, bash_argc_a);
+
+  ce = array_shift (bash_argc_a, 1, 0);
+  if (ce == 0 || legal_number (element_value (ce), &i) == 0)
+    i = 0;
+
+  for ( ; i > 0; i--)
+    array_pop (bash_argv_a);
+  array_dispose_element (ce);
+#endif /* ARRAY_VARS && DEBUGGER */
+}
+
+/*************************************************
+ *                                              *
+ *     Functions to manage special variables    *
+ *                                              *
+ *************************************************/
+
+/* Extern declarations for variables this code has to manage. */
+extern int eof_encountered, eof_encountered_limit, ignoreeof;
+
+#if defined (READLINE)
+extern int no_line_editing;
+extern int hostname_list_initialized;
+#endif
+
+/* An alist of name.function for each special variable.  Most of the
+   functions don't do much, and in fact, this would be faster with a
+   switch statement, but by the end of this file, I am sick of switch
+   statements. */
+
+#define SET_INT_VAR(name, intvar)  intvar = find_variable (name) != 0
+
+/* This table will be sorted with qsort() the first time it's accessed. */
+struct name_and_function {
+  char *name;
+  sh_sv_func_t *function;
+};
+
+static struct name_and_function special_vars[] = {
+  { "GLOBIGNORE", sv_globignore },
+
+#if defined (HISTORY)
+  { "HISTCONTROL", sv_history_control },
+  { "HISTFILESIZE", sv_histsize },
+  { "HISTIGNORE", sv_histignore },
+  { "HISTSIZE", sv_histsize },
+  { "HISTTIMEFORMAT", sv_histtimefmt },
+#endif
+
+#if defined (READLINE)
+  { "HOSTFILE", sv_hostfile },
+#endif
+
+  { "IFS", sv_ifs },
+  { "IGNOREEOF", sv_ignoreeof },
+
+  { "LANG", sv_locale },
+  { "LC_ALL", sv_locale },
+  { "LC_COLLATE", sv_locale },
+  { "LC_CTYPE", sv_locale },
+  { "LC_MESSAGES", sv_locale },
+  { "LC_NUMERIC", sv_locale },
+
+  { "MAIL", sv_mail },
+  { "MAILCHECK", sv_mail },
+  { "MAILPATH", sv_mail },
+
+  { "OPTERR", sv_opterr },
+  { "OPTIND", sv_optind },
+
+  { "PATH", sv_path },
+  { "POSIXLY_CORRECT", sv_strict_posix },
+
+#if defined (READLINE)
+  { "TERM", sv_terminal },
+  { "TERMCAP", sv_terminal },
+  { "TERMINFO", sv_terminal },
+#endif /* READLINE */
+
+  { "TEXTDOMAIN", sv_locale },
+  { "TEXTDOMAINDIR", sv_locale },
+
+#if defined (HAVE_TZSET) && defined (PROMPT_STRING_DECODE)
+  { "TZ", sv_tz },
+#endif
+
+#if defined (HISTORY) && defined (BANG_HISTORY)
+  { "histchars", sv_histchars },
+#endif /* HISTORY && BANG_HISTORY */
+
+  { "ignoreeof", sv_ignoreeof },
+
+  { (char *)0, (sh_sv_func_t *)0 }
+};
+
+#define N_SPECIAL_VARS (sizeof (special_vars) / sizeof (special_vars[0]) - 1)
+
+static int
+sv_compare (sv1, sv2)
+     struct name_and_function *sv1, *sv2;
+{
+  int r;
+
+  if ((r = sv1->name[0] - sv2->name[0]) == 0)
+    r = strcmp (sv1->name, sv2->name);
+  return r;
+}
+
+static inline int
+find_special_var (name)
+     const char *name;
+{
+  register int i, r;
+
+  for (i = 0; special_vars[i].name; i++)
+    {
+      r = special_vars[i].name[0] - name[0];
+      if (r == 0)
+       r = strcmp (special_vars[i].name, name);
+      if (r == 0)
+       return i;
+      else if (r > 0)
+       /* Can't match any of rest of elements in sorted list.  Take this out
+          if it causes problems in certain environments. */
+        break;
+    }
+  return -1;
+}
+
+/* The variable in NAME has just had its state changed.  Check to see if it
+   is one of the special ones where something special happens. */
+void
+stupidly_hack_special_variables (name)
+     char *name;
+{
+  static int sv_sorted = 0;
+  int i;
+
+  if (sv_sorted == 0)  /* shouldn't need, but it's fairly cheap. */
+    {
+      qsort (special_vars, N_SPECIAL_VARS, sizeof (special_vars[0]),
+               (QSFUNC *)sv_compare);
+      sv_sorted = 1;
+    }
+
+  i = find_special_var (name);
+  if (i != -1)
+    (*(special_vars[i].function)) (name);
+}
+
+void
+sv_ifs (name)
+     char *name;
+{
+  SHELL_VAR *v;
+
+  v = find_variable ("IFS");
+  setifs (v);
+}
+
+/* What to do just after the PATH variable has changed. */
+void
+sv_path (name)
+     char *name;
+{
+  /* hash -r */
+  phash_flush ();
+}
+
+/* What to do just after one of the MAILxxxx variables has changed.  NAME
+   is the name of the variable.  This is called with NAME set to one of
+   MAIL, MAILCHECK, or MAILPATH.  */
+void
+sv_mail (name)
+     char *name;
+{
+  /* If the time interval for checking the files has changed, then
+     reset the mail timer.  Otherwise, one of the pathname vars
+     to the users mailbox has changed, so rebuild the array of
+     filenames. */
+  if (name[4] == 'C')  /* if (strcmp (name, "MAILCHECK") == 0) */
+    reset_mail_timer ();
+  else
+    {
+      free_mail_files ();
+      remember_mail_dates ();
+    }
+}
+
+/* What to do when GLOBIGNORE changes. */
+void
+sv_globignore (name)
+     char *name;
+{
+  setup_glob_ignore (name);
+}
+
+#if defined (READLINE)
+/* What to do just after one of the TERMxxx variables has changed.
+   If we are an interactive shell, then try to reset the terminal
+   information in readline. */
+void
+sv_terminal (name)
+     char *name;
+{
+  if (interactive_shell && no_line_editing == 0)
+    rl_reset_terminal (get_string_value ("TERM"));
+}
+
+void
+sv_hostfile (name)
+     char *name;
+{
+  SHELL_VAR *v;
+
+  v = find_variable (name);
+  if (v == 0)
+    clear_hostname_list ();
+  else
+    hostname_list_initialized = 0;
+}
+#endif /* READLINE */
+
+#if defined (HISTORY)
+/* What to do after the HISTSIZE or HISTFILESIZE variables change.
+   If there is a value for this HISTSIZE (and it is numeric), then stifle
+   the history.  Otherwise, if there is NO value for this variable,
+   unstifle the history.  If name is HISTFILESIZE, and its value is
+   numeric, truncate the history file to hold no more than that many
+   lines. */
+void
+sv_histsize (name)
+     char *name;
+{
+  char *temp;
+  intmax_t num;
+
+  temp = get_string_value (name);
+
+  if (temp && *temp)
+    {
+      if (legal_number (temp, &num))
+       {
+         if (name[4] == 'S')
+           {
+             stifle_history (num);
+             num = where_history ();
+             if (history_lines_this_session > num)
+               history_lines_this_session = num;
+           }
+         else
+           {
+             history_truncate_file (get_string_value ("HISTFILE"), (int)num);
+             if (num <= history_lines_in_file)
+               history_lines_in_file = num;
+           }
+       }
+    }
+  else if (name[4] == 'S')
+    unstifle_history ();
+}
+
+/* What to do after the HISTIGNORE variable changes. */
+void
+sv_histignore (name)
+     char *name;
+{
+  setup_history_ignore (name);
+}
+
+/* What to do after the HISTCONTROL variable changes. */
+void
+sv_history_control (name)
+     char *name;
+{
+  char *temp;
+  char *val;
+  int tptr;
+
+  history_control = 0;
+  temp = get_string_value (name);
+
+  if (temp == 0 || *temp == 0)
+    return;
+
+  tptr = 0;
+  while (val = extract_colon_unit (temp, &tptr))
+    {
+      if (STREQ (val, "ignorespace"))
+       history_control |= HC_IGNSPACE;
+      else if (STREQ (val, "ignoredups"))
+       history_control |= HC_IGNDUPS;
+      else if (STREQ (val, "ignoreboth"))
+       history_control |= HC_IGNBOTH;
+      else if (STREQ (val, "erasedups"))
+       history_control |= HC_ERASEDUPS;
+
+      free (val);
+    }
+}
+
+#if defined (BANG_HISTORY)
+/* Setting/unsetting of the history expansion character. */
+void
+sv_histchars (name)
+     char *name;
+{
+  char *temp;
+
+  temp = get_string_value (name);
+  if (temp)
+    {
+      history_expansion_char = *temp;
+      if (temp[0] && temp[1])
+       {
+         history_subst_char = temp[1];
+         if (temp[2])
+             history_comment_char = temp[2];
+       }
+    }
+  else
+    {
+      history_expansion_char = '!';
+      history_subst_char = '^';
+      history_comment_char = '#';
+    }
+}
+#endif /* BANG_HISTORY */
+
+void
+sv_histtimefmt (name)
+     char *name;
+{
+  SHELL_VAR *v;
+
+  v = find_variable (name);
+  history_write_timestamps = (v != 0);
+}
+#endif /* HISTORY */
+
+#if defined (HAVE_TZSET) && defined (PROMPT_STRING_DECODE)
+void
+sv_tz (name)
+     char *name;
+{
+  tzset ();
+}
+#endif
+
+/* If the variable exists, then the value of it can be the number
+   of times we actually ignore the EOF.  The default is small,
+   (smaller than csh, anyway). */
+void
+sv_ignoreeof (name)
+     char *name;
+{
+  SHELL_VAR *tmp_var;
+  char *temp;
+
+  eof_encountered = 0;
+
+  tmp_var = find_variable (name);
+  ignoreeof = tmp_var != 0;
+  temp = tmp_var ? value_cell (tmp_var) : (char *)NULL;
+  if (temp)
+    eof_encountered_limit = (*temp && all_digits (temp)) ? atoi (temp) : 10;
+  set_shellopts ();    /* make sure `ignoreeof' is/is not in $SHELLOPTS */
+}
+
+void
+sv_optind (name)
+     char *name;
+{
+  char *tt;
+  int s;
+
+  tt = get_string_value ("OPTIND");
+  if (tt && *tt)
+    {
+      s = atoi (tt);
+
+      /* According to POSIX, setting OPTIND=1 resets the internal state
+        of getopt (). */
+      if (s < 0 || s == 1)
+       s = 0;
+    }
+  else
+    s = 0;
+  getopts_reset (s);
+}
+
+void
+sv_opterr (name)
+     char *name;
+{
+  char *tt;
+
+  tt = get_string_value ("OPTERR");
+  sh_opterr = (tt && *tt) ? atoi (tt) : 1;
+}
+
+void
+sv_strict_posix (name)
+     char *name;
+{
+  SET_INT_VAR (name, posixly_correct);
+  posix_initialize (posixly_correct);
+#if defined (READLINE)
+  if (interactive_shell)
+    posix_readline_initialize (posixly_correct);
+#endif /* READLINE */
+  set_shellopts ();    /* make sure `posix' is/is not in $SHELLOPTS */
+}
+
+void
+sv_locale (name)
+     char *name;
+{
+  char *v;
+
+  v = get_string_value (name);
+  if (name[0] == 'L' && name[1] == 'A')        /* LANG */
+    set_lang (name, v);
+  else
+    set_locale_var (name, v);          /* LC_*, TEXTDOMAIN* */
+}
+
+#if defined (ARRAY_VARS)
+void
+set_pipestatus_array (ps, nproc)
+     int *ps;
+     int nproc;
+{
+  SHELL_VAR *v;
+  ARRAY *a;
+  ARRAY_ELEMENT *ae;
+  register int i;
+  char *t, tbuf[INT_STRLEN_BOUND(int) + 1];
+
+  v = find_variable ("PIPESTATUS");
+  if (v == 0)
+    v = make_new_array_variable ("PIPESTATUS");
+  if (array_p (v) == 0)
+    return;            /* Do nothing if not an array variable. */
+  a = array_cell (v);
+
+  if (a == 0 || array_num_elements (a) == 0)
+    {
+      for (i = 0; i < nproc; i++)      /* was ps[i] != -1, not i < nproc */
+       {
+         t = inttostr (ps[i], tbuf, sizeof (tbuf));
+         array_insert (a, i, t);
+       }
+      return;
+    }
+
+  /* Fast case */
+  if (array_num_elements (a) == nproc && nproc == 1)
+    {
+      ae = element_forw (a->head);
+      free (element_value (ae));
+      ae->value = itos (ps[0]);
+    }
+  else if (array_num_elements (a) <= nproc)
+    {
+      /* modify in array_num_elements members in place, then add */
+      ae = a->head;
+      for (i = 0; i < array_num_elements (a); i++)
+       {
+         ae = element_forw (ae);
+         free (element_value (ae));
+         ae->value = itos (ps[i]);
+       }
+      /* add any more */
+      for ( ; i < nproc; i++)
+       {
+         t = inttostr (ps[i], tbuf, sizeof (tbuf));
+         array_insert (a, i, t);
+       }
+    }
+  else
+    {
+      /* deleting elements.  it's faster to rebuild the array. */        
+      array_flush (a);
+      for (i = 0; ps[i] != -1; i++)
+       {
+         t = inttostr (ps[i], tbuf, sizeof (tbuf));
+         array_insert (a, i, t);
+       }
+    }
+}
+#endif
+
+void
+set_pipestatus_from_exit (s)
+     int s;
+{
+#if defined (ARRAY_VARS)
+  static int v[2] = { 0, -1 };
+
+  v[0] = s;
+  set_pipestatus_array (v, 1);
+#endif
+}