]> git.ipfire.org Git - thirdparty/bash.git/blobdiff - builtins/evalstring.c
bash-5.2 distribution sources and documentation
[thirdparty/bash.git] / builtins / evalstring.c
index b164e744ed10c20c8d13e30961b0e6156774a174..fd635299581f57369fe999365d998de6669f7edb 100644 (file)
@@ -1,20 +1,22 @@
-/* Copyright (C) 1996 Free Software Foundation, Inc.
+/* evalstring.c - evaluate a string as one or more shell commands. */
+
+/* Copyright (C) 1996-2022 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. */
+   You should have received a copy of the GNU General Public License
+   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+*/
 
 #include <config.h>
 
 #include "../jobs.h"
 #include "../builtins.h"
 #include "../flags.h"
+#include "../parser.h"
 #include "../input.h"
 #include "../execute_cmd.h"
 #include "../redir.h"
 #include "../trap.h"
+#include "../bashintl.h"
+
+#include <y.tab.h>
 
 #if defined (HISTORY)
 #  include "../bashhist.h"
 #endif
 
 #include "common.h"
+#include "builtext.h"
 
 #if !defined (errno)
 extern int errno;
@@ -54,65 +61,189 @@ extern int errno;
 
 #define IS_BUILTIN(s)  (builtin_address_internal(s, 0) != (struct builtin *)NULL)
 
-extern int indirection_level, startup_state, subshell_environment;
-extern int line_number;
-extern int last_command_exit_value;
-extern int running_trap;
-extern int posixly_correct;
-
 int parse_and_execute_level = 0;
 
-static int cat_file __P((REDIRECT *));
+static int cat_file PARAMS((REDIRECT *));
+
+#define PE_TAG "parse_and_execute top"
+#define PS_TAG "parse_string top"
+
+#if defined (HISTORY)
+static void
+set_history_remembering ()
+{
+  remember_on_history = enable_history_list;
+}
+#endif
+
+static void
+restore_lastcom (x)
+     char *x;
+{
+  FREE (the_printed_command_except_trap);
+  the_printed_command_except_trap = x;
+}
+
+int
+should_optimize_fork (command, subshell)
+     COMMAND *command;
+     int subshell;
+{
+  return (running_trap == 0 &&
+      command->type == cm_simple &&
+      signal_is_trapped (EXIT_TRAP) == 0 &&
+      signal_is_trapped (ERROR_TRAP) == 0 &&
+      any_signals_trapped () < 0 &&
+      (subshell || (command->redirects == 0 && command->value.Simple->redirects == 0)) &&
+      ((command->flags & CMD_TIME_PIPELINE) == 0) &&
+      ((command->flags & CMD_INVERT_RETURN) == 0));
+}
+
+/* This has extra tests to account for STARTUP_STATE == 2, which is for
+   -c command but has been extended to command and process substitution
+   (basically any time you call parse_and_execute in a subshell). */
+int
+should_suppress_fork (command)
+     COMMAND *command;
+{
+  int subshell;
+
+  subshell = subshell_environment & SUBSHELL_PROCSUB;  /* salt to taste */
+  return (startup_state == 2 && parse_and_execute_level == 1 &&
+         *bash_input.location.string == '\0' &&
+         parser_expanding_alias () == 0 &&
+         should_optimize_fork (command, subshell));
+}
+
+int
+can_optimize_connection (command)
+     COMMAND *command;
+{
+  return (*bash_input.location.string == '\0' &&
+         parser_expanding_alias () == 0 &&
+         (command->value.Connection->connector == AND_AND || command->value.Connection->connector == OR_OR || command->value.Connection->connector == ';') &&
+         command->value.Connection->second->type == cm_simple);
+}
 
-/* How to force parse_and_execute () to clean up after itself. */
 void
-parse_and_execute_cleanup ()
+optimize_connection_fork (command)
+     COMMAND *command;
 {
-  if (running_trap)
+  if (command->type == cm_connection &&
+      (command->value.Connection->connector == AND_AND || command->value.Connection->connector == OR_OR || command->value.Connection->connector == ';') &&
+      (command->value.Connection->second->flags & CMD_TRY_OPTIMIZING) &&
+      ((startup_state == 2 && should_suppress_fork (command->value.Connection->second)) ||
+       ((subshell_environment & SUBSHELL_PAREN) && should_optimize_fork (command->value.Connection->second, 0))))
     {
-      run_trap_cleanup (running_trap - 1);
-      unfreeze_jobs_list ();
+      command->value.Connection->second->flags |= CMD_NO_FORK;
+      command->value.Connection->second->value.Simple->flags |= CMD_NO_FORK;
     }
-  run_unwind_frame ("parse_and_execute_top");
 }
 
-/* Parse and execute the commands in STRING.  Returns whatever
-   execute_command () returns.  This frees STRING.  FLAGS is a
-   flags word; look in common.h for the possible values.  Actions
-   are:
-       (flags & SEVAL_NONINT) -> interactive = 0;
-       (flags & SEVAL_INTERACT) -> interactive = 1;
-       (flags & SEVAL_NOHIST) -> call bash_history_disable ()
-*/
+void
+optimize_subshell_command (command)
+     COMMAND *command;
+{
+  if (should_optimize_fork (command, 0))
+    {
+      command->flags |= CMD_NO_FORK;
+      command->value.Simple->flags |= CMD_NO_FORK;
+    }
+  else if (command->type == cm_connection &&
+          (command->value.Connection->connector == AND_AND || command->value.Connection->connector == OR_OR || command->value.Connection->connector == ';') &&
+          command->value.Connection->second->type == cm_simple &&
+          parser_expanding_alias () == 0)
+    {     
+      command->value.Connection->second->flags |= CMD_TRY_OPTIMIZING;
+      command->value.Connection->second->value.Simple->flags |= CMD_TRY_OPTIMIZING;
+    }
+}
+
+void
+optimize_shell_function (command)
+     COMMAND *command;
+{
+  COMMAND *fc;
+
+  fc = (command->type == cm_group) ? command->value.Group->command : command;
+
+  if (fc->type == cm_simple && should_suppress_fork (fc))
+    {
+      fc->flags |= CMD_NO_FORK;
+      fc->value.Simple->flags |= CMD_NO_FORK;
+    }
+  else if (fc->type == cm_connection && can_optimize_connection (fc) && should_suppress_fork (fc->value.Connection->second))
+    {
+      fc->value.Connection->second->flags |= CMD_NO_FORK;
+      fc->value.Connection->second->value.Simple->flags |= CMD_NO_FORK;
+    }  
+}
 
 int
-parse_and_execute (string, from_file, flags)
+can_optimize_cat_file (command)
+     COMMAND *command;
+{
+  return (command->type == cm_simple && !command->redirects &&
+           (command->flags & CMD_TIME_PIPELINE) == 0 &&
+           command->value.Simple->words == 0 &&
+           command->value.Simple->redirects &&
+           command->value.Simple->redirects->next == 0 &&
+           command->value.Simple->redirects->instruction == r_input_direction &&
+           command->value.Simple->redirects->redirector.dest == 0);
+}
+
+/* How to force parse_and_execute () to clean up after itself. */
+void
+parse_and_execute_cleanup (old_running_trap)
+     int old_running_trap;
+{
+  if (running_trap > 0)
+    {
+      /* We assume if we have a different value for running_trap than when
+        we started (the only caller that cares is evalstring()), the
+        original caller will perform the cleanup, and we should not step
+        on them. */
+      if (running_trap != old_running_trap)
+       run_trap_cleanup (running_trap - 1);
+      unfreeze_jobs_list ();
+    }
+
+  if (have_unwind_protects ())
+     run_unwind_frame (PE_TAG);
+  else
+    parse_and_execute_level = 0;                       /* XXX */
+}
+
+static void
+parse_prologue (string, flags, tag)
      char *string;
-     const char *from_file;
      int flags;
+     char *tag;
 {
-  int code, x;
-  volatile int should_jump_to_top_level, last_result;
-  char *orig_string;
-  COMMAND *volatile command;
+  char *orig_string, *lastcom;
+  int x;
 
   orig_string = string;
   /* Unwind protect this invocation of parse_and_execute (). */
-  begin_unwind_frame ("parse_and_execute_top");
+  begin_unwind_frame (tag);
   unwind_protect_int (parse_and_execute_level);
   unwind_protect_jmp_buf (top_level);
   unwind_protect_int (indirection_level);
   unwind_protect_int (line_number);
+  unwind_protect_int (line_number_for_err_trap);
+  unwind_protect_int (loop_level);
+  unwind_protect_int (executing_list);
+  unwind_protect_int (comsub_ignore_return);
   if (flags & (SEVAL_NONINT|SEVAL_INTERACT))
     unwind_protect_int (interactive);
 
 #if defined (HISTORY)
-  unwind_protect_int (remember_on_history);    /* can be used in scripts */
+  if (parse_and_execute_level == 0)
+    add_unwind_protect (set_history_remembering, (char *)NULL);
+  else
+    unwind_protect_int (remember_on_history);  /* can be used in scripts */
 #  if defined (BANG_HISTORY)
-  if (interactive_shell)
-    {
-      unwind_protect_int (history_expansion_inhibited);
-    }
+  unwind_protect_int (history_expansion_inhibited);
 #  endif /* BANG_HISTORY */
 #endif /* HISTORY */
 
@@ -121,30 +252,96 @@ parse_and_execute (string, from_file, flags)
       x = get_current_prompt_level ();
       add_unwind_protect (set_current_prompt_level, x);
     }
-  
+
+  if (the_printed_command_except_trap)
+    {
+      lastcom = savestring (the_printed_command_except_trap);
+      add_unwind_protect (restore_lastcom, lastcom);
+    }
+
   add_unwind_protect (pop_stream, (char *)NULL);
-  if (orig_string)
+  if (parser_expanding_alias ())
+    add_unwind_protect (parser_restore_alias, (char *)NULL);
+
+  if (orig_string && ((flags & SEVAL_NOFREE) == 0))
     add_unwind_protect (xfree, orig_string);
   end_unwind_frame ();
 
-  parse_and_execute_level++;
-  push_stream (1);     /* reset the line number */
-  indirection_level++;
   if (flags & (SEVAL_NONINT|SEVAL_INTERACT))
     interactive = (flags & SEVAL_NONINT) ? 0 : 1;
 
 #if defined (HISTORY)
   if (flags & SEVAL_NOHIST)
     bash_history_disable ();
+#  if defined (BANG_HISTORY)
+  if (flags & SEVAL_NOHISTEXP)
+    history_expansion_inhibited = 1;
+#  endif /* BANG_HISTORY */
 #endif /* HISTORY */
+}
+
+/* Parse and execute the commands in STRING.  Returns whatever
+   execute_command () returns.  This frees STRING.  FLAGS is a
+   flags word; look in common.h for the possible values.  Actions
+   are:
+       (flags & SEVAL_NONINT) -> interactive = 0;
+       (flags & SEVAL_INTERACT) -> interactive = 1;
+       (flags & SEVAL_NOHIST) -> call bash_history_disable ()
+       (flags & SEVAL_NOFREE) -> don't free STRING when finished
+       (flags & SEVAL_RESETLINE) -> reset line_number to 1
+       (flags & SEVAL_NOHISTEXP) -> history_expansion_inhibited -> 1
+*/
+
+int
+parse_and_execute (string, from_file, flags)
+     char *string;
+     const char *from_file;
+     int flags;
+{
+  int code, lreset;
+  volatile int should_jump_to_top_level, last_result;
+  COMMAND *volatile command;
+  volatile sigset_t pe_sigmask;
+
+  parse_prologue (string, flags, PE_TAG);
+
+  parse_and_execute_level++;
+
+  lreset = flags & SEVAL_RESETLINE;
+
+#if defined (HAVE_POSIX_SIGNALS)
+  /* If we longjmp and are going to go on, use this to restore signal mask */
+  sigemptyset ((sigset_t *)&pe_sigmask);
+  sigprocmask (SIG_BLOCK, (sigset_t *)NULL, (sigset_t *)&pe_sigmask);
+#endif
+
+  /* Reset the line number if the caller wants us to.  If we don't reset the
+     line number, we have to subtract one, because we will add one just
+     before executing the next command (resetting the line number sets it to
+     0; the first line number is 1). */
+  push_stream (lreset);
+  if (parser_expanding_alias ())
+    /* push current shell_input_line */
+    parser_save_alias ();
+  
+  if (lreset == 0)
+    line_number--;
+    
+  indirection_level++;
 
   code = should_jump_to_top_level = 0;
   last_result = EXECUTION_SUCCESS;
-  command = (COMMAND *)NULL;
+
+  /* We need to reset enough of the token state so we can start fresh. */
+  if (current_token == yacc_EOF)
+    current_token = '\n';              /* reset_parser() ? */
 
   with_input_from_string (string, from_file);
-  while (*(bash_input.location.string))
+  clear_shell_input_line ();
+  while (*(bash_input.location.string) || parser_expanding_alias ())
     {
+      command = (COMMAND *)NULL;
+
       if (interrupt_state)
        {
          last_result = EXECUTION_FAILURE;
@@ -154,24 +351,62 @@ parse_and_execute (string, from_file, flags)
       /* Provide a location for functions which `longjmp (top_level)' to
         jump to.  This prevents errors in substitution from restarting
         the reader loop directly, for example. */
-      code = setjmp (top_level);
+      code = setjmp_nosigs (top_level);
 
       if (code)
        {
          should_jump_to_top_level = 0;
          switch (code)
            {
-           case FORCE_EOF:
+           case ERREXIT:
+             /* variable_context -> 0 is what eval.c:reader_loop() does in
+                these circumstances.  Don't bother with cleanup here because
+                we don't want to run the function execution cleanup stuff
+                that will cause pop_context and other functions to run.
+                We call reset_local_contexts() instead, which just frees
+                context memory.
+                XXX - change that if we want the function context to be
+                unwound. */
+             if (exit_immediately_on_error && variable_context)
+               {
+                 discard_unwind_frame ("pe_dispose");
+                 reset_local_contexts (); /* not in a function */
+               }
+             should_jump_to_top_level = 1;
+             goto out;
+           case FORCE_EOF:           
            case EXITPROG:
-             run_unwind_frame ("pe_dispose");
+             if (command)
+               run_unwind_frame ("pe_dispose");
              /* Remember to call longjmp (top_level) after the old
                 value for it is restored. */
              should_jump_to_top_level = 1;
              goto out;
 
+           case EXITBLTIN:
+             if (command)
+               {
+                 if (variable_context && signal_is_trapped (0))
+                   {
+                     /* Let's make sure we run the exit trap in the function
+                        context, as we do when not running parse_and_execute.
+                        The pe_dispose unwind frame comes before any unwind-
+                        protects installed by the string we're evaluating, so
+                        it will undo the current function scope. */
+                     dispose_command (command);
+                     discard_unwind_frame ("pe_dispose");
+                   }
+                 else
+                   run_unwind_frame ("pe_dispose");
+               }
+             should_jump_to_top_level = 1;
+             goto out;
+
            case DISCARD:
-             run_unwind_frame ("pe_dispose");
+             if (command)
+               run_unwind_frame ("pe_dispose");
              last_result = last_command_exit_value = EXECUTION_FAILURE; /* XXX */
+             set_pipestatus_from_exit (last_command_exit_value);
              if (subshell_environment)
                {
                  should_jump_to_top_level = 1;
@@ -181,6 +416,9 @@ parse_and_execute (string, from_file, flags)
                {
 #if 0
                  dispose_command (command);    /* pe_dispose does this */
+#endif
+#if defined (HAVE_POSIX_SIGNALS)
+                 sigprocmask (SIG_SETMASK, (sigset_t *)&pe_sigmask, (sigset_t *)NULL);
 #endif
                  continue;
                }
@@ -190,10 +428,10 @@ parse_and_execute (string, from_file, flags)
              break;
            }
        }
-         
+
       if (parse_command () == 0)
        {
-         if (interactive_shell == 0 && read_but_dont_execute)
+         if ((flags & SEVAL_PARSEONLY) || (interactive_shell == 0 && read_but_dont_execute))
            {
              last_result = EXECUTION_SUCCESS;
              dispose_command (global_command);
@@ -203,6 +441,28 @@ parse_and_execute (string, from_file, flags)
            {
              struct fd_bitmap *bitmap;
 
+             if (flags & SEVAL_FUNCDEF)
+               {
+                 char *x;
+
+                 /* If the command parses to something other than a straight
+                    function definition, or if we have not consumed the entire
+                    string, or if the parser has transformed the function
+                    name (as parsing will if it begins or ends with shell
+                    whitespace, for example), reject the attempt */
+                 if (command->type != cm_function_def ||
+                     ((x = parser_remaining_input ()) && *x) ||
+                     (STREQ (from_file, command->value.Function_def->name->word) == 0))
+                   {
+                     internal_warning (_("%s: ignoring function definition attempt"), from_file);
+                     should_jump_to_top_level = 0;
+                     last_result = last_command_exit_value = EX_BADUSAGE;
+                     set_pipestatus_from_exit (last_command_exit_value);
+                     reset_parser ();
+                     break;
+                   }
+               }
+
              bitmap = new_fd_bitmap (FD_BITMAP_SIZE);
              begin_unwind_frame ("pe_dispose");
              add_unwind_protect (dispose_fd_bitmap, bitmap);
@@ -210,38 +470,48 @@ parse_and_execute (string, from_file, flags)
 
              global_command = (COMMAND *)NULL;
 
+             if ((subshell_environment & SUBSHELL_COMSUB) && comsub_ignore_return)
+               command->flags |= CMD_IGNORE_RETURN;
+
 #if defined (ONESHOT)
              /*
               * IF
               *   we were invoked as `bash -c' (startup_state == 2) AND
               *   parse_and_execute has not been called recursively AND
+              *   we're not running a trap AND
               *   we have parsed the full command (string == '\0') AND
+              *   we're not going to run the exit trap AND
               *   we have a simple command without redirections AND
-              *   the command is not being timed
+              *   the command is not being timed AND
+              *   the command's return status is not being inverted AND
+              *   there aren't any traps in effect
               * THEN
               *   tell the execution code that we don't need to fork
               */
-             if (startup_state == 2 && parse_and_execute_level == 1 &&
-                 *bash_input.location.string == '\0' &&
-                 command->type == cm_simple &&
-                 !command->redirects && !command->value.Simple->redirects &&
-                 ((command->flags & CMD_TIME_PIPELINE) == 0))
+             if (should_suppress_fork (command))
                {
                  command->flags |= CMD_NO_FORK;
                  command->value.Simple->flags |= CMD_NO_FORK;
                }
+
+             /* Can't optimize forks out here execept for simple commands.
+                This knows that the parser sets up commands as left-side heavy
+                (&& and || are left-associative) and after the single parse,
+                if we are at the end of the command string, the last in a
+                series of connection commands is
+                command->value.Connection->second. */
+             else if (command->type == cm_connection && can_optimize_connection (command))
+               {
+                 command->value.Connection->second->flags |= CMD_TRY_OPTIMIZING;
+                 command->value.Connection->second->value.Simple->flags |= CMD_TRY_OPTIMIZING;
+               }
 #endif /* ONESHOT */
 
              /* See if this is a candidate for $( <file ). */
              if (startup_state == 2 &&
                  (subshell_environment & SUBSHELL_COMSUB) &&
                  *bash_input.location.string == '\0' &&
-                 command->type == cm_simple && !command->redirects &&
-                 (command->flags & CMD_TIME_PIPELINE) == 0 &&
-                 command->value.Simple->words == 0 &&
-                 command->value.Simple->redirects &&
-                 command->value.Simple->redirects->next == 0 &&
-                 command->value.Simple->redirects->instruction == r_input_direction)
+                 can_optimize_cat_file (command))
                {
                  int r;
                  r = cat_file (command->value.Simple->redirects);
@@ -250,15 +520,29 @@ parse_and_execute (string, from_file, flags)
              else
                last_result = execute_command_internal
                                (command, 0, NO_PIPE, NO_PIPE, bitmap);
-
              dispose_command (command);
              dispose_fd_bitmap (bitmap);
              discard_unwind_frame ("pe_dispose");
+
+             if (flags & SEVAL_ONECMD)
+               {
+                 reset_parser ();
+                 break;
+               }
            }
        }
       else
        {
-         last_result = EXECUTION_FAILURE;
+         last_result = EX_BADUSAGE;    /* was EXECUTION_FAILURE */
+
+         if (interactive_shell == 0 && this_shell_builtin &&
+             (this_shell_builtin == source_builtin || this_shell_builtin == eval_builtin) &&
+             last_command_exit_value == EX_BADSYNTAX && posixly_correct && executing_command_builtin == 0)
+           {
+             should_jump_to_top_level = 1;
+             code = ERREXIT;
+             last_command_exit_value = EX_BADUSAGE;
+           }
 
          /* Since we are shell compatible, syntax errors in a script
             abort the execution of the script.  Right? */
@@ -268,7 +552,7 @@ parse_and_execute (string, from_file, flags)
 
  out:
 
-  run_unwind_frame ("parse_and_execute_top");
+  run_unwind_frame (PE_TAG);
 
   if (interrupt_state && parse_and_execute_level == 0)
     {
@@ -279,22 +563,154 @@ parse_and_execute (string, from_file, flags)
       throw_to_top_level ();
     }
 
+  CHECK_TERMSIG;
+
   if (should_jump_to_top_level)
     jump_to_top_level (code);
 
   return (last_result);
 }
 
-/* Handle a $( < file ) command substitution.  This expands the filename,
-   returning errors as appropriate, then just cats the file to the standard
-   output. */
-static int
-cat_file (r)
+/* Parse a command contained in STRING according to FLAGS and return the
+   number of characters consumed from the string.  If non-NULL, set *ENDP
+   to the position in the string where the parse ended.  Used to validate
+   command substitutions during parsing to obey Posix rules about finding
+   the end of the command and balancing parens. */
+int
+parse_string (string, from_file, flags, cmdp, endp)
+     char *string;
+     const char *from_file;
+     int flags;
+     COMMAND **cmdp;
+     char **endp;
+{
+  int code, nc;
+  volatile int should_jump_to_top_level;
+  COMMAND *volatile command, *oglobal;
+  char *ostring;
+  volatile sigset_t ps_sigmask;
+
+  parse_prologue (string, flags, PS_TAG);
+
+#if defined (HAVE_POSIX_SIGNALS)
+  /* If we longjmp and are going to go on, use this to restore signal mask */
+  sigemptyset ((sigset_t *)&ps_sigmask);
+  sigprocmask (SIG_BLOCK, (sigset_t *)NULL, (sigset_t *)&ps_sigmask);
+#endif
+
+  /* Reset the line number if the caller wants us to.  If we don't reset the
+     line number, we have to subtract one, because we will add one just
+     before executing the next command (resetting the line number sets it to
+     0; the first line number is 1). */
+  push_stream (0);
+  if (parser_expanding_alias ())
+    /* push current shell_input_line */
+    parser_save_alias ();
+
+  code = should_jump_to_top_level = 0;
+  oglobal = global_command;
+
+  with_input_from_string (string, from_file);
+  ostring = bash_input.location.string;
+  while (*(bash_input.location.string))                /* XXX - parser_expanding_alias () ? */
+    {
+      command = (COMMAND *)NULL;
+
+#if 0
+      if (interrupt_state)
+       break;
+#endif
+
+      /* Provide a location for functions which `longjmp (top_level)' to
+        jump to. */
+      code = setjmp_nosigs (top_level);
+
+      if (code)
+       {
+         INTERNAL_DEBUG(("parse_string: longjmp executed: code = %d", code));
+
+         should_jump_to_top_level = 0;
+         switch (code)
+           {
+           case FORCE_EOF:
+           case ERREXIT:
+           case EXITPROG:
+           case EXITBLTIN:
+           case DISCARD:               /* XXX */
+             if (command)
+               dispose_command (command);
+             /* Remember to call longjmp (top_level) after the old
+                value for it is restored. */
+             should_jump_to_top_level = 1;
+             goto out;
+
+           default:
+#if defined (HAVE_POSIX_SIGNALS)
+             sigprocmask (SIG_SETMASK, (sigset_t *)&ps_sigmask, (sigset_t *)NULL);
+#endif
+             command_error ("parse_string", CMDERR_BADJUMP, code, 0);
+             break;
+           }
+       }
+         
+      if (parse_command () == 0)
+       {
+         if (cmdp)
+           *cmdp = global_command;
+         else
+           dispose_command (global_command);
+         global_command = (COMMAND *)NULL;
+       }
+      else
+       {
+         if ((flags & SEVAL_NOLONGJMP) == 0)
+           {
+             should_jump_to_top_level = 1;
+             code = DISCARD;
+           }
+         else
+           reset_parser ();    /* XXX - sets token_to_read */
+         break;
+       }
+
+      if (current_token == yacc_EOF || current_token == shell_eof_token)
+       {
+         if (current_token == shell_eof_token)
+           rewind_input_string ();
+         break;
+       }
+    }
+
+out:
+
+  global_command = oglobal;
+  nc = bash_input.location.string - ostring;
+  if (endp)
+    *endp = bash_input.location.string;
+
+  run_unwind_frame (PS_TAG);
+
+  /* If we return < 0, the caller (xparse_dolparen) will jump_to_top_level for
+     us, after doing cleanup */
+  if (should_jump_to_top_level)
+    {
+      if (parse_and_execute_level == 0)
+       top_level_cleanup ();
+      if (code == DISCARD)
+       return -DISCARD;
+      jump_to_top_level (code);
+    }
+
+  return (nc);
+}
+
+int
+open_redir_file (r, fnp)
      REDIRECT *r;
+     char **fnp;
 {
-  char lbuf[128], *fn;
+  char *fn;
   int fd, rval;
-  ssize_t nr;
 
   if (r->instruction != r_input_direction)
     return -1;
@@ -308,7 +724,7 @@ cat_file (r)
 
   if (fn == 0)
     {
-      redirection_error (r, AMBIGUOUS_REDIRECT);
+      redirection_error (r, AMBIGUOUS_REDIRECT, fn);
       return -1;
     }
 
@@ -317,29 +733,86 @@ cat_file (r)
     {
       file_error (fn);
       free (fn);
+      if (fnp)
+       *fnp = 0;
       return -1;
     }
 
-  rval = 0;
-  while (1)
-    {
-      nr = zread (fd, lbuf, sizeof(lbuf));
-      if (nr == 0)
-       break;
-      else if (nr < 0)
-       {
-         rval = -1;
-         break;
-       }
-      if (zwrite (1, lbuf, nr) < 0)
-       {
-         rval = -1;
-         break;
-       }
-    }
+  if (fnp)
+    *fnp = fn;
+  return fd;
+}
+
+/* Handle a $( < file ) command substitution.  This expands the filename,
+   returning errors as appropriate, then just cats the file to the standard
+   output. */
+static int
+cat_file (r)
+     REDIRECT *r;
+{
+  char *fn;
+  int fd, rval;
+
+  fd = open_redir_file (r, &fn);
+  if (fd < 0)
+    return -1;
+
+  rval = zcatfd (fd, 1, fn);
 
   free (fn);
   close (fd);
 
   return (rval);
 }
+
+int
+evalstring (string, from_file, flags)
+     char *string;
+     const char *from_file;
+     int flags;
+{
+  volatile int r, rflag, rcatch;
+  volatile int was_trap;
+
+  /* Are we running a trap when we execute this function? */
+  was_trap = running_trap;
+
+  rcatch = 0;
+  rflag = return_catch_flag;
+  /* If we are in a place where `return' is valid, we have to catch
+     `eval "... return"' and make sure parse_and_execute cleans up. Then
+     we can trampoline to the previous saved return_catch location. */
+  if (rflag)
+    {
+      begin_unwind_frame ("evalstring");
+
+      unwind_protect_int (return_catch_flag);
+      unwind_protect_jmp_buf (return_catch);
+
+      return_catch_flag++;     /* increment so we have a counter */
+      rcatch = setjmp_nosigs (return_catch);
+    }
+
+  if (rcatch)
+    {
+      /* We care about whether or not we are running the same trap we were
+        when we entered this function. */
+      parse_and_execute_cleanup (was_trap);
+      r = return_catch_value;
+    }
+  else
+    /* Note that parse_and_execute () frees the string it is passed. */
+    r = parse_and_execute (string, from_file, flags);
+
+  if (rflag)
+    {
+      run_unwind_frame ("evalstring");
+      if (rcatch && return_catch_flag)
+       {
+         return_catch_value = r;
+         sh_longjmp (return_catch, 1);
+       }
+    }
+    
+  return (r);
+}