]> git.ipfire.org Git - thirdparty/bash.git/blobdiff - execute_cmd.c
Bash-5.0 patch 4: the wait builtin without arguments only waits for known children...
[thirdparty/bash.git] / execute_cmd.c
index ce90d25b70ab388c0a53b5842f11aa8e2808c56e..8b3c83aa8aedb92353fd3fb93b56143238ca584c 100644 (file)
@@ -1,22 +1,23 @@
-/* execute_command.c -- Execute a COMMAND structure. */
+/* execute_cmd.c -- Execute a COMMAND structure. */
 
-/* Copyright (C) 1987-2005 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2018 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 free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
 
-   Bash is distributed in the hope that it will be useful, but WITHOUT
-   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
-   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
-   License for more details.
+   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. */
+   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
 #include "config.h"
 
 #if !defined (__GNUC__) && !defined (HAVE_ALLOCA_H) && defined (_AIX)
@@ -32,7 +33,7 @@
 #include "filecntl.h"
 #include "posixstat.h"
 #include <signal.h>
-#ifndef _MINIX
+#if defined (HAVE_SYS_PARAM_H)
 #  include <sys/param.h>
 #endif
 
 extern int errno;
 #endif
 
+#define NEED_FPURGE_DECL
+#define NEED_SH_SETLINEBUF_DECL
+
 #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 "parser.h"
 #include "flags.h"
 #include "builtins.h"
 #include "hashlib.h"
@@ -80,6 +85,8 @@ extern int errno;
 #include "builtins/common.h"
 #include "builtins/builtext.h" /* list of builtins */
 
+#include "builtins/getopt.h"
+
 #include <glob/strmatch.h>
 #include <tilde/tilde.h>
 
@@ -95,19 +102,13 @@ extern int errno;
 #  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 int tempenv_assign_error;
-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 defined (HAVE_MBSTR_H) && defined (HAVE_MBSCHR)
+#  include <mbstr.h>           /* mbschr */
+#endif
+
+extern int command_string_index;
+extern char *the_printed_command;
+extern time_t shell_start_time;
 #if 0
 extern char *glob_argv_flags;
 #endif
@@ -131,6 +132,7 @@ static int builtin_status __P((int));
 
 static int execute_for_command __P((FOR_COM *));
 #if defined (SELECT_COMMAND)
+static int displen __P((const char *));
 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));
@@ -174,21 +176,22 @@ static void execute_subshell_builtin_or_function __P((WORD_LIST *, REDIRECT *,
                                                      int, int, int,
                                                      struct fd_bitmap *,
                                                      int));
-static void execute_disk_command __P((WORD_LIST *, REDIRECT *, char *,
+static int 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 *));
+#if defined (COPROCESS_SUPPORT)
+static void coproc_setstatus __P((struct coproc *, int));
+static int execute_coproc __P((COMMAND *, int, int, struct fd_bitmap *));
+#endif
 
 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;
+static int execute_intern_function __P((WORD_DESC *, FUNCTION_DEF *));
 
 /* 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. */
@@ -203,29 +206,22 @@ char *this_command_name;
    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;
+volatile 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;
 
+/* Are we currently ignoring the -e option for the duration of a builtin's
+   execution? */
+int builtin_ignoring_errexit = 0;
+
 /* The list of redirections to perform which will undo the redirections
    that I made in the shell. */
 REDIRECT *redirection_undo_list = (REDIRECT *)NULL;
@@ -235,6 +231,18 @@ REDIRECT *redirection_undo_list = (REDIRECT *)NULL;
    that must be undone even when exec discards redirection_undo_list. */
 REDIRECT *exec_redirection_undo_list = (REDIRECT *)NULL;
 
+/* When greater than zero, value is the `level' of builtins we are
+   currently executing (e.g. `eval echo a' would have it set to 2). */
+int executing_builtin = 0;
+
+/* Non-zero if we are executing a command list (a;b;c, etc.) */
+int executing_list = 0;
+
+/* Non-zero if failing commands in a command substitution should not exit the
+   shell even if -e is set.  Used to pass the CMD_IGNORE_RETURN flag down to
+   commands run in command substitutions by parse_and_execute. */
+int comsub_ignore_return = 0;
+
 /* Non-zero if we have just forked and are currently running in a subshell
    environment. */
 int subshell_environment;
@@ -248,6 +256,42 @@ SHELL_VAR *this_shell_function;
 /* If non-zero, matches in case and [[ ... ]] are case-insensitive */
 int match_ignore_case = 0;
 
+int executing_command_builtin = 0;
+
+struct stat SB;                /* used for debugging */
+
+static int special_builtin_failed;
+
+static COMMAND *currently_executing_command;
+
+/* The line number that the currently executing function starts on. */
+static int function_line_number;
+
+/* 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;
+
+static int connection_count;
+
+/* $LINENO ($BASH_LINENO) for use by an ERR trap.  Global so parse_and_execute
+   can save and restore it. */
+int line_number_for_err_trap;
+
+/* A sort of function nesting level counter */
+int funcnest = 0;
+int funcnest_max = 0;
+
+int evalnest = 0;
+int evalnest_max = EVALNEST_MAX;
+
+int sourcenest = 0;
+int sourcenest_max = SOURCENEST_MAX;
+
+volatile int from_return_trap = 0;
+
+int lastpipe_opt = 0;
+
 struct fd_bitmap *current_fds_to_close = (struct fd_bitmap *)NULL;
 
 #define FD_BITMAP_DEFAULT_SIZE 32
@@ -313,11 +357,11 @@ executing_line_number ()
        return currently_executing_command->value.Cond->line;
 #endif
 #if defined (DPAREN_ARITHMETIC)
-      else if (currently_executing_command->type == cm_arith)
+      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)
+      if (currently_executing_command->type == cm_arith_for)
        return currently_executing_command->value.ArithFor->line;
 #endif
 
@@ -355,10 +399,11 @@ execute_command (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)
+  if (variable_context == 0 && executing_list == 0)
     unlink_fifo_list ();
 #endif /* PROCESS_SUBSTITUTION */
 
+  QUIT;
   return (result);
 }
 
@@ -405,6 +450,16 @@ cleanup_redirects (list)
   dispose_redirects (list);
 }
 
+void
+undo_partial_redirects ()
+{
+  if (redirection_undo_list)
+    {
+      cleanup_redirects (redirection_undo_list);
+      redirection_undo_list = (REDIRECT *)NULL;
+    }
+}
+
 #if 0
 /* Function to unwind_protect the redirections for functions and builtins. */
 static void
@@ -425,6 +480,16 @@ dispose_exec_redirects ()
     }
 }
 
+void
+dispose_partial_redirects ()
+{
+  if (redirection_undo_list)
+    {
+      dispose_redirects (redirection_undo_list);
+      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. */
@@ -473,7 +538,7 @@ async_redirect_stdin ()
 
 #define DESCRIBE_PID(pid) do { if (interactive) describe_pid (pid); } while (0)
 
-/* Execute the command passed in COMMAND, perhaps doing it asynchrounously.
+/* Execute the command passed in COMMAND, perhaps doing it asynchronously.
    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
@@ -493,14 +558,21 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out,
      int pipe_in, pipe_out;
      struct fd_bitmap *fds_to_close;
 {
-  int exec_result, invert, ignore_return, was_error_trap;
+  int exec_result, user_subshell, invert, ignore_return, was_error_trap;
   REDIRECT *my_undo_list, *exec_undo_list;
-  volatile int last_pid;
+  char *tcmd;
   volatile int save_line_number;
+#if defined (PROCESS_SUBSTITUTION)
+  volatile int ofifo, nfifo, osize, saved_fifo;
+  volatile char *ofifo_list;
+#endif
 
-  if (command == 0 || breaking || continuing || read_but_dont_execute)
+  if (breaking || continuing)
+    return (last_command_exit_value);
+  if (command == 0 || read_but_dont_execute)
     return (EXECUTION_SUCCESS);
 
+  QUIT;
   run_pending_traps ();
 
 #if 0
@@ -524,26 +596,76 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out,
   if (command->type == cm_subshell && (command->flags & CMD_NO_FORK))
     return (execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close));
 
+#if defined (COPROCESS_SUPPORT)
+  if (command->type == cm_coproc)
+    return (last_command_exit_value = execute_coproc (command, pipe_in, pipe_out, fds_to_close));
+#endif
+
+  user_subshell = command->type == cm_subshell || ((command->flags & CMD_WANT_SUBSHELL) != 0);
+
+#if defined (TIME_BEFORE_SUBSHELL)
+  if ((command->flags & CMD_TIME_PIPELINE) && user_subshell && asynchronous == 0)
+    {
+      command->flags |= CMD_FORCE_SUBSHELL;
+      exec_result = time_command (command, asynchronous, pipe_in, pipe_out, fds_to_close);
+      currently_executing_command = (COMMAND *)NULL;
+      return (exec_result);
+    }
+#endif
+
   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;
+      int s;
+      char *p;
 
       /* 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 (command->type == cm_subshell)
+       line_number_for_err_trap = line_number = command->value.Subshell->line; /* XXX - save value? */
+       /* Otherwise we defer setting line_number */
+      tcmd = make_command_string (command);
+      paren_pid = make_child (p = savestring (tcmd), asynchronous);
+
+      if (user_subshell && signal_is_trapped (ERROR_TRAP) && 
+         signal_in_progress (DEBUG_TRAP) == 0 && running_trap == 0)
+       {
+         FREE (the_printed_command_except_trap);
+         the_printed_command_except_trap = savestring (the_printed_command);
+       }
+
       if (paren_pid == 0)
-       exit (execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close));
-       /* NOTREACHED */
+        {
+#if defined (JOB_CONTROL)
+         FREE (p);             /* child doesn't use pointer */
+#endif
+         /* We want to run the exit trap for forced {} subshells, and we
+            want to note this before execute_in_subshell modifies the
+            COMMAND struct.  Need to keep in mind that execute_in_subshell
+            runs the exit trap for () subshells itself. */
+         /* This handles { command; } & */
+         s = user_subshell == 0 && command->type == cm_group && pipe_in == NO_PIPE && pipe_out == NO_PIPE && asynchronous;
+         /* run exit trap for : | { ...; } and { ...; } | : */
+         /* run exit trap for : | ( ...; ) and ( ...; ) | : */
+         s += user_subshell == 0 && command->type == cm_group && (pipe_in != NO_PIPE || pipe_out != NO_PIPE) && asynchronous == 0;
+
+         last_command_exit_value = execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close);
+         if (s)
+           subshell_exit (last_command_exit_value);
+         else
+           sh_exit (last_command_exit_value);
+         /* NOTREACHED */
+        }
       else
        {
          close_pipes (pipe_in, pipe_out);
 
 #if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD)
-         unlink_fifo_list ();
+         if (variable_context == 0)    /* wait until shell function completes */
+           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
@@ -557,17 +679,34 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out,
 
          if (asynchronous == 0)
            {
-             last_command_exit_value = wait_for (paren_pid);
+             was_error_trap = signal_is_trapped (ERROR_TRAP) && signal_is_ignored (ERROR_TRAP) == 0;
+             invert = (command->flags & CMD_INVERT_RETURN) != 0;
+             ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0;
+
+             exec_result = wait_for (paren_pid);
 
              /* If we have to, invert the return value. */
              if (invert)
-               exec_result = ((last_command_exit_value == EXECUTION_SUCCESS)
+               exec_result = ((exec_result == EXECUTION_SUCCESS)
                                ? EXECUTION_FAILURE
                                : EXECUTION_SUCCESS);
-             else
-               exec_result = last_command_exit_value;
 
-             return (last_command_exit_value = exec_result);
+             last_command_exit_value = exec_result;
+             if (user_subshell && was_error_trap && ignore_return == 0 && invert == 0 && exec_result != EXECUTION_SUCCESS)
+               {
+                 save_line_number = line_number;
+                 line_number = line_number_for_err_trap;
+                 run_error_trap ();
+                 line_number = save_line_number;
+               }
+
+             if (user_subshell && ignore_return == 0 && invert == 0 && exit_immediately_on_error && exec_result != EXECUTION_SUCCESS)
+               {
+                 run_pending_traps ();
+                 jump_to_top_level (ERREXIT);
+               }
+
+             return (last_command_exit_value);
            }
          else
            {
@@ -575,6 +714,9 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out,
 
              run_pending_traps ();
 
+             /* Posix 2013 2.9.3.1: "the exit status of an asynchronous list
+                shall be zero." */
+             last_command_exit_value = 0;
              return (EXECUTION_SUCCESS);
            }
        }
@@ -603,33 +745,66 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out,
   if (shell_control_structure (command->type) && command->redirects)
     stdin_redir = stdin_redirects (command->redirects);
 
+#if defined (PROCESS_SUBSTITUTION)
+#  if !defined (HAVE_DEV_FD)
+  reap_procsubs ();
+#  endif
+
+  if (variable_context != 0)   /* XXX - also if sourcelevel != 0? */
+    {
+      ofifo = num_fifos ();
+      ofifo_list = copy_fifo_list ((int *)&osize);
+      begin_unwind_frame ("internal_fifos");
+      add_unwind_protect (xfree, ofifo_list);
+      saved_fifo = 1;
+    }
+  else
+    saved_fifo = 0;
+#endif
+
   /* Handle WHILE FOR CASE etc. with redirections.  (Also '&' input
      redirection.)  */
   if (do_redirections (command->redirects, RX_ACTIVE|RX_UNDOABLE) != 0)
     {
-      cleanup_redirects (redirection_undo_list);
-      redirection_undo_list = (REDIRECT *)NULL;
+      undo_partial_redirects ();
       dispose_exec_redirects ();
-      return (EXECUTION_FAILURE);
+#if defined (PROCESS_SUBSTITUTION)
+      if (saved_fifo)
+       {
+         free ((void *)ofifo_list);
+          discard_unwind_frame ("internal_fifos");
+       }
+#endif
+      return (last_command_exit_value = EXECUTION_FAILURE);
     }
 
+#if 0
   if (redirection_undo_list)
     {
+      /* XXX - why copy here? */
       my_undo_list = (REDIRECT *)copy_redirects (redirection_undo_list);
-      dispose_redirects (redirection_undo_list);
-      redirection_undo_list = (REDIRECT *)NULL;
+      dispose_partial_redirects ();
     }
   else
     my_undo_list = (REDIRECT *)NULL;
+#else
+  my_undo_list = redirection_undo_list;
+  redirection_undo_list = (REDIRECT *)NULL;
+#endif
 
+#if 0
   if (exec_redirection_undo_list)
     {
+      /* XXX - why copy here? */
       exec_undo_list = (REDIRECT *)copy_redirects (exec_redirection_undo_list);
-      dispose_redirects (exec_redirection_undo_list);
-      exec_redirection_undo_list = (REDIRECT *)NULL;
+      dispose_exec_redirects ();
     }
   else
     exec_undo_list = (REDIRECT *)NULL;
+#else
+  exec_undo_list = exec_redirection_undo_list;
+  exec_redirection_undo_list = (REDIRECT *)NULL;
+#endif
 
   if (my_undo_list || exec_undo_list)
     begin_unwind_frame ("loop_redirections");
@@ -655,7 +830,6 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out,
 #if defined (RECYCLES_PIDS)
        last_made_pid = NO_PID;
 #endif
-       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)
@@ -663,7 +837,7 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out,
        if (command->flags & CMD_STDIN_REDIR)
          command->value.Simple->flags |= CMD_STDIN_REDIR;
 
-       line_number = command->value.Simple->line;
+       line_number_for_err_trap = line_number = command->value.Simple->line;
        exec_result =
          execute_simple_command (command->value.Simple, pipe_in, pipe_out,
                                  asynchronous, fds_to_close);
@@ -683,21 +857,27 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out,
           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_made_pid != last_pid)
+          when the shell is compiled without job control.  Don't worry about
+          whether or not last_made_pid == last_pid; already_making_children
+          tells us whether or not there are unwaited-for children to wait
+          for and reap. */
+       if (already_making_children && pipe_out == NO_PIPE)
          {
            stop_pipeline (asynchronous, (COMMAND *)NULL);
 
            if (asynchronous)
              {
                DESCRIBE_PID (last_made_pid);
+               exec_result = EXECUTION_SUCCESS;
+               invert = 0;             /* async commands always succeed */
              }
            else
 #if !defined (JOB_CONTROL)
              /* Do not wait for asynchronous processes started from
                 startup files. */
-           if (last_made_pid != last_asynchronous_pid)
+           if (last_made_pid != NO_PID && last_made_pid != last_asynchronous_pid)
+#else
+           if (last_made_pid != NO_PID)
 #endif
            /* When executing a shell function that executes other
               commands, this causes the last simple command in
@@ -708,18 +888,34 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out,
          }
       }
 
-      if (was_error_trap && ignore_return == 0 && invert == 0 && exec_result != EXECUTION_SUCCESS)
+      /* 2009/02/13 -- pipeline failure is processed elsewhere.  This handles
+        only the failure of a simple command. We don't want to run the error
+        trap if the command run by the `command' builtin fails; we want to
+        defer that until the command builtin itself returns failure. */
+      if (was_error_trap && ignore_return == 0 && invert == 0 &&
+           pipe_in == NO_PIPE && pipe_out == NO_PIPE &&
+           (command->value.Simple->flags & CMD_COMMAND_BUILTIN) == 0 &&
+           exec_result != EXECUTION_SUCCESS)
        {
          last_command_exit_value = exec_result;
+         line_number = line_number_for_err_trap;
          run_error_trap ();
+         line_number = save_line_number;
        }
 
       if (ignore_return == 0 && invert == 0 &&
          ((posixly_correct && interactive == 0 && special_builtin_failed) ||
-          (exit_immediately_on_error && (exec_result != EXECUTION_SUCCESS))))
+          (exit_immediately_on_error && pipe_in == NO_PIPE && pipe_out == NO_PIPE && exec_result != EXECUTION_SUCCESS)))
        {
          last_command_exit_value = exec_result;
          run_pending_traps ();
+
+         /* Undo redirections before running exit trap on the way out of
+            set -e. Report by Mark Farrell 5/19/2014 */
+         if (exit_immediately_on_error && signal_is_trapped (0) &&
+               unwind_protect_tag_on_stack ("saved-redirects"))
+           run_unwind_frame ("saved-redirects");
+
          jump_to_top_level (ERREXIT);
        }
 
@@ -818,29 +1014,71 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out,
     case cm_connection:
       exec_result = execute_connection (command, asynchronous,
                                        pipe_in, pipe_out, fds_to_close);
+      if (asynchronous)
+       invert = 0;             /* XXX */
+
       break;
 
 #if defined (DPAREN_ARITHMETIC)
     case cm_arith:
+      was_error_trap = signal_is_trapped (ERROR_TRAP) && signal_is_ignored (ERROR_TRAP) == 0;
       if (ignore_return)
        command->value.Arith->flags |= CMD_IGNORE_RETURN;
+      line_number_for_err_trap = save_line_number = line_number;
       exec_result = execute_arith_command (command->value.Arith);
+      line_number = save_line_number;
+
+      if (was_error_trap && ignore_return == 0 && invert == 0 && exec_result != EXECUTION_SUCCESS)
+       {
+         last_command_exit_value = exec_result;
+         save_line_number = line_number;
+         line_number = line_number_for_err_trap;
+         run_error_trap ();
+         line_number = save_line_number;
+       }
+
+      if (ignore_return == 0 && invert == 0 && exit_immediately_on_error && exec_result != EXECUTION_SUCCESS)
+       {
+         last_command_exit_value = exec_result;
+         run_pending_traps ();
+         jump_to_top_level (ERREXIT);
+       }
+
       break;
 #endif
 
 #if defined (COND_COMMAND)
     case cm_cond:
+      was_error_trap = signal_is_trapped (ERROR_TRAP) && signal_is_ignored (ERROR_TRAP) == 0;
       if (ignore_return)
        command->value.Cond->flags |= CMD_IGNORE_RETURN;
-      save_line_number = line_number;
+
+      line_number_for_err_trap = save_line_number = line_number;
       exec_result = execute_cond_command (command->value.Cond);
       line_number = save_line_number;
+
+      if (was_error_trap && ignore_return == 0 && invert == 0 && exec_result != EXECUTION_SUCCESS)
+       {
+         last_command_exit_value = exec_result;
+         save_line_number = line_number;
+         line_number = line_number_for_err_trap;
+         run_error_trap ();
+         line_number = save_line_number;
+       }
+
+      if (ignore_return == 0 && invert == 0 && exit_immediately_on_error && exec_result != EXECUTION_SUCCESS)
+       {
+         last_command_exit_value = exec_result;
+         run_pending_traps ();
+         jump_to_top_level (ERREXIT);
+       }
+
       break;
 #endif
     
     case cm_function_def:
       exec_result = execute_intern_function (command->value.Function_def->name,
-                                            command->value.Function_def->command);
+                                            command->value.Function_def);
       break;
 
     default:
@@ -848,10 +1086,7 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out,
     }
 
   if (my_undo_list)
-    {
-      do_redirections (my_undo_list, RX_ACTIVE);
-      dispose_redirects (my_undo_list);
-    }
+    cleanup_redirects (my_undo_list);
 
   if (exec_undo_list)
     dispose_redirects (exec_undo_list);
@@ -859,18 +1094,50 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out,
   if (my_undo_list || exec_undo_list)
     discard_unwind_frame ("loop_redirections");
 
+#if defined (PROCESS_SUBSTITUTION)
+  if (saved_fifo)
+    {
+      nfifo = num_fifos ();
+      if (nfifo > ofifo)
+       close_new_fifos ((char *)ofifo_list, osize);
+      free ((void *)ofifo_list);
+      discard_unwind_frame ("internal_fifos");
+    }
+#endif
+
   /* Invert the return value if we have to */
   if (invert)
     exec_result = (exec_result == EXECUTION_SUCCESS)
                    ? EXECUTION_FAILURE
                    : EXECUTION_SUCCESS;
 
+#if defined (DPAREN_ARITHMETIC) || defined (COND_COMMAND)
+  /* This is where we set PIPESTATUS from the exit status of the appropriate
+     compound commands (the ones that look enough like simple commands to
+     cause confusion).  We might be able to optimize by not doing this if
+     subshell_environment != 0. */
+  switch (command->type)
+    {
+#  if defined (DPAREN_ARITHMETIC)
+    case cm_arith:
+#  endif
+#  if defined (COND_COMMAND)
+    case cm_cond:
+#  endif
+      set_pipestatus_from_exit (exec_result);
+      break;
+    default:
+      break;
+    }
+#endif
+
   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);
 }
 
@@ -885,7 +1152,7 @@ extern int timeval_to_cpu __P((struct timeval *, struct timeval *, struct timeva
 #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 };
+static const int precs[] = { 0, 100, 10, 1 };
 
 /* Expand one `%'-prefixed escape sequence from a time format string. */
 static int
@@ -931,7 +1198,7 @@ mkfmt (buf, prec, lng, sec, sec_fraction)
      and 999. */
   if (prec != 0)
     {
-      buf[ind++] = '.';
+      buf[ind++] = locale_decpoint ();
       for (aind = 1; aind <= prec; aind++)
        {
          buf[ind++] = (sec_fraction / precs[aind]) + '0';
@@ -1001,8 +1268,11 @@ print_formatted_time (fp, format, rs, rsf, us, usf, ss, ssf, cpu)
       else if (s[1] == 'P')
        {
          s++;
+#if 0
+         /* clamp CPU usage at 100% */
          if (cpu > 10000)
            cpu = 10000;
+#endif
          sum = cpu / 100;
          sum_frac = (cpu % 100) * 10;
          len = mkfmt (ts, 2, 0, sum, sum_frac);
@@ -1056,11 +1326,12 @@ time_command (command, asynchronous, pipe_in, pipe_out, fds_to_close)
      int asynchronous, pipe_in, pipe_out;
      struct fd_bitmap *fds_to_close;
 {
-  int rv, posix_time, old_flags;
+  int rv, posix_time, old_flags, nullcmd, code;
   time_t rs, us, ss;
   int rsf, usf, ssf;
   int cpu;
   char *time_format;
+  volatile procenv_t save_top_level;
 
 #if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY)
   struct timeval real, user, sys;
@@ -1090,12 +1361,32 @@ time_command (command, asynchronous, pipe_in, pipe_out, fds_to_close)
 #  endif
 #endif
 
-  posix_time = (command->flags & CMD_TIME_POSIX);
+  posix_time = command && (command->flags & CMD_TIME_POSIX);
+
+  nullcmd = (command == 0) || (command->type == cm_simple && command->value.Simple->words == 0 && command->value.Simple->redirects == 0);
+  if (posixly_correct && nullcmd)
+    {
+#if defined (HAVE_GETRUSAGE)
+      selfb.ru_utime.tv_sec = kidsb.ru_utime.tv_sec = selfb.ru_stime.tv_sec = kidsb.ru_stime.tv_sec = 0;
+      selfb.ru_utime.tv_usec = kidsb.ru_utime.tv_usec = selfb.ru_stime.tv_usec = kidsb.ru_stime.tv_usec = 0;
+      before.tv_sec = shell_start_time;
+      before.tv_usec = 0;
+#else
+      before.tms_utime = before.tms_stime = before.tms_cutime = before.tms_cstime = 0;
+      tbefore = shell_start_time;
+#endif
+    }
 
   old_flags = command->flags;
+  COPY_PROCENV (top_level, save_top_level);
   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;
+  code = setjmp_nosigs (top_level);
+  if (code == NOT_JUMPED)
+    {
+      rv = execute_command_internal (command, asynchronous, pipe_in, pipe_out, fds_to_close);
+      command->flags = old_flags;
+    }
+  COPY_PROCENV (save_top_level, top_level);
 
   rs = us = ss = 0;
   rsf = usf = ssf = cpu = 0;
@@ -1145,11 +1436,19 @@ time_command (command, asynchronous, pipe_in, pipe_out, fds_to_close)
   if (posix_time)
     time_format = POSIX_TIMEFORMAT;
   else if ((time_format = get_string_value ("TIMEFORMAT")) == 0)
-    time_format = BASH_TIMEFORMAT;
+    {
+      if (posixly_correct && nullcmd)
+       time_format = "user\t%2lU\nsys\t%2lS";
+      else
+       time_format = BASH_TIMEFORMAT;
+    }
 
   if (time_format && *time_format)
     print_formatted_time (stderr, time_format, rs, rsf, us, usf, ss, ssf, cpu);
 
+  if (code)
+    sh_longjmp (top_level, code);
+
   return rv;
 }
 #endif /* COMMAND_TIMING */
@@ -1164,11 +1463,12 @@ execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close)
      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;
+  volatile int user_subshell, user_coproc, invert;
+  int return_code, function_value, should_redir_stdin, ois, result;
+  volatile COMMAND *tcom;
 
   USE_VAR(user_subshell);
+  USE_VAR(user_coproc);
   USE_VAR(invert);
   USE_VAR(tcom);
   USE_VAR(asynchronous);
@@ -1180,6 +1480,7 @@ execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close)
 
   invert = (command->flags & CMD_INVERT_RETURN) != 0;
   user_subshell = command->type == cm_subshell || ((command->flags & CMD_WANT_SUBSHELL) != 0);
+  user_coproc = command->type == cm_coproc;
 
   command->flags &= ~(CMD_FORCE_SUBSHELL | CMD_WANT_SUBSHELL | CMD_INVERT_RETURN);
 
@@ -1187,6 +1488,10 @@ execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close)
      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.
+     XXX - asynchronous used to be set to 0 in this block, but that
+     means that setup_async_signals was never run.  Now it's set to
+     0 after subshell_environment is set appropriately and setup_async_signals
+     is run.
 
      This seems semantically correct to me.  For example,
      ( foo ) & seems to say ``do the command `foo' in a subshell
@@ -1214,46 +1519,95 @@ execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close)
         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;
+  /* And we're no longer in a loop. See Posix interp 842 (we are not in the
+     "same execution environment"). */
+  if (shell_compatibility_level > 44)
+    loop_level = 0;
+
+  if (user_subshell)
+    {
+      subshell_environment = SUBSHELL_PAREN;   /* XXX */
+      if (asynchronous)
+       subshell_environment |= SUBSHELL_ASYNC;
+    }
+  else
+    {
+      subshell_environment = 0;                        /* XXX */
+      if (asynchronous)
+       subshell_environment |= SUBSHELL_ASYNC;
+      if (pipe_in != NO_PIPE || pipe_out != NO_PIPE)
+       subshell_environment |= SUBSHELL_PIPE;
+      if (user_coproc)
+       subshell_environment |= SUBSHELL_COPROC;
+    }
 
   reset_terminating_signals ();                /* in sig.c */
   /* Cancel traps, in trap.c. */
-  restore_original_signals ();
+  /* Reset the signal handlers in the child, but don't free the
+     trap strings.  Set a flag noting that we have to free the
+     trap strings if we run trap to change a signal disposition. */
+  reset_signal_handlers ();
+  subshell_environment |= SUBSHELL_RESETTRAP;
+#if 0  /* TAG:bash-5.1 */
+  /* We are in a subshell, so forget that we are running a trap handler or
+     that the signal handler has changed (we haven't changed it!) */
+  if (running_trap > 0)
+    {
+      run_trap_cleanup (running_trap - 1);
+      running_trap = 0;
+    }
+#endif
+
+  /* Make sure restore_original_signals doesn't undo the work done by
+     make_child to ensure that asynchronous children are immune to SIGINT
+     and SIGQUIT.  Turn off asynchronous to make sure more subshells are
+     not spawned. */
   if (asynchronous)
-    setup_async_signals ();
+    {
+      setup_async_signals ();
+      asynchronous = 0;
+    }
+  else
+    set_sigint_handler ();
 
 #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 defined (COPROCESS_SUPPORT)
+  coproc_closeall ();
+#endif
+
   /* 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. */
+     sh compatibility, but I'm not sure it's the right thing to do.
+     Note that an input pipe to a compound command suffices to inhibit
+     the implicit /dev/null redirection for asynchronous commands
+     executed as part of that compound command. */
   if (user_subshell)
     {
-      stdin_redir = stdin_redirects (command->redirects);
-      restore_default_signal (0);
+      stdin_redir = stdin_redirects (command->redirects) || pipe_in != NO_PIPE;
+#if 0
+      restore_default_signal (EXIT_TRAP);      /* XXX - reset_signal_handlers above */
+#endif
     }
+  else if (shell_control_structure (command->type) && pipe_in != NO_PIPE)
+    stdin_redir = 1;
 
   /* If this is an asynchronous command (command &), we want to
      redirect the standard input from /dev/null in the absence of
@@ -1261,6 +1615,12 @@ execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close)
   if (should_redir_stdin && stdin_redir == 0)
     async_redirect_stdin ();
 
+#if 0
+  /* XXX - TAG:bash-5.1 */
+  if (user_subshell && command->type == cm_subshell)
+    optimize_subshell_command (command->value.Subshell->command);
+#endif
+
   /* Do redirections, then dispose of them before recursive call. */
   if (command->redirects)
     {
@@ -1271,7 +1631,12 @@ execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close)
       command->redirects = (REDIRECT *)NULL;
     }
 
-  tcom = (command->type == cm_subshell) ? command->value.Subshell->command : command;
+  if (command->type == cm_subshell)
+    tcom = command->value.Subshell->command;
+  else if (user_coproc)
+    tcom = command->value.Coproc->command;
+  else
+    tcom = command;
 
   if (command->flags & CMD_TIME_PIPELINE)
     tcom->flags |= CMD_TIME_PIPELINE;
@@ -1287,7 +1652,7 @@ execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close)
      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) &&
+  if ((user_subshell || user_coproc) && (tcom->type == cm_simple || tcom->type == cm_subshell) &&
       ((tcom->flags & CMD_TIME_PIPELINE) == 0) &&
       ((tcom->flags & CMD_INVERT_RETURN) == 0))
     {
@@ -1299,23 +1664,31 @@ execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close)
   invert = (tcom->flags & CMD_INVERT_RETURN) != 0;
   tcom->flags &= ~CMD_INVERT_RETURN;
 
+  result = setjmp_nosigs (top_level);
+
   /* 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)
+    function_value = setjmp_nosigs (return_catch);
+
+  /* If we're going to exit the shell, we don't want to invert the return
+     status. */
+  if (result == EXITPROG)
+    invert = 0, return_code = last_command_exit_value;
+  else if (result)
+    return_code = (last_command_exit_value == EXECUTION_SUCCESS) ? EXECUTION_FAILURE : last_command_exit_value;
+  else if (function_value)
     return_code = return_catch_value;
   else
-    return_code = execute_command_internal
-      (tcom, asynchronous, NO_PIPE, NO_PIPE, fds_to_close);
+    return_code = execute_command_internal ((COMMAND *)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))
@@ -1324,39 +1697,793 @@ execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close)
       return_code = run_exit_trap ();
     }
 
-  subshell_level--;
+#if 0
+  subshell_level--;            /* don't bother, caller will just exit */
+#endif
   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;
+#if defined (COPROCESS_SUPPORT)
+#define COPROC_MAX     16
+
+typedef struct cpelement
+  {
+    struct cpelement *next;
+    struct coproc *coproc;
+  }
+cpelement_t;
+    
+typedef struct cplist
+  {
+    struct cpelement *head;
+    struct cpelement *tail;
+    int ncoproc;
+    int lock;
+  }
+cplist_t;
+
+static struct cpelement *cpe_alloc __P((struct coproc *));
+static void cpe_dispose __P((struct cpelement *));
+static struct cpelement *cpl_add __P((struct coproc *));
+static struct cpelement *cpl_delete __P((pid_t));
+static void cpl_reap __P((void));
+static void cpl_flush __P((void));
+static void cpl_closeall __P((void));
+static struct cpelement *cpl_search __P((pid_t));
+static struct cpelement *cpl_searchbyname __P((const char *));
+static void cpl_prune __P((void));
+
+static void coproc_free __P((struct coproc *));
+
+/* Will go away when there is fully-implemented support for multiple coprocs. */
+Coproc sh_coproc = { 0, NO_PID, -1, -1, 0, 0, 0, 0, 0 };
+
+cplist_t coproc_list = {0, 0, 0};
+
+/* Functions to manage the list of coprocs */
+
+static struct cpelement *
+cpe_alloc (cp)
+     Coproc *cp;
 {
-  int prev, fildes[2], new_bitmap_size, dummyfd, ignore_return, exec_result;
-  COMMAND *cmd;
-  struct fd_bitmap *fd_bitmap;
+  struct cpelement *cpe;
 
-#if defined (JOB_CONTROL)
-  sigset_t set, oset;
-  BLOCK_CHILD (set, oset);
-#endif /* JOB_CONTROL */
+  cpe = (struct cpelement *)xmalloc (sizeof (struct cpelement));
+  cpe->coproc = cp;
+  cpe->next = (struct cpelement *)0;
+  return cpe;
+}
 
-  ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0;
+static void
+cpe_dispose (cpe)
+      struct cpelement *cpe;
+{
+  free (cpe);
+}
 
-  prev = pipe_in;
-  cmd = command;
+static struct cpelement *
+cpl_add (cp)
+     Coproc *cp;
+{
+  struct cpelement *cpe;
 
-  while (cmd && cmd->type == cm_connection &&
-        cmd->value.Connection && cmd->value.Connection->connector == '|')
+  cpe = cpe_alloc (cp);
+
+  if (coproc_list.head == 0)
     {
-      /* Make a pipeline between the two commands. */
-      if (pipe (fildes) < 0)
-       {
-         sys_error ("pipe error");
-#if defined (JOB_CONTROL)
+      coproc_list.head = coproc_list.tail = cpe;
+      coproc_list.ncoproc = 0;                 /* just to make sure */
+    }
+  else
+    {
+      coproc_list.tail->next = cpe;
+      coproc_list.tail = cpe;
+    }
+  coproc_list.ncoproc++;
+
+  return cpe;
+}
+
+static struct cpelement *
+cpl_delete (pid)
+     pid_t pid;
+{
+  struct cpelement *prev, *p;
+
+  for (prev = p = coproc_list.head; p; prev = p, p = p->next)
+    if (p->coproc->c_pid == pid)
+      {
+        prev->next = p->next;  /* remove from list */
+        break;
+      }
+
+  if (p == 0)
+    return 0;          /* not found */
+
+#if defined (DEBUG)
+  itrace("cpl_delete: deleting %d", pid);
+#endif
+
+  /* Housekeeping in the border cases. */
+  if (p == coproc_list.head)
+    coproc_list.head = coproc_list.head->next;
+  else if (p == coproc_list.tail)
+    coproc_list.tail = prev;
+
+  coproc_list.ncoproc--;
+  if (coproc_list.ncoproc == 0)
+    coproc_list.head = coproc_list.tail = 0;
+  else if (coproc_list.ncoproc == 1)
+    coproc_list.tail = coproc_list.head;               /* just to make sure */
+
+  return (p);
+}
+
+static void
+cpl_reap ()
+{
+  struct cpelement *p, *next, *nh, *nt;
+
+  /* Build a new list by removing dead coprocs and fix up the coproc_list
+     pointers when done. */
+  nh = nt = next = (struct cpelement *)0;
+  for (p = coproc_list.head; p; p = next)
+    {
+      next = p->next;
+      if (p->coproc->c_flags & COPROC_DEAD)
+       {
+         coproc_list.ncoproc--;        /* keep running count, fix up pointers later */
+
+#if defined (DEBUG)
+         itrace("cpl_reap: deleting %d", p->coproc->c_pid);
+#endif
+
+         coproc_dispose (p->coproc);
+         cpe_dispose (p);
+       }
+      else if (nh == 0)
+       nh = nt = p;
+      else
+       {
+         nt->next = p;
+         nt = nt->next;
+       }
+    }
+
+  if (coproc_list.ncoproc == 0)
+    coproc_list.head = coproc_list.tail = 0;
+  else
+    {
+      if (nt)
+        nt->next = 0;
+      coproc_list.head = nh;
+      coproc_list.tail = nt;
+      if (coproc_list.ncoproc == 1)
+       coproc_list.tail = coproc_list.head;            /* just to make sure */  
+    }
+}
+
+/* Clear out the list of saved statuses */
+static void
+cpl_flush ()
+{
+  struct cpelement *cpe, *p;
+
+  for (cpe = coproc_list.head; cpe; )
+    {
+      p = cpe;
+      cpe = cpe->next;
+
+      coproc_dispose (p->coproc);
+      cpe_dispose (p);
+    }
+
+  coproc_list.head = coproc_list.tail = 0;
+  coproc_list.ncoproc = 0;
+}
+
+static void
+cpl_closeall ()
+{
+  struct cpelement *cpe;
+
+  for (cpe = coproc_list.head; cpe; cpe = cpe->next)
+    coproc_close (cpe->coproc);
+}
+
+static void
+cpl_fdchk (fd)
+     int fd;
+{
+  struct cpelement *cpe;
+
+  for (cpe = coproc_list.head; cpe; cpe = cpe->next)
+    coproc_checkfd (cpe->coproc, fd);
+}
+
+/* Search for PID in the list of coprocs; return the cpelement struct if
+   found.  If not found, return NULL. */
+static struct cpelement *
+cpl_search (pid)
+     pid_t pid;
+{
+  struct cpelement *cpe;
+
+  for (cpe = coproc_list.head ; cpe; cpe = cpe->next)
+    if (cpe->coproc->c_pid == pid)
+      return cpe;
+  return (struct cpelement *)NULL;
+}
+
+/* Search for the coproc named NAME in the list of coprocs; return the
+   cpelement struct if found.  If not found, return NULL. */
+static struct cpelement *
+cpl_searchbyname (name)
+     const char *name;
+{
+  struct cpelement *cp;
+
+  for (cp = coproc_list.head ; cp; cp = cp->next)
+    if (STREQ (cp->coproc->c_name, name))
+      return cp;
+  return (struct cpelement *)NULL;
+}
+
+static pid_t
+cpl_firstactive ()
+{
+  struct cpelement *cpe;
+
+  for (cpe = coproc_list.head ; cpe; cpe = cpe->next)
+    if ((cpe->coproc->c_flags & COPROC_DEAD) == 0)
+      return cpe->coproc->c_pid;
+  return (pid_t)NO_PID;
+}
+
+#if 0
+static void
+cpl_prune ()
+{
+  struct cpelement *cp;
+
+  while (coproc_list.head && coproc_list.ncoproc > COPROC_MAX)
+    {
+      cp = coproc_list.head;
+      coproc_list.head = coproc_list.head->next;
+      coproc_dispose (cp->coproc);
+      cpe_dispose (cp);
+      coproc_list.ncoproc--;
+    }
+}
+#endif
+
+/* These currently use a single global "shell coproc" but are written in a
+   way to not preclude additional coprocs later (using the list management
+   package above). */
+
+struct coproc *
+getcoprocbypid (pid)
+     pid_t pid;
+{
+#if MULTIPLE_COPROCS
+  struct cpelement *p;
+
+  p = cpl_search (pid);
+  return (p ? p->coproc : 0);
+#else
+  return (pid == sh_coproc.c_pid ? &sh_coproc : 0);
+#endif
+}
+
+struct coproc *
+getcoprocbyname (name)
+     const char *name;
+{
+#if MULTIPLE_COPROCS
+  struct cpelement *p;
+
+  p = cpl_searchbyname (name);
+  return (p ? p->coproc : 0);
+#else
+  return ((sh_coproc.c_name && STREQ (sh_coproc.c_name, name)) ? &sh_coproc : 0);
+#endif
+}
+
+void
+coproc_init (cp)
+     struct coproc *cp;
+{
+  cp->c_name = 0;
+  cp->c_pid = NO_PID;
+  cp->c_rfd = cp->c_wfd = -1;
+  cp->c_rsave = cp->c_wsave = -1;
+  cp->c_flags = cp->c_status = cp->c_lock = 0;
+}
+
+struct coproc *
+coproc_alloc (name, pid)
+     char *name;
+     pid_t pid;
+{
+  struct coproc *cp;
+
+#if MULTIPLE_COPROCS
+  cp = (struct coproc *)xmalloc (sizeof (struct coproc));
+#else
+  cp = &sh_coproc;
+#endif
+  coproc_init (cp);
+  cp->c_lock = 2;
+
+  cp->c_pid = pid;
+  cp->c_name = savestring (name);
+#if MULTIPLE_COPROCS
+  cpl_add (cp);
+#endif
+  cp->c_lock = 0;
+  return (cp);
+}
+
+static void
+coproc_free (cp)
+     struct coproc *cp;
+{
+  free (cp);
+}
+
+void
+coproc_dispose (cp)
+     struct coproc *cp;
+{
+  sigset_t set, oset;
+
+  if (cp == 0)
+    return;
+
+  BLOCK_SIGNAL (SIGCHLD, set, oset);
+  cp->c_lock = 3;
+  coproc_unsetvars (cp);
+  FREE (cp->c_name);
+  coproc_close (cp);
+#if MULTIPLE_COPROCS
+  coproc_free (cp);
+#else
+  coproc_init (cp);
+  cp->c_lock = 0;
+#endif
+  UNBLOCK_SIGNAL (oset);
+}
+
+/* Placeholder for now.  Will require changes for multiple coprocs */
+void
+coproc_flush ()
+{
+#if MULTIPLE_COPROCS
+  cpl_flush ();
+#else
+  coproc_dispose (&sh_coproc);
+#endif
+}
+
+void
+coproc_close (cp)
+     struct coproc *cp;
+{
+  if (cp->c_rfd >= 0)
+    {
+      close (cp->c_rfd);
+      cp->c_rfd = -1;
+    }
+  if (cp->c_wfd >= 0)
+    {
+      close (cp->c_wfd);
+      cp->c_wfd = -1;
+    }
+  cp->c_rsave = cp->c_wsave = -1;
+}
+
+void
+coproc_closeall ()
+{
+#if MULTIPLE_COPROCS
+  cpl_closeall ();
+#else
+  coproc_close (&sh_coproc);   /* XXX - will require changes for multiple coprocs */
+#endif
+}
+
+void
+coproc_reap ()
+{
+#if MULTIPLE_COPROCS
+  cpl_reap ();
+#else
+  struct coproc *cp;
+
+  cp = &sh_coproc;             /* XXX - will require changes for multiple coprocs */
+  if (cp && (cp->c_flags & COPROC_DEAD))
+    coproc_dispose (cp);
+#endif
+}
+
+void
+coproc_rclose (cp, fd)
+     struct coproc *cp;
+     int fd;
+{
+  if (cp->c_rfd >= 0 && cp->c_rfd == fd)
+    {
+      close (cp->c_rfd);
+      cp->c_rfd = -1;
+    }
+}
+
+void
+coproc_wclose (cp, fd)
+     struct coproc *cp;
+     int fd;
+{
+  if (cp->c_wfd >= 0 && cp->c_wfd == fd)
+    {
+      close (cp->c_wfd);
+      cp->c_wfd = -1;
+    }
+}
+
+void
+coproc_checkfd (cp, fd)
+     struct coproc *cp;
+     int fd;
+{
+  int update;
+
+  update = 0;
+  if (cp->c_rfd >= 0 && cp->c_rfd == fd)
+    update = cp->c_rfd = -1;
+  if (cp->c_wfd >= 0 && cp->c_wfd == fd)
+    update = cp->c_wfd = -1;
+  if (update)
+    coproc_setvars (cp);
+}
+
+void
+coproc_fdchk (fd)
+     int fd;
+{
+#if MULTIPLE_COPROCS
+  cpl_fdchk (fd);
+#else
+  coproc_checkfd (&sh_coproc, fd);
+#endif
+}
+
+void
+coproc_fdclose (cp, fd)
+     struct coproc *cp;
+     int fd;
+{
+  coproc_rclose (cp, fd);
+  coproc_wclose (cp, fd);
+  coproc_setvars (cp);
+}
+
+void
+coproc_fdsave (cp)
+     struct coproc *cp;
+{
+  cp->c_rsave = cp->c_rfd;
+  cp->c_wsave = cp->c_wfd;
+}
+
+void
+coproc_fdrestore (cp)
+     struct coproc *cp;
+{
+  cp->c_rfd = cp->c_rsave;
+  cp->c_wfd = cp->c_wsave;
+}
+
+static void
+coproc_setstatus (cp, status)
+     struct coproc *cp;
+     int status;
+{
+  cp->c_lock = 4;
+  cp->c_status = status;
+  cp->c_flags |= COPROC_DEAD;
+  cp->c_flags &= ~COPROC_RUNNING;
+  /* Don't dispose the coproc or unset the COPROC_XXX variables because
+     this is executed in a signal handler context.  Wait until coproc_reap
+     takes care of it. */
+  cp->c_lock = 0;
+}
+
+void
+coproc_pidchk (pid, status)
+     pid_t pid;
+     int status;
+{
+  struct coproc *cp;
+
+#if MULTIPLE_COPROCS
+  struct cpelement *cpe;
+
+  /* We're not disposing the coproc because this is executed in a signal
+     handler context */
+  cpe = cpl_search (pid);
+  cp = cpe ? cpe->coproc : 0;
+#else
+  cp = getcoprocbypid (pid);
+#endif
+  if (cp)
+    coproc_setstatus (cp, status);
+}
+
+pid_t
+coproc_active ()
+{
+#if MULTIPLE_COPROCS
+  return (cpl_firstactive ());
+#else
+  return ((sh_coproc.c_flags & COPROC_DEAD) ? NO_PID : sh_coproc.c_pid);
+#endif
+}
+void
+coproc_setvars (cp)
+     struct coproc *cp;
+{
+  SHELL_VAR *v;
+  char *namevar, *t;
+  int l;
+  WORD_DESC w;
+#if defined (ARRAY_VARS)
+  arrayind_t ind;
+#endif
+
+  if (cp->c_name == 0)
+    return;
+
+  /* We could do more here but right now we only check the name, warn if it's
+     not a valid identifier, and refuse to create variables with invalid names
+     if a coproc with such a name is supplied. */
+  w.word = cp->c_name;
+  w.flags = 0;
+  if (check_identifier (&w, 1) == 0)
+    return;
+
+  l = strlen (cp->c_name);
+  namevar = xmalloc (l + 16);
+
+#if defined (ARRAY_VARS)
+  v = find_variable (cp->c_name);
+
+  /* This is the same code as in find_or_make_array_variable */
+  if (v == 0)
+    {
+      v = find_variable_nameref_for_create (cp->c_name, 1);
+      if (v == INVALID_NAMEREF_VALUE)
+       {
+         free (namevar);
+         return;
+       }
+      if (v && nameref_p (v))
+       {
+         free (cp->c_name);
+         cp->c_name = savestring (nameref_cell (v));
+         v = make_new_array_variable (cp->c_name);       
+       }
+    }
+
+  if (v && (readonly_p (v) || noassign_p (v)))
+    {
+      if (readonly_p (v))
+       err_readonly (cp->c_name);
+      free (namevar);
+      return;
+    }
+  if (v == 0)
+    v = make_new_array_variable (cp->c_name);
+  if (array_p (v) == 0)
+    v = convert_var_to_array (v);
+
+  t = itos (cp->c_rfd);
+  ind = 0;
+  v = bind_array_variable (cp->c_name, ind, t, 0);
+  free (t);
+
+  t = itos (cp->c_wfd);
+  ind = 1;
+  v = bind_array_variable (cp->c_name, ind, t, 0);
+  free (t);
+#else
+  sprintf (namevar, "%s_READ", cp->c_name);
+  t = itos (cp->c_rfd);
+  bind_variable (namevar, t, 0);
+  free (t);
+  sprintf (namevar, "%s_WRITE", cp->c_name);
+  t = itos (cp->c_wfd);
+  bind_variable (namevar, t, 0);
+  free (t);
+#endif
+
+  sprintf (namevar, "%s_PID", cp->c_name);
+  t = itos (cp->c_pid);
+  v = bind_variable (namevar, t, 0);
+  free (t);
+
+  free (namevar);
+}
+
+void
+coproc_unsetvars (cp)
+     struct coproc *cp;
+{
+  int l;
+  char *namevar;
+
+  if (cp->c_name == 0)
+    return;
+
+  l = strlen (cp->c_name);
+  namevar = xmalloc (l + 16);
+
+  sprintf (namevar, "%s_PID", cp->c_name);
+  unbind_variable_noref (namevar);  
+
+#if defined (ARRAY_VARS)
+  check_unbind_variable (cp->c_name);
+#else
+  sprintf (namevar, "%s_READ", cp->c_name);
+  unbind_variable (namevar);
+  sprintf (namevar, "%s_WRITE", cp->c_name);
+  unbind_variable (namevar);
+#endif  
+
+  free (namevar);
+}
+
+static int
+execute_coproc (command, pipe_in, pipe_out, fds_to_close)
+     COMMAND *command;
+     int pipe_in, pipe_out;
+     struct fd_bitmap *fds_to_close;
+{
+  int rpipe[2], wpipe[2], estat, invert;
+  pid_t coproc_pid;
+  Coproc *cp;
+  char *tcmd, *p, *name;
+  sigset_t set, oset;
+
+  /* XXX -- can be removed after changes to handle multiple coprocs */
+#if !MULTIPLE_COPROCS
+  if (sh_coproc.c_pid != NO_PID && (sh_coproc.c_rfd >= 0 || sh_coproc.c_wfd >= 0))
+    internal_warning (_("execute_coproc: coproc [%d:%s] still exists"), sh_coproc.c_pid, sh_coproc.c_name);
+  coproc_init (&sh_coproc);
+#endif
+
+  invert = (command->flags & CMD_INVERT_RETURN) != 0;
+
+  /* expand name without splitting - could make this dependent on a shopt option */
+  name = expand_string_unsplit_to_string (command->value.Coproc->name, 0);
+  /* Optional check -- could be relaxed */
+  if (legal_identifier (name) == 0)
+    {
+      internal_error (_("`%s': not a valid identifier"), name);
+      return (invert ? EXECUTION_SUCCESS : EXECUTION_FAILURE);
+    }
+  else
+    {
+      free (command->value.Coproc->name);
+      command->value.Coproc->name = name;
+    }
+
+  command_string_index = 0;
+  tcmd = make_command_string (command);
+
+  sh_openpipe ((int *)&rpipe); /* 0 = parent read, 1 = child write */
+  sh_openpipe ((int *)&wpipe); /* 0 = child read, 1 = parent write */
+
+  BLOCK_SIGNAL (SIGCHLD, set, oset);
+
+  coproc_pid = make_child (p = savestring (tcmd), 1);
+
+  if (coproc_pid == 0)
+    {
+      close (rpipe[0]);
+      close (wpipe[1]);
+
+#if defined (JOB_CONTROL)
+      FREE (p);
+#endif
+
+      UNBLOCK_SIGNAL (oset);
+      estat = execute_in_subshell (command, 1, wpipe[0], rpipe[1], fds_to_close);
+
+      fflush (stdout);
+      fflush (stderr);
+
+      exit (estat);
+    }
+
+  close (rpipe[1]);
+  close (wpipe[0]);
+
+  cp = coproc_alloc (command->value.Coproc->name, coproc_pid);
+  cp->c_rfd = rpipe[0];
+  cp->c_wfd = wpipe[1];
+
+  cp->c_flags |= COPROC_RUNNING;
+
+  SET_CLOSE_ON_EXEC (cp->c_rfd);
+  SET_CLOSE_ON_EXEC (cp->c_wfd);
+
+  coproc_setvars (cp);
+
+  UNBLOCK_SIGNAL (oset);
+
+#if 0
+  itrace ("execute_coproc (%s): [%d] %s", command->value.Coproc->name, coproc_pid, the_printed_command);
+#endif
+
+  close_pipes (pipe_in, pipe_out);
+#if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD)
+  unlink_fifo_list ();
+#endif
+  stop_pipeline (1, (COMMAND *)NULL);
+  DESCRIBE_PID (coproc_pid);
+  run_pending_traps ();
+
+  return (invert ? EXECUTION_FAILURE : EXECUTION_SUCCESS);
+}
+#endif
+
+static void
+restore_stdin (s)
+     int s;
+{
+  dup2 (s, 0);
+  close (s);
+}
+
+/* Catch-all cleanup function for lastpipe code for unwind-protects */
+static void
+lastpipe_cleanup (s)
+     int s;
+{
+  set_jobs_list_frozen (s);
+}
+
+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;
+  int lstdin, lastpipe_flag, lastpipe_jid, old_frozen;
+  COMMAND *cmd;
+  struct fd_bitmap *fd_bitmap;
+  pid_t lastpid;
+
+#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 ();
          UNBLOCK_CHILD (oset);
@@ -1427,11 +2554,43 @@ execute_pipeline (command, asynchronous, pipe_in, pipe_out, fds_to_close)
       cmd = cmd->value.Connection->second;
     }
 
+  lastpid = last_made_pid;
+
   /* Now execute the rightmost command in the pipeline.  */
   if (ignore_return && cmd)
     cmd->flags |= CMD_IGNORE_RETURN;
+
+  lastpipe_flag = 0;
+
+  begin_unwind_frame ("lastpipe-exec");
+  lstdin = -1;
+  /* If the `lastpipe' option is set with shopt, and job control is not
+     enabled, execute the last element of non-async pipelines in the
+     current shell environment. */
+  if (lastpipe_opt && job_control == 0 && asynchronous == 0 && pipe_out == NO_PIPE && prev > 0)
+    {
+      lstdin = move_to_high_fd (0, 1, -1);
+      if (lstdin > 0)
+       {
+         do_piping (prev, pipe_out);
+         prev = NO_PIPE;
+         add_unwind_protect (restore_stdin, lstdin);
+         lastpipe_flag = 1;
+         old_frozen = freeze_jobs_list ();
+         lastpipe_jid = stop_pipeline (0, (COMMAND *)NULL);    /* XXX */
+         add_unwind_protect (lastpipe_cleanup, old_frozen);
+       }
+      if (cmd)
+       cmd->flags |= CMD_LASTPIPE;
+    }    
+  if (prev >= 0)
+    add_unwind_protect (close, prev);
+
   exec_result = execute_command_internal (cmd, asynchronous, prev, pipe_out, fds_to_close);
 
+  if (lstdin > 0)
+    restore_stdin (lstdin);
+
   if (prev >= 0)
     close (prev);
 
@@ -1439,6 +2598,39 @@ execute_pipeline (command, asynchronous, pipe_in, pipe_out, fds_to_close)
   UNBLOCK_CHILD (oset);
 #endif
 
+  QUIT;
+
+  if (lastpipe_flag)
+    {
+#if defined (JOB_CONTROL)
+      if (INVALID_JOB (lastpipe_jid) == 0)
+        {
+          append_process (savestring (the_printed_command_except_trap), dollar_dollar_pid, exec_result, lastpipe_jid);
+          lstdin = wait_for (lastpid);
+        }
+      else
+        lstdin = wait_for_single_pid (lastpid, 0);             /* checks bgpids list */
+#else
+      lstdin = wait_for (lastpid);
+#endif
+
+#if defined (JOB_CONTROL)
+      /* If wait_for removes the job from the jobs table, use result of last
+        command as pipeline's exit status as usual.  The jobs list can get
+        frozen and unfrozen at inconvenient times if there are multiple pipelines
+        running simultaneously. */
+      if (INVALID_JOB (lastpipe_jid) == 0)
+       exec_result = job_exit_status (lastpipe_jid);
+      else if (pipefail_opt)
+       exec_result = exec_result | lstdin;     /* XXX */
+      /* otherwise we use exec_result */
+#endif
+
+      set_jobs_list_frozen (old_frozen);
+    }
+
+  discard_unwind_frame ("lastpipe-exec");
+
   return (exec_result);
 }
 
@@ -1448,9 +2640,9 @@ execute_connection (command, asynchronous, pipe_in, pipe_out, fds_to_close)
      int asynchronous, pipe_in, pipe_out;
      struct fd_bitmap *fds_to_close;
 {
-  REDIRECT *rp;
   COMMAND *tc, *second;
-  int ignore_return, exec_result;
+  int ignore_return, exec_result, was_error_trap, invert;
+  volatile int save_line_number;
 
   ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0;
 
@@ -1462,8 +2654,6 @@ execute_connection (command, asynchronous, pipe_in, pipe_out, fds_to_close)
       if (tc == 0)
        return (EXECUTION_SUCCESS);
 
-      rp = tc->redirects;
-
       if (ignore_return)
        tc->flags |= CMD_IGNORE_RETURN;
       tc->flags |= CMD_AMPERSAND;
@@ -1480,6 +2670,7 @@ execute_connection (command, asynchronous, pipe_in, pipe_out, fds_to_close)
        tc->flags |= CMD_STDIN_REDIR;
 
       exec_result = execute_command_internal (tc, 1, pipe_in, pipe_out, fds_to_close);
+      QUIT;
 
       if (tc->flags & CMD_STDIN_REDIR)
        tc->flags &= ~CMD_STDIN_REDIR;
@@ -1504,16 +2695,46 @@ execute_connection (command, asynchronous, pipe_in, pipe_out, fds_to_close)
          if (command->value.Connection->second)
            command->value.Connection->second->flags |= CMD_IGNORE_RETURN;
        }
+      executing_list++;
       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);
+      executing_list--;
       break;
 
     case '|':
+      was_error_trap = signal_is_trapped (ERROR_TRAP) && signal_is_ignored (ERROR_TRAP) == 0;
+      invert = (command->flags & CMD_INVERT_RETURN) != 0;
+      ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0;
+
+      line_number_for_err_trap = line_number;  /* XXX - save value? */
       exec_result = execute_pipeline (command, asynchronous, pipe_in, pipe_out, fds_to_close);
+
+      if (asynchronous)
+       {
+         exec_result = EXECUTION_SUCCESS;
+         invert = 0;
+       }
+
+      if (was_error_trap && ignore_return == 0 && invert == 0 && exec_result != EXECUTION_SUCCESS)
+       {
+         last_command_exit_value = exec_result;
+         save_line_number = line_number;
+         line_number = line_number_for_err_trap;
+         run_error_trap ();
+         line_number = save_line_number;
+       }
+
+      if (ignore_return == 0 && invert == 0 && exit_immediately_on_error && exec_result != EXECUTION_SUCCESS)
+       {
+         last_command_exit_value = exec_result;
+         run_pending_traps ();
+         jump_to_top_level (ERREXIT);
+       }
+
       break;
 
     case AND_AND:
@@ -1535,6 +2756,7 @@ execute_connection (command, asynchronous, pipe_in, pipe_out, fds_to_close)
         and the connector is OR_OR, then execute the second command,
         otherwise return. */
 
+      executing_list++;
       if (command->value.Connection->first)
        command->value.Connection->first->flags |= CMD_IGNORE_RETURN;
 
@@ -1545,11 +2767,13 @@ execute_connection (command, asynchronous, pipe_in, pipe_out, fds_to_close)
          ((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;
+         second = command->value.Connection->second;
+         if (ignore_return && second)
+           second->flags |= CMD_IGNORE_RETURN;
 
-         exec_result = execute_command (command->value.Connection->second);
+         exec_result = execute_command (second);
        }
+      executing_list--;
       break;
 
     default:
@@ -1561,10 +2785,13 @@ execute_connection (command, asynchronous, pipe_in, pipe_out, fds_to_close)
   return exec_result;
 }
 
+/* The test used to be only for interactive_shell, but we don't want to report
+   job status when the shell is not interactive or when job control isn't
+   enabled. */
 #define REAP() \
   do \
     { \
-      if (!interactive_shell) \
+      if (job_control == 0 || interactive_shell == 0) \
        reap_dead_jobs (); \
     } \
   while (0)
@@ -1588,7 +2815,7 @@ execute_for_command (for_command)
     {
       if (posixly_correct && interactive_shell == 0)
        {
-         last_command_exit_value = EX_USAGE;
+         last_command_exit_value = EX_BADUSAGE;
          jump_to_top_level (ERREXIT);
        }
       return (EXECUTION_FAILURE);
@@ -1597,6 +2824,7 @@ execute_for_command (for_command)
   loop_level++;
   identifier = for_command->name->word;
 
+  line_number = for_command->line;     /* for expansion error messages */
   list = releaser = expand_words_no_vars (for_command->map_list);
 
   begin_unwind_frame ("for");
@@ -1629,7 +2857,7 @@ execute_for_command (for_command)
 
       /* Save this command unless it's a trap command and we're not running
         a debug trap. */
-      if (signal_in_progress (DEBUG_TRAP) == 0 && (this_command_name == 0 || (STREQ (this_command_name, "trap") == 0)))
+      if (signal_in_progress (DEBUG_TRAP) == 0 && running_trap == 0)
        {
          FREE (the_printed_command_except_trap);
          the_printed_command_except_trap = savestring (the_printed_command);
@@ -1644,11 +2872,27 @@ execute_for_command (for_command)
 #endif
 
       this_command_name = (char *)NULL;
-      v = bind_variable (identifier, list->word->word, 0);
-      if (readonly_p (v) || noassign_p (v))
+      /* XXX - special ksh93 for command index variable handling */
+      v = find_variable_last_nameref (identifier, 1);
+      if (v && nameref_p (v))
+       {
+         if (valid_nameref_value (list->word->word, 1) == 0)
+           {
+             sh_invalidid (list->word->word);
+             v = 0;
+           }
+         else if (readonly_p (v))
+           err_readonly (name_cell (v));
+         else
+           v = bind_variable_value (v, list->word->word, ASS_NAMEREF);
+       }
+      else
+       v = bind_variable (identifier, list->word->word, 0);
+
+      if (v == 0 || readonly_p (v) || noassign_p (v))
        {
          line_number = save_line_number;
-         if (readonly_p (v) && interactive_shell == 0 && posixly_correct)
+         if (v && readonly_p (v) && interactive_shell == 0 && posixly_correct)
            {
              last_command_exit_value = EXECUTION_FAILURE;
              jump_to_top_level (FORCE_EOF);
@@ -1661,6 +2905,10 @@ execute_for_command (for_command)
              return (EXECUTION_FAILURE);
            }
        }
+
+      if (ifsname (identifier))
+       setifs (v);
+
       retval = execute_command (for_command->action);
       REAP ();
       QUIT;
@@ -1691,7 +2939,7 @@ execute_for_command (for_command)
        {
          SHELL_VAR *new_value;
 
-         new_value = bind_variable (identifier, value_cell(old_value), 0);
+         new_value = bind_variable (identifier, value_cell (old_value), 0);
          new_value->attributes = old_value->attributes;
          dispose_variable (old_value);
        }
@@ -1737,7 +2985,7 @@ eval_arith_for_expr (l, okp)
 
       command_string_index = 0;
       print_arith_command (new);
-      if (signal_in_progress (DEBUG_TRAP) == 0)
+      if (signal_in_progress (DEBUG_TRAP) == 0 && running_trap == 0)
        {
          FREE (the_printed_command_except_trap);
          the_printed_command_except_trap = savestring (the_printed_command);
@@ -1748,7 +2996,7 @@ eval_arith_for_expr (l, okp)
         skip the command. */
 #if defined (DEBUGGER)
       if (debugging_mode == 0 || r == EXECUTION_SUCCESS)
-       expresult = evalexp (new->word->word, okp);
+       expresult = evalexp (new->word->word, EXP_EXPANDED, okp);
       else
        {
          expresult = 0;
@@ -1756,7 +3004,7 @@ eval_arith_for_expr (l, okp)
            *okp = 1;
        }
 #else
-      expresult = evalexp (new->word->word, okp);
+      expresult = evalexp (new->word->word, EXP_EXPANDED, okp);
 #endif
       dispose_words (new);
     }
@@ -1789,8 +3037,13 @@ execute_arith_for_command (arith_for_command)
      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;
+  if (variable_context && interactive_shell && sourcelevel == 0)
+    {
+      /* line numbers in a function start at 1 */
+      line_number -= function_line_number - 1;
+      if (line_number <= 0)
+       line_number = 1;
+    }
 
   /* Evaluate the initialization expression. */
   expresult = eval_arith_for_expr (arith_for_command->init, &expok);
@@ -1869,6 +3122,29 @@ static int LINES, COLS, tabsize;
                                                 : ((s < 100000) ? 5 \
                                                                : 6)))))
 
+static int
+displen (s)
+     const char *s;
+{
+#if defined (HANDLE_MULTIBYTE)
+  wchar_t *wcstr;
+  size_t slen;
+  int wclen;
+
+  wcstr = 0;
+  slen = mbstowcs (wcstr, s, 0);
+  if (slen == -1)
+    slen = 0;
+  wcstr = (wchar_t *)xmalloc (sizeof (wchar_t) * (slen + 1));
+  mbstowcs (wcstr, s, slen + 1);
+  wclen = wcswidth (wcstr, slen);
+  free (wcstr);
+  return (wclen < 0 ? STRLEN(s) : wclen);
+#else
+  return (STRLEN (s));
+#endif
+}
+
 static int
 print_index_and_element (len, ind, list)
       int len, ind;
@@ -1881,8 +3157,10 @@ print_index_and_element (len, ind, list)
     return (0);
   for (i = ind, l = list; l && --i; l = l->next)
     ;
+  if (l == 0)          /* don't think this can happen */
+    return (0);
   fprintf (stderr, "%*d%s%s", len, ind, RP_SPACE, l->word->word);
-  return (STRLEN (l->word->word));
+  return (displen (l->word->word));
 }
 
 static void
@@ -1969,10 +3247,7 @@ select_query (list, list_len, prompt, print_menu)
   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;
+  COLS = default_columns ();
 
 #if 0
   t = get_string_value ("TABSIZE");
@@ -1986,7 +3261,7 @@ select_query (list, list_len, prompt, print_menu)
   max_elem_len = 0;
   for (l = list; l; l = l->next)
     {
-      len = STRLEN (l->word->word);
+      len = displen (l->word->word);
       if (len > max_elem_len)
        max_elem_len = len;
     }
@@ -2001,12 +3276,14 @@ select_query (list, list_len, prompt, print_menu)
       fflush (stderr);
       QUIT;
 
-      if (read_builtin ((WORD_LIST *)NULL) == EXECUTION_FAILURE)
+      if (read_builtin ((WORD_LIST *)NULL) != EXECUTION_SUCCESS)
        {
          putchar ('\n');
          return ((char *)NULL);
        }
       repl_string = get_string_value ("REPLY");
+      if (repl_string == 0)
+       return ((char *)NULL);
       if (*repl_string == 0)
        {
          print_menu = 1;
@@ -2019,7 +3296,7 @@ select_query (list, list_len, prompt, print_menu)
 
       for (l = list; l && --reply; l = l->next)
        ;
-      return (l->word->word);
+      return (l->word->word);          /* XXX - can't be null? */
     }
 }
 
@@ -2048,7 +3325,11 @@ execute_select_command (select_command)
   if (echo_command_at_execute)
     xtrace_print_select_command_head (select_command);
 
+#if 0
   if (signal_in_progress (DEBUG_TRAP) == 0 && (this_command_name == 0 || (STREQ (this_command_name, "trap") == 0)))
+#else
+  if (signal_in_progress (DEBUG_TRAP) == 0 && running_trap == 0)
+#endif
     {
       FREE (the_printed_command_except_trap);
       the_printed_command_except_trap = savestring (the_printed_command);
@@ -2105,9 +3386,9 @@ execute_select_command (select_command)
        }
 
       v = bind_variable (identifier, selection, 0);
-      if (readonly_p (v) || noassign_p (v))
+      if (v == 0 || readonly_p (v) || noassign_p (v))
        {
-         if (readonly_p (v) && interactive_shell == 0 && posixly_correct)
+         if (v && readonly_p (v) && interactive_shell == 0 && posixly_correct)
            {
              last_command_exit_value = EXECUTION_FAILURE;
              jump_to_top_level (FORCE_EOF);
@@ -2169,7 +3450,7 @@ execute_case_command (case_command)
   WORD_LIST *wlist, *es;
   PATTERN_LIST *clauses;
   char *word, *pattern;
-  int retval, match, ignore_return, save_line_number;
+  int retval, match, ignore_return, save_line_number, qflags;
 
   save_line_number = line_number;
   line_number = case_command->line;
@@ -2180,7 +3461,11 @@ execute_case_command (case_command)
   if (echo_command_at_execute)
     xtrace_print_case_command_head (case_command);
 
-  if (signal_in_progress (DEBUG_TRAP == 0) && (this_command_name == 0 || (STREQ (this_command_name, "trap") == 0)))
+#if 0
+  if (signal_in_progress (DEBUG_TRAP) == 0 && (this_command_name == 0 || (STREQ (this_command_name, "trap") == 0)))
+#else
+  if (signal_in_progress (DEBUG_TRAP) == 0 && running_trap == 0)
+#endif
     {
       FREE (the_printed_command_except_trap);
       the_printed_command_except_trap = savestring (the_printed_command);
@@ -2197,15 +3482,27 @@ execute_case_command (case_command)
     }
 #endif
 
-  wlist = expand_word_unsplit (case_command->word, 0);
-  word = wlist ? string_list (wlist) : savestring ("");
+  /* Use the same expansions (the ones POSIX specifies) as the patterns;
+     dequote the resulting string (as POSIX specifies) since the quotes in
+     patterns are handled specially below. We have to do it in this order
+     because we're not supposed to perform word splitting. */
+  wlist = expand_word_leave_quoted (case_command->word, 0);
+  if (wlist)
+    {
+      char *t;
+      t = string_list (wlist);
+      word = dequote_string (t);
+      free (t);
+    }
+  else
+    word = 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);
+  add_unwind_protect (xfree, word);
 
 #define EXIT_CASE()  goto exit_case_command
 
@@ -2217,7 +3514,18 @@ execute_case_command (case_command)
          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);
+           {
+             /* Convert quoted null strings into empty strings. */
+             qflags = QGLOB_CVTNULL;
+
+             /* We left CTLESC in place quoting CTLESC and CTLNUL after the
+                call to expand_word_leave_quoted; tell quote_string_for_globbing
+                to remove those here. This works for both unquoted portions of
+                the word (which call quote_escapes) and quoted portions
+                (which call quote_string). */
+             qflags |= QGLOB_CTLESC;
+             pattern = quote_string_for_globbing (es->word->word, qflags);
+           }
          else
            {
              pattern = (char *)xmalloc (1);
@@ -2234,10 +3542,17 @@ execute_case_command (case_command)
 
          if (match)
            {
-             if (clauses->action && ignore_return)
-               clauses->action->flags |= CMD_IGNORE_RETURN;
-             retval = execute_command (clauses->action);
-             EXIT_CASE ();
+             do
+               {
+                 if (clauses->action && ignore_return)
+                   clauses->action->flags |= CMD_IGNORE_RETURN;
+                 retval = execute_command (clauses->action);
+               }
+             while ((clauses->flags & CASEPAT_FALLTHROUGH) && (clauses = clauses->next));
+             if (clauses == 0 || (clauses->flags & CASEPAT_TESTNEXT) == 0)
+               EXIT_CASE ();
+             else
+               break;
            }
 
          QUIT;
@@ -2300,17 +3615,22 @@ execute_while_or_until (while_command, type)
          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. */
+         this, and the shell will cease to execute commands.  The same holds
+         true for `continue'. */
       if (type == CMD_WHILE && return_value != EXECUTION_SUCCESS)
        {
          if (breaking)
            breaking--;
+         if (continuing)
+           continuing--;
          break;
        }
       if (type == CMD_UNTIL && return_value == EXECUTION_SUCCESS)
        {
          if (breaking)
            breaking--;
+         if (continuing)
+           continuing--;
          break;
        }
 
@@ -2378,20 +3698,26 @@ execute_arith_command (arith_command)
   int expok, save_line_number, retval;
   intmax_t expresult;
   WORD_LIST *new;
+  char *exp;
 
   expresult = 0;
 
   save_line_number = line_number;
   this_command_name = "((";    /* )) */
-  line_number = arith_command->line;
+  line_number_for_err_trap = 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;
+  if (variable_context && interactive_shell && sourcelevel == 0)
+    {
+      /* line numbers in a function start at 1 */
+      line_number -= function_line_number - 1;
+      if (line_number <= 0)
+       line_number = 1;
+    }      
 
   command_string_index = 0;
   print_arith_command (arith_command->exp);
 
-  if (signal_in_progress (DEBUG_TRAP) == 0)
+  if (signal_in_progress (DEBUG_TRAP) == 0 && running_trap == 0)
     {
       FREE (the_printed_command_except_trap);
       the_printed_command_except_trap = savestring (the_printed_command);
@@ -2420,8 +3746,11 @@ execute_arith_command (arith_command)
 
   if (new)
     {
-      expresult = evalexp (new->word->word, &expok);
+      exp = new->next ? string_list (new) : new->word->word;
+      expresult = evalexp (exp, EXP_EXPANDED, &expok);
       line_number = save_line_number;
+      if (exp != new->word->word)
+       free (exp);
       dispose_words (new);
     }
   else
@@ -2439,17 +3768,29 @@ execute_arith_command (arith_command)
 
 #if defined (COND_COMMAND)
 
-static char *nullstr = "";
+static char * const nullstr = "";
 
+/* XXX - can COND ever be NULL when this is called? */
 static int
 execute_cond_node (cond)
      COND_COM *cond;
 {
-  int result, invert, patmatch, rmatch, mflags;
+  int result, invert, patmatch, rmatch, mflags, ignore;
   char *arg1, *arg2;
+#if 0
+  char *t1, *t2;
+#endif
 
   invert = (cond->flags & CMD_INVERT_RETURN);
-
+  ignore = (cond->flags & CMD_IGNORE_RETURN);
+  if (ignore)
+    {
+      if (cond->left)
+       cond->left->flags |= CMD_IGNORE_RETURN;
+      if (cond->right)
+       cond->right->flags |= CMD_IGNORE_RETURN;
+    }
+      
   if (cond->type == COND_EXPR)
     result = execute_cond_node (cond->left);
   else if (cond->type == COND_OR)
@@ -2466,7 +3807,11 @@ execute_cond_node (cond)
     }
   else if (cond->type == COND_UNARY)
     {
+      if (ignore)
+       comsub_ignore_return++;
       arg1 = cond_expand_word (cond->left->op, 0);
+      if (ignore)
+       comsub_ignore_return--;
       if (arg1 == 0)
        arg1 = nullstr;
       if (echo_command_at_execute)
@@ -2477,18 +3822,28 @@ execute_cond_node (cond)
     }
   else if (cond->type == COND_BINARY)
     {
-      patmatch = ((cond->op->word[1] == '=') && (cond->op->word[2] == '\0') &&
-                 (cond->op->word[0] == '!' || cond->op->word[0] == '=') ||
+      rmatch = 0;
+      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
 
+      if (ignore)
+       comsub_ignore_return++;
       arg1 = cond_expand_word (cond->left->op, 0);
+      if (ignore)
+       comsub_ignore_return--;
       if (arg1 == 0)
        arg1 = nullstr;
-      arg2 = cond_expand_word (cond->right->op, patmatch);
+      if (ignore)
+       comsub_ignore_return++;
+      arg2 = cond_expand_word (cond->right->op,
+                              (rmatch && shell_compatibility_level > 31) ? 2 : (patmatch ? 1 : 0));
+      if (ignore)
+       comsub_ignore_return--;
       if (arg2 == 0)
        arg2 = nullstr;
 
@@ -2503,6 +3858,14 @@ execute_cond_node (cond)
          mflags |= SHMAT_SUBEXP;
 #endif
 
+#if 0
+         t1 = strescape(arg1);
+         t2 = strescape(arg2);
+         itrace("execute_cond_node: sh_regmatch on `%s' and `%s'", t1, t2);
+         free(t1);
+         free(t2);
+#endif
+
          result = sh_regmatch (arg1, arg2, mflags);
        }
       else
@@ -2511,7 +3874,7 @@ execute_cond_node (cond)
          int oe;
          oe = extended_glob;
          extended_glob = 1;
-         result = binary_test (cond->op->word, arg1, arg2, TEST_PATMATCH|TEST_ARITHEXP)
+         result = binary_test (cond->op->word, arg1, arg2, TEST_PATMATCH|TEST_ARITHEXP|TEST_LOCALE)
                                  ? EXECUTION_SUCCESS
                                  : EXECUTION_FAILURE;
          extended_glob = oe;
@@ -2540,19 +3903,22 @@ execute_cond_command (cond_command)
 {
   int retval, save_line_number;
 
-  retval = EXECUTION_SUCCESS;
   save_line_number = line_number;
 
   this_command_name = "[[";
-  line_number = cond_command->line;
+  line_number_for_err_trap = 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;
-
+  if (variable_context && interactive_shell && sourcelevel == 0)
+    {
+      /* line numbers in a function start at 1 */
+      line_number -= function_line_number - 1;
+      if (line_number <= 0)
+       line_number = 1;
+    }
   command_string_index = 0;
   print_cond_command (cond_command);
 
-  if (signal_in_progress (DEBUG_TRAP) == 0)
+  if (signal_in_progress (DEBUG_TRAP) == 0 && running_trap == 0)
     {
       FREE (the_printed_command_except_trap);
       the_printed_command_except_trap = savestring (the_printed_command);
@@ -2590,7 +3956,8 @@ bind_lastarg (arg)
   if (arg == 0)
     arg = "";
   var = bind_variable ("_", arg, 0);
-  VUNSETATTR (var, att_exported);
+  if (var)
+    VUNSETATTR (var, att_exported);
 }
 
 /* Execute a null command.  Fork a subshell if the command uses pipes or is
@@ -2602,8 +3969,17 @@ execute_null_command (redirects, pipe_in, pipe_out, async)
      int pipe_in, pipe_out, async;
 {
   int r;
+  int forcefork;
+  REDIRECT *rd;
+
+  for (forcefork = 0, rd = redirects; rd; rd = rd->next)
+    {
+      forcefork += rd->rflags & REDIR_VARASSIGN;
+      /* Safety */
+      forcefork += (rd->redirector.dest == 0 || fd_is_bash_input (rd->redirector.dest)) && (INPUT_REDIRECT (rd->instruction) || TRANSLATE_REDIRECT (rd->instruction) || rd->instruction == r_close_this);
+    }
 
-  if (pipe_in != NO_PIPE || pipe_out != NO_PIPE || async)
+  if (forcefork || 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. */
@@ -2614,7 +3990,17 @@ execute_null_command (redirects, pipe_in, pipe_out, async)
 
          do_piping (pipe_in, pipe_out);
 
-         subshell_environment = SUBSHELL_ASYNC;
+#if defined (COPROCESS_SUPPORT)
+         coproc_closeall ();
+#endif
+
+         interactive = 0;                      /* XXX */
+
+         subshell_environment = 0;
+         if (async)
+           subshell_environment |= SUBSHELL_ASYNC;
+         if (pipe_in != NO_PIPE || pipe_out != NO_PIPE)
+           subshell_environment |= SUBSHELL_PIPE;
 
          if (do_redirections (redirects, RX_ACTIVE) == 0)
            exit (EXECUTION_SUCCESS);
@@ -2625,7 +4011,8 @@ execute_null_command (redirects, pipe_in, pipe_out, async)
        {
          close_pipes (pipe_in, pipe_out);
 #if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD)
-         unlink_fifo_list ();
+         if (pipe_out == NO_PIPE)
+           unlink_fifo_list ();
 #endif
          return (EXECUTION_SUCCESS);
        }
@@ -2658,29 +4045,102 @@ static void
 fix_assignment_words (words)
      WORD_LIST *words;
 {
-  WORD_LIST *w;
+  WORD_LIST *w, *wcmd;
   struct builtin *b;
+  int assoc, global, array, integer;
 
   if (words == 0)
     return;
 
   b = 0;
+  assoc = global = array = integer = 0;
 
-  for (w = words; w; w = w->next)
+  /* Skip over assignment statements preceding a command name */
+  wcmd = words;
+  for (wcmd = words; wcmd; wcmd = wcmd->next)
+    if ((wcmd->word->flags & W_ASSIGNMENT) == 0)
+      break;
+  /* Posix (post-2008) says that `command' doesn't change whether
+     or not the builtin it shadows is a `declaration command', even
+     though it removes other special builtin properties.  In Posix
+     mode, we skip over one or more instances of `command' and
+     deal with the next word as the assignment builtin. */
+  while (posixly_correct && wcmd && wcmd->word && wcmd->word->word && STREQ (wcmd->word->word, "command"))
+    wcmd = wcmd->next;
+
+  for (w = wcmd; w; w = w->next)
     if (w->word->flags & W_ASSIGNMENT)
       {
+       /* Lazy builtin lookup, only do it if we find an assignment */
        if (b == 0)
          {
-           b = builtin_address_internal (words->word->word, 0);
+           b = builtin_address_internal (wcmd->word->word, 0);
            if (b == 0 || (b->flags & ASSIGNMENT_BUILTIN) == 0)
              return;
            else if (b && (b->flags & ASSIGNMENT_BUILTIN))
-             words->word->flags |= W_ASSNBLTIN;
+             wcmd->word->flags |= W_ASSNBLTIN;
          }
        w->word->flags |= (W_NOSPLIT|W_NOGLOB|W_TILDEEXP|W_ASSIGNARG);
+#if defined (ARRAY_VARS)
+       if (assoc)
+         w->word->flags |= W_ASSIGNASSOC;
+       if (array)
+         w->word->flags |= W_ASSIGNARRAY;
+#endif
+       if (global)
+         w->word->flags |= W_ASSNGLOBAL;
+
+       /* If we have an assignment builtin that does not create local variables,
+          make sure we create global variables even if we internally call
+          `declare'.  The CHKLOCAL flag means to set attributes or values on
+          an existing local variable, if there is one. */
+       if (b && ((b->flags & (ASSIGNMENT_BUILTIN|LOCALVAR_BUILTIN)) == ASSIGNMENT_BUILTIN))
+         w->word->flags |= W_ASSNGLOBAL|W_CHKLOCAL;
+#if 0
+       else if (b && (b->flags & ASSIGNMENT_BUILTIN) && (b->flags & LOCALVAR_BUILTIN))
+         w->word->flags |= W_CHKLOCAL;
+#endif
+      }
+#if defined (ARRAY_VARS)
+    /* Note that we saw an associative array option to a builtin that takes
+       assignment statements.  This is a bit of a kludge. */
+    else if (w->word->word[0] == '-' && (strpbrk (w->word->word+1, "Aag") != 0))
+#else
+    else if (w->word->word[0] == '-' && strchr (w->word->word+1, 'g'))
+#endif
+      {
+       if (b == 0)
+         {
+           b = builtin_address_internal (wcmd->word->word, 0);
+           if (b == 0 || (b->flags & ASSIGNMENT_BUILTIN) == 0)
+             return;
+           else if (b && (b->flags & ASSIGNMENT_BUILTIN))
+             wcmd->word->flags |= W_ASSNBLTIN;
+         }
+       if ((wcmd->word->flags & W_ASSNBLTIN) && strchr (w->word->word+1, 'A'))
+         assoc = 1;
+       else if ((wcmd->word->flags & W_ASSNBLTIN) && strchr (w->word->word+1, 'a'))
+         array = 1;
+       if ((wcmd->word->flags & W_ASSNBLTIN) && strchr (w->word->word+1, 'g'))
+         global = 1;
       }
 }
 
+/* Return 1 if the file found by searching $PATH for PATHNAME, defaulting
+   to PATHNAME, is a directory.  Used by the autocd code below. */
+static int
+is_dirname (pathname)
+     char *pathname;
+{
+  char *temp;
+  int ret;
+
+  temp = search_for_command (pathname, 0);
+  ret = temp ? file_isdir (temp) : file_isdir (pathname);
+  free (temp);
+  return ret;
+}
+
 /* The meaty part of all the executions.  We have to start hacking the
    real execution of commands here.  Fork a process, set things up,
    execute the command. */
@@ -2696,20 +4156,32 @@ execute_simple_command (simple_command, pipe_in, pipe_out, async, fds_to_close)
   pid_t old_last_async_pid;
   sh_builtin_func_t *builtin;
   SHELL_VAR *func;
+  volatile int old_builtin, old_command_builtin;
 
   result = EXECUTION_SUCCESS;
   special_builtin_failed = builtin_is_special = 0;
   command_line = (char *)0;
 
+  QUIT;
+
   /* If we're in a function, update the line number information. */
-  if (variable_context && interactive_shell)
-    line_number -= function_line_number;
+  if (variable_context && interactive_shell && sourcelevel == 0)
+    {
+      /* line numbers in a function start at 1 */
+      line_number -= function_line_number - 1;
+      if (line_number <= 0)
+       line_number = 1;
+    }
 
   /* Remember what this command line looks like at invocation. */
   command_string_index = 0;
   print_simple_command (simple_command);
 
+#if 0
   if (signal_in_progress (DEBUG_TRAP) == 0 && (this_command_name == 0 || (STREQ (this_command_name, "trap") == 0)))
+#else
+  if (signal_in_progress (DEBUG_TRAP) == 0 && running_trap == 0)
+#endif
     {
       FREE (the_printed_command_except_trap);
       the_printed_command_except_trap = the_printed_command ? savestring (the_printed_command) : (char *)0;
@@ -2726,12 +4198,12 @@ execute_simple_command (simple_command, pipe_in, pipe_out, async, fds_to_close)
 #endif
 
   first_word_quoted =
-    simple_command->words ? (simple_command->words->word->flags & W_QUOTED): 0;
+    simple_command->words ? (simple_command->words->word->flags & W_QUOTED) : 0;
 
   last_command_subst_pid = NO_PID;
   old_last_async_pid = last_asynchronous_pid;
 
-  already_forked = dofork = 0;
+  already_forked = 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
@@ -2749,18 +4221,20 @@ execute_simple_command (simple_command, pipe_in, pipe_out, async, fds_to_close)
 
   if (dofork)
     {
+      char *p;
+
       /* Do this now, because execute_disk_command will do it anyway in the
         vast majority of cases. */
       maybe_make_export_env ();
 
       /* Don't let a DEBUG trap overwrite the command string to be saved with
         the process/job associated with this child. */
-      if (make_child (savestring (the_printed_command_except_trap), async) == 0)
+      if (make_child (p = savestring (the_printed_command_except_trap), async) == 0)
        {
          already_forked = 1;
          simple_command->flags |= CMD_NO_FORK;
 
-         subshell_environment = SUBSHELL_FORK;
+         subshell_environment = SUBSHELL_FORK;         /* XXX */
          if (pipe_in != NO_PIPE || pipe_out != NO_PIPE)
            subshell_environment |= SUBSHELL_PIPE;
          if (async)
@@ -2772,30 +4246,65 @@ execute_simple_command (simple_command, pipe_in, pipe_out, async, fds_to_close)
          if (fds_to_close)
            close_fd_bitmap (fds_to_close);
 
+         /* If we fork because of an input pipe, note input pipe for later to
+            inhibit async commands from redirecting stdin from /dev/null */
+         stdin_redir |= pipe_in != NO_PIPE;
+
          do_piping (pipe_in, pipe_out);
          pipe_in = pipe_out = NO_PIPE;
+#if defined (COPROCESS_SUPPORT)
+         coproc_closeall ();
+#endif
 
          last_asynchronous_pid = old_last_async_pid;
+
+         CHECK_SIGTERM;
+
+         if (async)
+           subshell_level++;           /* not for pipes yet */
+
+#if defined (JOB_CONTROL)
+         FREE (p);                     /* child doesn't use pointer */
+#endif
        }
       else
        {
+         /* Don't let simple commands that aren't the last command in a
+            pipeline change $? for the rest of the pipeline (or at all). */
+         if (pipe_out != NO_PIPE)
+           result = last_command_exit_value;
          close_pipes (pipe_in, pipe_out);
 #if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD)
-         unlink_fifo_list ();
+#if 0
+         /* Close /dev/fd file descriptors in the parent after forking the
+            last child in a (possibly one-element) pipeline.  Defer this
+            until any running shell function completes. */
+         if (pipe_out == NO_PIPE && variable_context == 0)     /* XXX */
+           unlink_fifo_list ();                /* XXX */
+#endif
 #endif
          command_line = (char *)NULL;      /* don't free this. */
+#if 0
          bind_lastarg ((char *)NULL);
+#endif
          return (result);
        }
     }
 
+  QUIT;                /* XXX */
+
   /* 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);
+      /* Pass the ignore return flag down to command substitutions */
+      if (simple_command->flags & CMD_IGNORE_RETURN)   /* XXX */
+       comsub_ignore_return++;
       words = expand_words (simple_command->words);
+      if (simple_command->flags & CMD_IGNORE_RETURN)
+       comsub_ignore_return--;
       current_fds_to_close = (struct fd_bitmap *)NULL;
     }
   else
@@ -2811,7 +4320,7 @@ execute_simple_command (simple_command, pipe_in, pipe_out, async, fds_to_close)
                                     pipe_in, pipe_out,
                                     already_forked ? 0 : async);
       if (already_forked)
-       exit (result);
+       sh_exit (result);
       else
        {
          bind_lastarg ((char *)NULL);
@@ -2849,11 +4358,12 @@ execute_simple_command (simple_command, pipe_in, pipe_out, async, fds_to_close)
 
   /* In POSIX mode, assignment errors in the temporary environment cause a
      non-interactive shell to exit. */
-  if (builtin_is_special && interactive_shell == 0 && tempenv_assign_error)
+  if (posixly_correct && builtin_is_special && interactive_shell == 0 && tempenv_assign_error)
     {
       last_command_exit_value = EXECUTION_FAILURE;
       jump_to_top_level (ERREXIT);
     }
+  tempenv_assign_error = 0;    /* don't care about this any more */
 
   add_unwind_protect (dispose_words, words);
   QUIT;
@@ -2874,7 +4384,7 @@ execute_simple_command (simple_command, pipe_in, pipe_out, async, fds_to_close)
       goto return_result;
     }
 
-  /* One other possiblilty.  The user may want to resume an existing job.
+  /* One other possibililty.  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 &&
@@ -2909,6 +4419,7 @@ execute_simple_command (simple_command, pipe_in, pipe_out, async, fds_to_close)
     }
 #endif /* JOB_CONTROL */
 
+run_builtin:
   /* Remember the name of this command globally. */
   this_command_name = words->word->word;
 
@@ -2927,11 +4438,21 @@ execute_simple_command (simple_command, pipe_in, pipe_out, async, fds_to_close)
 
   if (builtin || func)
     {
+      if (builtin)
+        {
+         old_builtin = executing_builtin;
+         old_command_builtin = executing_command_builtin;
+         unwind_protect_int (executing_builtin);       /* modified in execute_builtin */
+         unwind_protect_int (executing_command_builtin);       /* ditto */
+        }
       if (already_forked)
        {
          /* reset_terminating_signals (); */   /* XXX */
-         /* Cancel traps, in trap.c. */
-         restore_original_signals ();
+         /* Reset the signal handlers in the child, but don't free the
+            trap strings.  Set a flag noting that we have to free the
+            trap strings if we run trap to change a signal disposition. */
+         reset_signal_handlers ();
+         subshell_environment |= SUBSHELL_RESETTRAP;
 
          if (async)
            {
@@ -2942,7 +4463,8 @@ execute_simple_command (simple_command, pipe_in, pipe_out, async, fds_to_close)
              setup_async_signals ();
            }
 
-         subshell_level++;
+         if (async == 0)
+           subshell_level++;
          execute_subshell_builtin_or_function
            (words, simple_command->redirects, builtin, func,
             pipe_in, pipe_out, async, fds_to_close,
@@ -2958,9 +4480,29 @@ execute_simple_command (simple_command, pipe_in, pipe_out, async, fds_to_close)
            {
              if (result > EX_SHERRBASE)
                {
+                 switch (result)
+                   {
+                   case EX_REDIRFAIL:
+                   case EX_BADASSIGN:
+                   case EX_EXPFAIL:
+                     /* These errors cause non-interactive posix mode shells to exit */
+                     if (posixly_correct && builtin_is_special && interactive_shell == 0)
+                       {
+                         last_command_exit_value = EXECUTION_FAILURE;
+                         jump_to_top_level (ERREXIT);
+                       }
+                     break;
+                   case EX_DISKFALLBACK:
+                     /* XXX - experimental */
+                     executing_builtin = old_builtin;
+                     executing_command_builtin = old_command_builtin;
+                     builtin = 0;
+                     /* XXX - redirections will have to be performed again */
+                     goto execute_from_filesystem;
+                   }
                  result = builtin_status (result);
                  if (builtin_is_special)
-                   special_builtin_failed = 1;
+                   special_builtin_failed = 1; /* XXX - take command builtin into account? */
                }
              /* In POSIX mode, if there are assignment statements preceding
                 a special builtin, they persist after the builtin
@@ -2982,10 +4524,27 @@ execute_simple_command (simple_command, pipe_in, pipe_out, async, fds_to_close)
        }
     }
 
+  if (autocd && interactive && words->word && is_dirname (words->word->word))
+    {
+      words = make_word_list (make_word ("--"), words);
+      words = make_word_list (make_word ("cd"), words);
+      xtrace_print_word_list (words, 0);
+      func = find_function ("cd");
+      goto run_builtin;
+    }
+
+execute_from_filesystem:
   if (command_line == 0)
-    command_line = savestring (the_printed_command);
+    command_line = savestring (the_printed_command_except_trap ? the_printed_command_except_trap : "");
 
-  execute_disk_command (words, simple_command->redirects, command_line,
+#if defined (PROCESS_SUBSTITUTION)
+  /* The old code did not test already_forked and only did this if
+     subshell_environment&SUBSHELL_COMSUB != 0 (comsubs and procsubs). Other
+     uses of the no-fork optimization left FIFOs in $TMPDIR */
+  if (already_forked == 0 && (simple_command->flags & CMD_NO_FORK) && fifos_pending() > 0)
+    simple_command->flags &= ~CMD_NO_FORK;
+#endif
+  result = execute_disk_command (words, simple_command->redirects, command_line,
                        pipe_in, pipe_out, async, fds_to_close,
                        simple_command->flags);
 
@@ -2993,6 +4552,11 @@ execute_simple_command (simple_command, pipe_in, pipe_out, async, fds_to_close)
   bind_lastarg (lastarg);
   FREE (command_line);
   dispose_words (words);
+  if (builtin)
+    {
+      executing_builtin = old_builtin;
+      executing_command_builtin = old_command_builtin;
+    }
   discard_unwind_frame ("simple-command");
   this_command_name = (char *)NULL;    /* points to freed memory now */
   return (result);
@@ -3030,21 +4594,37 @@ execute_builtin (builtin, words, flags, subshell)
      WORD_LIST *words;
      int flags, subshell;
 {
-  int old_e_flag, result, eval_unwind;
-  int isbltinenv;
+  int result, eval_unwind, ignexit_flag;
+  int isbltinenv, should_keep;
+  char *error_trap;
+
+  error_trap = 0;
+  should_keep = 0;
 
-  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))
+     value of the command, we turn the -e flag off ourselves and disable
+     the ERR trap, then restore them when the command completes.  This is
+     also a problem (as below) for the command and source/. builtins. */
+  if (subshell == 0 && (flags & CMD_IGNORE_RETURN) &&
+       (builtin == eval_builtin || builtin == command_builtin || builtin == source_builtin))
     {
       begin_unwind_frame ("eval_builtin");
       unwind_protect_int (exit_immediately_on_error);
+      unwind_protect_int (builtin_ignoring_errexit);
+      error_trap = TRAP_STRING (ERROR_TRAP);
+      if (error_trap)
+       {
+         error_trap = savestring (error_trap);
+         add_unwind_protect (xfree, error_trap);
+         add_unwind_protect (set_error_trap, error_trap);
+         restore_default_signal (ERROR_TRAP);
+       }
       exit_immediately_on_error = 0;
+      ignexit_flag = builtin_ignoring_errexit;
+      builtin_ignoring_errexit = 1;
       eval_unwind = 1;
     }
   else
@@ -3052,9 +4632,21 @@ execute_builtin (builtin, words, flags, subshell)
 
   /* 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 `unset', `source' and `eval' builtins. */
-
-  isbltinenv = (builtin == source_builtin || builtin == eval_builtin || builtin == unset_builtin);
+     problem only with the `unset', `source' and `eval' builtins.
+     `mapfile' is a special case because it uses evalstring (same as
+     eval or source) to run its callbacks. */
+  /* SHOULD_KEEP is for the pop_scope call below; it only matters when
+     posixly_correct is set, but we should propagate the temporary environment
+     to the enclosing environment only for special builtins. */
+  isbltinenv = (builtin == source_builtin || builtin == eval_builtin || builtin == unset_builtin || builtin == mapfile_builtin);
+  should_keep = isbltinenv && builtin != mapfile_builtin;
+#if defined (HISTORY) && defined (READLINE)
+  if (builtin == fc_builtin || builtin == read_builtin)
+    {
+      isbltinenv = 1;
+      should_keep = 0;
+    }
+#endif
 
   if (isbltinenv)
     {
@@ -3064,22 +4656,53 @@ execute_builtin (builtin, words, flags, subshell)
       if (temporary_env)
        {
          push_scope (VC_BLTNENV, temporary_env);
+         if (flags & CMD_COMMAND_BUILTIN)
+           should_keep = 0;
          if (subshell == 0)
-           add_unwind_protect (pop_scope, (flags & CMD_COMMAND_BUILTIN) ? 0 : "1");
+           add_unwind_protect (pop_scope, should_keep ? "1" : 0);
           temporary_env = (HASH_TABLE *)NULL;    
        }
     }
 
+  if (subshell == 0 && builtin == eval_builtin)
+    {
+      if (evalnest_max > 0 && evalnest >= evalnest_max)
+       {
+         internal_error (_("eval: maximum eval nesting level exceeded (%d)"), evalnest);
+         evalnest = 0;
+         jump_to_top_level (DISCARD);
+       }
+      unwind_protect_int (evalnest);
+      /* The test for subshell == 0 above doesn't make a difference */
+      evalnest++;      /* execute_subshell_builtin_or_function sets this to 0 */
+    }
+  else if (subshell == 0 && builtin == source_builtin)
+    {
+      if (sourcenest_max > 0 && sourcenest >= sourcenest_max)
+       {
+         internal_error (_("%s: maximum source nesting level exceeded (%d)"), this_command_name, sourcenest);
+         sourcenest = 0;
+         jump_to_top_level (DISCARD);
+       }
+      unwind_protect_int (sourcenest);
+      /* The test for subshell == 0 above doesn't make a difference */
+      sourcenest++;    /* execute_subshell_builtin_or_function sets this to 0 */
+    }
+
   /* `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)
+     table, since `return' is a POSIX special builtin. We don't do this if
+     it's being run by the `command' builtin, since that's supposed to inhibit
+     the special builtin properties. */
+  if (posixly_correct && subshell == 0 && builtin == return_builtin && (flags & CMD_COMMAND_BUILTIN) == 0 && temporary_env)
     {
       begin_unwind_frame ("return_temp_env");
       add_unwind_protect (merge_temporary_env, (char *)NULL);
     }
 
+  executing_builtin++;
+  executing_command_builtin |= builtin == command_builtin;
   result = ((*builtin) (words->next));
 
   /* This shouldn't happen, but in case `return' comes back instead of
@@ -3092,13 +4715,52 @@ execute_builtin (builtin, words, flags, subshell)
 
   if (eval_unwind)
     {
-      exit_immediately_on_error += old_e_flag;
+      builtin_ignoring_errexit = ignexit_flag;
+      exit_immediately_on_error = builtin_ignoring_errexit ? 0 : errexit_flag;
+      if (error_trap)
+       {
+         set_error_trap (error_trap);
+         free (error_trap);
+       }
       discard_unwind_frame ("eval_builtin");
     }
 
   return (result);
 }
 
+static void
+maybe_restore_getopt_state (gs)
+     sh_getopt_state_t *gs;
+{
+  /* If we have a local copy of OPTIND and it's at the right (current)
+     context, then we restore getopt's internal state.  If not, we just
+     let it go.  We know there is a local OPTIND if gs->gs_flags & 1.
+     This is set below in execute_function() before the context is run. */
+  if (gs->gs_flags & 1)
+    sh_getopt_restore_istate (gs);
+  else
+    free (gs);
+}
+
+#if defined (ARRAY_VARS)
+void
+restore_funcarray_state (fa)
+     struct func_array_state *fa;
+{
+  SHELL_VAR *nfv;
+  ARRAY *funcname_a;
+
+  array_pop (fa->source_a);
+  array_pop (fa->lineno_a);
+
+  GET_ARRAY_FROM_VAR ("FUNCNAME", nfv, funcname_a);
+  if (nfv == fa->funcname_v)
+    array_pop (funcname_a);
+
+  free (fa);
+}
+#endif
+
 static int
 execute_function (var, words, flags, fds_to_close, async, subshell)
      SHELL_VAR *var;
@@ -3111,15 +4773,26 @@ execute_function (var, words, flags, fds_to_close, async, subshell)
   COMMAND *tc, *fc, *save_current;
   char *debug_trap, *error_trap, *return_trap;
 #if defined (ARRAY_VARS)
-  SHELL_VAR *funcname_v, *nfv, *bash_source_v, *bash_lineno_v;
-  ARRAY *funcname_a, *bash_source_a, *bash_lineno_a;
+  SHELL_VAR *funcname_v, *bash_source_v, *bash_lineno_v;
+  ARRAY *funcname_a;
+  volatile ARRAY *bash_source_a;
+  volatile ARRAY *bash_lineno_a;
+  struct func_array_state *fa;
 #endif
   FUNCTION_DEF *shell_fn;
   char *sfile, *t;
-  static int funcnest = 0;
+  sh_getopt_state_t *gs;
+  SHELL_VAR *gv;
 
   USE_VAR(fc);
 
+  if (funcnest_max > 0 && funcnest >= funcnest_max)
+    {
+      internal_error (_("%s: maximum function nesting level exceeded (%d)"), var->name, funcnest);
+      funcnest = 0;    /* XXX - should we reset it somewhere else? */
+      jump_to_top_level (DISCARD);
+    }
+
 #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);
@@ -3130,16 +4803,30 @@ execute_function (var, words, flags, fds_to_close, async, subshell)
   if (tc && (flags & CMD_IGNORE_RETURN))
     tc->flags |= CMD_IGNORE_RETURN;
 
+  gs = sh_getopt_save_istate ();
   if (subshell == 0)
     {
       begin_unwind_frame ("function_calling");
+      /* If the shell is in posix mode, this will push the variables in
+        the temporary environment to the "current shell environment" (the
+        global scope), and dispose the temporary env before setting it to
+        NULL later. This behavior has disappeared from the latest edition
+        of the standard, so I will eventually remove it from variables.c:
+        push_var_context. */
       push_context (var->name, subshell, temporary_env);
+      /* This has to be before the pop_context(), because the unwinding of
+        local variables may cause the restore of a local declaration of
+        OPTIND to force a getopts state reset. */
+      add_unwind_protect (maybe_restore_getopt_state, gs);
       add_unwind_protect (pop_context, (char *)NULL);
       unwind_protect_int (line_number);
+      unwind_protect_int (line_number_for_err_trap);
+      unwind_protect_int (function_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 (funcnest);
       unwind_protect_int (loop_level);
     }
   else
@@ -3167,7 +4854,7 @@ execute_function (var, words, flags, fds_to_close, async, subshell)
        {
          debug_trap = savestring (debug_trap);
          add_unwind_protect (xfree, debug_trap);
-         add_unwind_protect (set_debug_trap, debug_trap);
+         add_unwind_protect (maybe_set_debug_trap, debug_trap);
        }
       restore_default_signal (DEBUG_TRAP);
     }
@@ -3179,24 +4866,20 @@ execute_function (var, words, flags, fds_to_close, async, subshell)
        {
          error_trap = savestring (error_trap);
          add_unwind_protect (xfree, error_trap);
-         add_unwind_protect (set_error_trap, error_trap);
+         add_unwind_protect (maybe_set_error_trap, error_trap);
        }
       restore_default_signal (ERROR_TRAP);
     }
 
   /* Shell functions inherit the RETURN trap if function tracing is on
      globally or on individually for this function. */
-#if 0
-  if (return_trap && ((trace_p (var) == 0) && function_trace_mode == 0))
-#else
   if (return_trap && (signal_in_progress (DEBUG_TRAP) || ((trace_p (var) == 0) && function_trace_mode == 0)))
-#endif
     {
       if (subshell == 0)
        {
          return_trap = savestring (return_trap);
          add_unwind_protect (xfree, return_trap);
-         add_unwind_protect (set_return_trap, return_trap);
+         add_unwind_protect (maybe_set_return_trap, return_trap);
        }
       restore_default_signal (RETURN_TRAP);
     }
@@ -3206,22 +4889,43 @@ execute_function (var, words, flags, fds_to_close, async, subshell)
   /* 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 ((ARRAY *)funcname_a, this_shell_function->name);
 
-  array_push (bash_source_a, sfile);
+  array_push ((ARRAY *)bash_source_a, sfile);
   t = itos (executing_line_number ());
-  array_push (bash_lineno_a, t);
+  array_push ((ARRAY *)bash_lineno_a, t);
   free (t);
 #endif
 
+#if defined (ARRAY_VARS)
+  fa = (struct func_array_state *)xmalloc (sizeof (struct func_array_state));
+  fa->source_a = (ARRAY *)bash_source_a;
+  fa->source_v = bash_source_v;
+  fa->lineno_a = (ARRAY *)bash_lineno_a;
+  fa->lineno_v = bash_lineno_v;
+  fa->funcname_a = (ARRAY *)funcname_a;
+  fa->funcname_v = funcname_v;
+  if (subshell == 0)
+    add_unwind_protect (restore_funcarray_state, fa);
+#endif
+
   /* The temporary environment for a function is supposed to apply to
      all commands executed within the function body. */
 
+  /* Initialize BASH_ARGC and BASH_ARGV before we blow away the positional
+     parameters */
+  if (debugging_mode || shell_compatibility_level <= 44)
+    init_bash_argv ();
+
   remember_args (words->next, 1);
 
   /* Update BASH_ARGV and BASH_ARGC */
   if (debugging_mode)
-    push_args (words->next);
+    {
+      push_args (words->next);
+      if (subshell == 0)
+       add_unwind_protect (pop_args, 0);
+    }
 
   /* Number of the line on which the function body starts. */
   line_number = function_line_number = tc->line;
@@ -3231,17 +4935,23 @@ execute_function (var, words, flags, fds_to_close, async, subshell)
     stop_pipeline (async, (COMMAND *)NULL);
 #endif
 
+  if (shell_compatibility_level > 43)
+    loop_level = 0;
+
   fc = tc;
 
+  from_return_trap = 0;
+
   return_catch_flag++;
-  return_val = setjmp (return_catch);
+  return_val = setjmp_nosigs (return_catch);
 
   if (return_val)
     {
       result = return_catch_value;
       /* Run the RETURN trap in the function's context. */
       save_current = currently_executing_command;
-      run_return_trap ();
+      if (from_return_trap == 0)
+       run_return_trap ();
       currently_executing_command = save_current;
     }
   else
@@ -3275,29 +4985,30 @@ execute_function (var, words, flags, fds_to_close, async, subshell)
       showing_function_line = 0;
     }
 
-  /* Restore BASH_ARGC and BASH_ARGV */
-  if (debugging_mode)
-    pop_args ();
+  /* If we have a local copy of OPTIND, note it in the saved getopts state. */
+  gv = find_variable ("OPTIND");
+  if (gv && gv->context == variable_context)
+    gs->gs_flags |= 1;
 
   if (subshell == 0)
     run_unwind_frame ("function_calling");
-
-  funcnest--;
 #if defined (ARRAY_VARS)
-  /* These two variables cannot be unset, and cannot be affected by the
-     function. */
-  array_pop (bash_source_a);
-  array_pop (bash_lineno_a);
-
-  /* FUNCNAME can be unset, and so can potentially be changed by the
-     function. */
-  GET_ARRAY_FROM_VAR ("FUNCNAME", nfv, funcname_a);
-  if (nfv == funcname_v)
-    array_pop (funcname_a);
+  else
+    {
+      restore_funcarray_state (fa);
+      /* Restore BASH_ARGC and BASH_ARGV */
+      if (debugging_mode)
+       pop_args ();
+    }
 #endif
-  
+
   if (variable_context == 0 || this_shell_function == 0)
-    make_funcname_visible (0);
+    {
+      make_funcname_visible (0);
+#if defined (PROCESS_SUBSTITUTION)
+      unlink_fifo_list ();
+#endif
+    }
 
   return (result);
 }
@@ -3342,7 +5053,7 @@ execute_subshell_builtin_or_function (words, redirects, builtin, var,
      struct fd_bitmap *fds_to_close;
      int flags;
 {
-  int result, r;
+  int result, r, funcvalue;
 #if defined (JOB_CONTROL)
   int jobs_hack;
 
@@ -3352,8 +5063,15 @@ execute_subshell_builtin_or_function (words, redirects, builtin, var,
 
   /* A subshell is neither a login shell nor interactive. */
   login_shell = interactive = 0;
+  if (builtin == eval_builtin)
+    evalnest = 0;
+  else if (builtin == source_builtin)
+    sourcenest = 0;
 
-  subshell_environment = SUBSHELL_ASYNC;
+  if (async)
+    subshell_environment |= SUBSHELL_ASYNC;
+  if (pipe_in != NO_PIPE || pipe_out != NO_PIPE)
+    subshell_environment |= SUBSHELL_PIPE;
 
   maybe_make_export_env ();    /* XXX - is this needed? */
 
@@ -3369,6 +5087,8 @@ execute_subshell_builtin_or_function (words, redirects, builtin, var,
     without_job_control ();
 
   set_sigchld_handler ();
+#else
+  without_job_control ();
 #endif /* JOB_CONTROL */
 
   set_sigint_handler ();
@@ -3385,22 +5105,44 @@ execute_subshell_builtin_or_function (words, redirects, builtin, var,
     {
       /* Give builtins a place to jump back to on failure,
         so we don't go back up to main(). */
-      result = setjmp (top_level);
+      result = setjmp_nosigs (top_level);
+
+      /* Give the return builtin a place to jump to when executed in a subshell
+         or pipeline */
+      funcvalue = 0;
+      if (return_catch_flag && builtin == return_builtin)
+        funcvalue = setjmp_nosigs (return_catch);
 
       if (result == EXITPROG)
        exit (last_command_exit_value);
       else if (result)
        exit (EXECUTION_FAILURE);
+      else if (funcvalue)
+       exit (return_catch_value);
       else
        {
          r = execute_builtin (builtin, words, flags, 1);
+         fflush (stdout);
          if (r == EX_USAGE)
            r = EX_BADUSAGE;
-         exit (r);
+         /* XXX - experimental */
+         else if (r == EX_DISKFALLBACK)
+           {
+             char *command_line;
+
+             command_line = savestring (the_printed_command_except_trap ? the_printed_command_except_trap : "");
+             r = execute_disk_command (words, (REDIRECT *)0, command_line,
+                 -1, -1, async, (struct fd_bitmap *)0, flags|CMD_NO_FORK);
+           }
+         sh_exit (r);
        }
     }
   else
-    exit (execute_function (var, words, flags, fds_to_close, async, 1));
+    {
+      r = execute_function (var, words, flags, fds_to_close, async, 1);
+      fflush (stdout);
+      sh_exit (r);
+    }
 }
 
 /* Execute a builtin or function in the current shell context.  If BUILTIN
@@ -3423,17 +5165,31 @@ execute_builtin_or_function (words, builtin, var, redirects,
 {
   int result;
   REDIRECT *saved_undo_list;
-  sh_builtin_func_t *saved_this_shell_builtin;
+#if defined (PROCESS_SUBSTITUTION)
+  int ofifo, nfifo, osize;
+  char *ofifo_list;
+#endif
+
+#if defined (PROCESS_SUBSTITUTION)
+  begin_unwind_frame ("saved_fifos");
+  /* If we return, we longjmp and don't get a chance to restore the old
+     fifo list, so we add an unwind protect to free it */
+  ofifo = num_fifos ();
+  ofifo_list = copy_fifo_list (&osize);
+  if (ofifo_list)
+    add_unwind_protect (xfree, ofifo_list);
+#endif
 
   if (do_redirections (redirects, RX_ACTIVE|RX_UNDOABLE) != 0)
     {
-      cleanup_redirects (redirection_undo_list);
-      redirection_undo_list = (REDIRECT *)NULL;
+      undo_partial_redirects ();
       dispose_exec_redirects ();
+#if defined (PROCESS_SUBSTITUTION)
+      free (ofifo_list);
+#endif
       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. */
@@ -3448,7 +5204,7 @@ execute_builtin_or_function (words, builtin, var, redirects,
 
   if (saved_undo_list)
     {
-      begin_unwind_frame ("saved redirects");
+      begin_unwind_frame ("saved-redirects");
       add_unwind_protect (cleanup_redirects, (char *)saved_undo_list);
     }
 
@@ -3460,6 +5216,8 @@ execute_builtin_or_function (words, builtin, var, redirects,
     result = execute_function (var, words, flags, fds_to_close, 0, 0);
 
   /* We do this before undoing the effects of any redirections. */
+  fflush (stdout);
+  fpurge (stdout);
   if (ferror (stdout))
     clearerr (stdout);  
 
@@ -3471,24 +5229,37 @@ execute_builtin_or_function (words, builtin, var, redirects,
      and preserve the redirections. */
   if (builtin == command_builtin && this_shell_builtin == exec_builtin)
     {
+      int discard;
+
+      discard = 0;
       if (saved_undo_list)
-       dispose_redirects (saved_undo_list);
+       {
+         dispose_redirects (saved_undo_list);
+         discard = 1;
+       }
       redirection_undo_list = exec_redirection_undo_list;
       saved_undo_list = exec_redirection_undo_list = (REDIRECT *)NULL;      
-      discard_unwind_frame ("saved_redirects");
+      if (discard)
+       discard_unwind_frame ("saved-redirects");
     }
 
   if (saved_undo_list)
     {
       redirection_undo_list = saved_undo_list;
-      discard_unwind_frame ("saved redirects");
+      discard_unwind_frame ("saved-redirects");
     }
 
-  if (redirection_undo_list)
-    {
-      cleanup_redirects (redirection_undo_list);
-      redirection_undo_list = (REDIRECT *)NULL;
-    }
+  undo_partial_redirects ();
+
+#if defined (PROCESS_SUBSTITUTION)
+  /* Close any FIFOs created by this builtin or function. */
+  nfifo = num_fifos ();
+  if (nfifo > ofifo)
+    close_new_fifos (ofifo_list, osize);
+  if (ofifo_list)
+    free (ofifo_list);
+  discard_unwind_frame ("saved_fifos");
+#endif
 
   return (result);
 }
@@ -3504,10 +5275,15 @@ setup_async_signals ()
   if (job_control == 0)
 #endif
     {
+      /* Make sure we get the original signal dispositions now so we don't
+        confuse the trap builtin later if the subshell tries to use it to
+        reset SIGINT/SIGQUIT.  Don't call set_signal_ignored; that sets
+        the value of original_signals to SIG_IGN. Posix interpretation 751. */
+      get_original_signal (SIGINT);
       set_signal_handler (SIGINT, SIG_IGN);
-      set_signal_ignored (SIGINT);
+
+      get_original_signal (SIGQUIT);
       set_signal_handler (SIGQUIT, SIG_IGN);
-      set_signal_ignored (SIGQUIT);
     }
 }
 
@@ -3529,7 +5305,13 @@ setup_async_signals ()
    this gnarly hair, for no good reason.
 
    NOTE: callers expect this to fork or exit(). */
-static void
+
+/* Name of a shell function to call when a command name is not found. */
+#ifndef NOTFOUND_HOOK
+#  define NOTFOUND_HOOK "command_not_found_handle"
+#endif
+
+static int
 execute_disk_command (words, redirects, command_line, pipe_in, pipe_out,
                      async, fds_to_close, cmdflags)
      WORD_LIST *words;
@@ -3539,20 +5321,25 @@ execute_disk_command (words, redirects, command_line, pipe_in, pipe_out,
      struct fd_bitmap *fds_to_close;
      int cmdflags;
 {
-  char *pathname, *command, **args;
-  int nofork;
+  char *pathname, *command, **args, *p;
+  int nofork, stdpath, result;
   pid_t pid;
+  SHELL_VAR *hookf;
+  WORD_LIST *wl;
 
-  nofork = (cmdflags & CMD_NO_FORK);  /* Don't fork, just exec, if no pipes */
+  stdpath = (cmdflags & CMD_STDPATH);  /* use command -p path */
+  nofork = (cmdflags & CMD_NO_FORK);   /* Don't fork, just exec, if no pipes */
   pathname = words->word->word;
 
+  p = 0;
+  result = EXECUTION_SUCCESS;
 #if defined (RESTRICTED_SHELL)
   command = (char *)NULL;
-  if (restricted && xstrchr (pathname, '/'))
+  if (restricted && mbschr (pathname, '/'))
     {
       internal_error (_("%s: restricted: cannot specify `/' in command names"),
                    pathname);
-      last_command_exit_value = EXECUTION_FAILURE;
+      result = 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).  */
@@ -3563,10 +5350,16 @@ execute_disk_command (words, redirects, command_line, pipe_in, pipe_out,
     }
 #endif /* RESTRICTED_SHELL */
 
-  command = search_for_command (pathname);
+  command = search_for_command (pathname, CMDSRCH_HASH|(stdpath ? CMDSRCH_STDPATH : 0));
+  QUIT;
 
   if (command)
     {
+      /* If we're optimizing out the fork (implicit `exec'), decrement the
+        shell level like `exec' would do. */
+      if (nofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE)
+       adjust_shell_level (-1);
+
       maybe_make_export_env ();
       put_command_name_into_env (command);
     }
@@ -3578,23 +5371,22 @@ execute_disk_command (words, redirects, command_line, pipe_in, pipe_out,
   if (nofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE)
     pid = 0;
   else
-    pid = make_child (savestring (command_line), async);
+    pid = make_child (p = 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
-
+      reset_terminating_signals ();    /* XXX */
       /* Cancel traps, in trap.c. */
       restore_original_signals ();
 
+      CHECK_SIGTERM;
+
+#if defined (JOB_CONTROL)
+      FREE (p);
+#endif
+
       /* restore_original_signals may have undone the work done
         by make_child to ensure that SIGINT and SIGQUIT are ignored
         in asynchronous children. */
@@ -3621,7 +5413,7 @@ execute_disk_command (words, redirects, command_line, pipe_in, pipe_out,
       if (async)
        interactive = 0;
 
-      subshell_environment = SUBSHELL_FORK;
+      subshell_environment |= SUBSHELL_FORK;   /* XXX - was just = */
 
       if (redirects && (do_redirections (redirects, RX_ACTIVE) != 0))
        {
@@ -3638,10 +5430,30 @@ execute_disk_command (words, redirects, command_line, pipe_in, pipe_out,
 
       if (command == 0)
        {
-         internal_error (_("%s: command not found"), pathname);
-         exit (EX_NOTFOUND);   /* Posix.2 says the exit status is 127 */
+         hookf = find_function (NOTFOUND_HOOK);
+         if (hookf == 0)
+           {
+             /* Make sure filenames are displayed using printable characters */
+             pathname = printable_filename (pathname, 0);
+             internal_error (_("%s: command not found"), pathname);
+             exit (EX_NOTFOUND);       /* Posix.2 says the exit status is 127 */
+           }
+
+         /* We don't want to manage process groups for processes we start
+            from here, so we turn off job control and don't attempt to
+            manipulate the terminal's process group. */
+         without_job_control ();
+
+#if defined (JOB_CONTROL)
+         set_sigchld_handler ();
+#endif
+
+         wl = make_word_list (make_word (NOTFOUND_HOOK), words);
+         exit (execute_shell_function (hookf, wl));
        }
 
+      CHECK_SIGTERM;
+
       /* 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. */
@@ -3651,12 +5463,18 @@ execute_disk_command (words, redirects, command_line, pipe_in, pipe_out,
   else
     {
 parent_return:
+      QUIT;
+
       /* 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 ();
+#if 0
+      if (variable_context == 0)
+        unlink_fifo_list ();
+#endif
 #endif
       FREE (command);
+      return (result);
     }
 }
 
@@ -3778,9 +5596,10 @@ initialize_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 ();
+
+#if defined (JOB_CONTROL)
   set_sigchld_handler ();
   init_job_stats ();
 #endif /* JOB_CONTROL */
@@ -3799,9 +5618,13 @@ initialize_subshell ()
     shell_variables = shell_variables->down;
 
   clear_unwind_protect_list (0);
+  /* XXX -- are there other things we should be resetting here? */
+  parse_and_execute_level = 0;         /* nothing left to restore it */
 
   /* We're no longer inside a shell function. */
-  variable_context = return_catch_flag = 0;
+  variable_context = return_catch_flag = funcnest = evalnest = sourcenest = 0;
+
+  executing_list = 0;          /* XXX */
 
   /* If we're not interactive, close the file descriptor from which we're
      reading the current shell script. */
@@ -3815,13 +5638,15 @@ initialize_subshell ()
 #  define SETOSTYPE(x)
 #endif
 
+#define HASH_BANG_BUFSIZ       128
+
 #define READ_SAMPLE_BUF(file, buf, len) \
   do \
     { \
       fd = open(file, O_RDONLY); \
       if (fd >= 0) \
        { \
-         len = read (fd, buf, 80); \
+         len = read (fd, buf, HASH_BANG_BUFSIZ); \
          close (fd); \
        } \
       else \
@@ -3836,22 +5661,28 @@ shell_execve (command, args, env)
      char *command;
      char **args, **env;
 {
-  struct stat finfo;
   int larray, i, fd;
-  char sample[80];
+  char sample[HASH_BANG_BUFSIZ];
   int sample_len;
 
   SETOSTYPE (0);               /* Some systems use for USG/POSIX semantics */
   execve (command, args, env);
   i = errno;                   /* error from execve() */
+  CHECK_TERMSIG;
   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)))
+      /* make sure this is set correctly for file_error/report_error */
+      last_command_exit_value = (i == ENOENT) ?  EX_NOTFOUND : EX_NOEXEC; /* XXX Posix.2 says that exit status is 126 */
+      if (file_isdir (command))
+#if defined (EISDIR)
+       internal_error (_("%s: %s"), command, strerror (EISDIR));
+#else
        internal_error (_("%s: is a directory"), command);
+#endif
       else if (executable_file (command) == 0)
        {
          errno = i;
@@ -3869,6 +5700,8 @@ shell_execve (command, args, env)
             run it for some reason.  See why. */
 #if defined (HAVE_HASH_BANG_EXEC)
          READ_SAMPLE_BUF (command, sample, sample_len);
+         if (sample_len > 0)
+           sample[sample_len - 1] = '\0';
          if (sample_len > 2 && sample[0] == '#' && sample[1] == '!')
            {
              char *interp;
@@ -3892,7 +5725,7 @@ shell_execve (command, args, env)
          errno = i;
          file_error (command);
        }
-      return ((i == ENOENT) ? EX_NOTFOUND : EX_NOEXEC);        /* XXX Posix.2 says that exit status is 126 */
+      return (last_command_exit_value);
     }
 
   /* This file is executable.
@@ -3921,7 +5754,8 @@ shell_execve (command, args, env)
 #endif
       if (check_binary_file (sample, sample_len))
        {
-         internal_error (_("%s: cannot execute binary file"), command);
+         internal_error (_("%s: cannot execute binary file: %s"), command, strerror (i));
+         errno = i;
          return (EX_BINARY_FILE);
        }
     }
@@ -3929,6 +5763,7 @@ shell_execve (command, args, env)
   /* We have committed to attempting to execute the contents of this file
      as shell commands. */
 
+  reset_parser ();
   initialize_subshell ();
 
   set_sigint_handler ();
@@ -3969,27 +5804,47 @@ shell_execve (command, args, env)
 
   unbind_args ();      /* remove the positional parameters */
 
-  longjmp (subshell_top_level, 1);
+#if defined (PROCESS_SUBSTITUTION)
+  clear_fifo_list ();  /* pipe fds are what they are now */
+#endif
+
+  sh_longjmp (subshell_top_level, 1);
   /*NOTREACHED*/
 }
 
 static int
-execute_intern_function (name, function)
+execute_intern_function (name, funcdef)
      WORD_DESC *name;
-     COMMAND *function;
+     FUNCTION_DEF *funcdef;
 {
   SHELL_VAR *var;
+  char *t;
 
   if (check_identifier (name, posixly_correct) == 0)
     {
       if (posixly_correct && interactive_shell == 0)
        {
-         last_command_exit_value = EX_USAGE;
+         last_command_exit_value = EX_BADUSAGE;
          jump_to_top_level (ERREXIT);
        }
       return (EXECUTION_FAILURE);
     }
 
+  if (strchr (name->word, CTLESC))     /* WHY? */
+    {
+      t = dequote_escapes (name->word);
+      free (name->word);
+      name->word = t;
+    }
+
+  /* Posix interpretation 383 */
+  if (posixly_correct && find_special_builtin (name->word))
+    {
+      internal_error (_("`%s': is a special builtin"), name->word);
+      last_command_exit_value = EX_BADUSAGE;
+      jump_to_top_level (interactive_shell ? DISCARD : ERREXIT);
+    }
+
   var = find_function (name->word);
   if (var && (readonly_p (var) || noassign_p (var)))
     {
@@ -3998,7 +5853,11 @@ execute_intern_function (name, function)
       return (EXECUTION_FAILURE);
     }
 
-  bind_function (name->word, function);
+#if defined (DEBUGGER)
+  bind_function_def (name->word, funcdef, 1);
+#endif
+
+  bind_function (name->word, funcdef->command);
   return (EXECUTION_SUCCESS);
 }
 
@@ -4048,6 +5907,10 @@ do_piping (pipe_in, pipe_out)
        dup_error (pipe_in, 0);
       if (pipe_in > 0)
        close (pipe_in);
+#ifdef __CYGWIN__
+      /* Let stdio know the fd may have changed from text to binary mode. */
+      freopen (NULL, "r", stdin);
+#endif /* __CYGWIN__ */
     }
   if (pipe_out != NO_PIPE)
     {
@@ -4063,5 +5926,11 @@ do_piping (pipe_in, pipe_out)
          if (dup2 (1, 2) < 0)
            dup_error (1, 2);
        }
+#ifdef __CYGWIN__
+      /* Let stdio know the fd may have changed from text to binary mode, and
+        make sure to preserve stdout line buffering. */
+      freopen (NULL, "w", stdout);
+      sh_setlinebuf (stdout);
+#endif /* __CYGWIN__ */
     }
 }