]> git.ipfire.org Git - thirdparty/bash.git/commitdiff
subshells should not inherit FIFOs; initial framework for nofork (foreground) command...
authorChet Ramey <chet.ramey@case.edu>
Tue, 9 May 2023 14:33:56 +0000 (10:33 -0400)
committerChet Ramey <chet.ramey@case.edu>
Tue, 9 May 2023 14:33:56 +0000 (10:33 -0400)
CWRU/CWRU.chlog
Makefile.in
doc/bash.1
doc/bashref.texi
execute_cmd.c
parse.y
parser.h
subst.c
subst.h
support/Dockerfile [new file with mode: 0644]
tests/exportfunc.tests

index 9c287897f8ddf245772c55bc4750e164e16cee7b..c8b379bfbbf27ce8a1c938ad2075951a5fdffc87 100644 (file)
@@ -6257,3 +6257,48 @@ eval.c
          setting it to 0 for the expansion so it doesn't affect things like
          command substitution.
          From a report by Grisha Levit <grishalevit@gmail.com>
+
+parse.y
+       - parse_comsub: handle ${Ccommand; }, where C == space, tab, newline,
+         or '|'
+       - read_token_word: understand ${Ccommand; } and call parse_comsub to
+         parse the contents
+       - parse_matched_pair: understand ${Ccommand; } inside another paired
+         construct
+       - new `funsub' production, like comsub but for ${Ccommand;} nofork
+         command substitution
+
+execute_cmd.c
+       - execute_simple_command: if we fork because of pipes in or out, clear
+         the fifo list, since subshell aren't supposed to inherit fifos
+
+                                   5/8
+                                   ---
+parser.h
+       - PST_FUNSUBST: new parser state, parsing the command in ${Ccommand;}.
+         Primarily used to have read_token_word return `}' as a separate
+         token even if it's not delimited like a reserved word
+
+subst.h
+       - SX_FUNSUB: new flag, tell xparse_dolparen we are parsing the command
+         in a ${Ccommand;} nofork command substitution
+
+parse.y
+       - xparse_dolparen: handle parsing ${Ccommand;} nofork command
+         substitution like parse_comsub
+       - read_token: if we're parsing a foreground command substitution,
+         treat a word that begins with an unquoted `}' as a `}' token to
+         terminate the ${Ccommand;} construct without requiring that the
+         close brace be delimited like a reserved word
+       - no_semi_successors: add DOLBRACE
+       - reserved_word_acceptable: add DOLBRACE
+
+subst.c
+       - extract_function_subst: like extract_command_subst, but for nofork
+         command substitutions; calls xparse_dolparen with the SX_FUNSUB
+         flag to differentiate
+       - string_extract_double_quoted,extract_delimited_string,
+         extract_heredoc_dolbrace_string,extract_dollar_brace_string,
+         param_expand: call extract_function_subst as appropriate
+       - function_substitute: like command_substitute, but for nofork command
+         substitutions. Just a stub for now
index 115c6a210bd0c3f10c7b1e43e704c4f59a6c5741..7f990bfa5d96534559b4a3ca2ef6e446fb255493 100644 (file)
@@ -811,7 +811,8 @@ $(srcdir)/configure:        $(srcdir)/configure.ac $(srcdir)/aclocal.m4 $(srcdir)/confi
 reconfig: force
        sh $(srcdir)/configure -C
 
-loadables:
+# force loadables to wait until the shell is built
+loadables: .made
        cd $(LOADABLES_DIR) && $(MAKE) $(MFLAGS) all
 
 #newversion:   mkversion
index bee814ae1855de46e20d40c427cedf24b38030bb..06408e0c72761897f8085705b6bb69add839c5f9 100644 (file)
@@ -9539,7 +9539,7 @@ in the same way as \fBecho \-e\fP.
 .B %q
 causes \fBprintf\fP to output the corresponding
 \fIargument\fP in a format that can be reused as shell input.
-\fB%q\fP and \fB%Q\fP use the \fB$''\fP quoting style if any characters
+\fB%q\fP and \fB%Q\fP use the \fB$\(aq\(aq\fP quoting style if any characters
 in the argument string require it, and backslash quoting otherwise.
 If the format string uses the \fIprintf\fP alternate form, these two
 formats quote the argument string using single quotes.
index 3263a73cab93e4fff7ca6aa00f0963f9b1b70625..ff9c2f4121e7b7be98ed7316661500f87d6b8a55 100644 (file)
@@ -475,8 +475,8 @@ when in double quotes (@pxref{Shell Parameter Expansion}).
 @subsubsection ANSI-C Quoting
 @cindex quoting, ANSI
 
-Character sequences of the form $'@var{string}' are treated as a special
-kind of single quotes.
+Character sequences of the form @code{$'@var{string}'} are treated as
+a special kind of single quotes.
 The sequence expands to @var{string}, with backslash-escaped characters
 in @var{string} replaced as specified by the ANSI C standard.
 Backslash escape sequences, if present, are decoded as follows:
index 77cecb31727b03add2b6eb8cf3f7a79073634358..c55fd92e49f79163b8b6ce2fd7d2282ad5c22621 100644 (file)
@@ -4430,6 +4430,10 @@ execute_simple_command (SIMPLE_COM *simple_command, int pipe_in, int pipe_out, i
          coproc_closeall ();
 #endif
 
+#if defined (PROCESS_SUBSTITUTION)
+         clear_fifo_list ();           /* subshells don't inherit fifos */
+#endif
+
          last_asynchronous_pid = old_last_async_pid;
 
          if (async)
@@ -4767,13 +4771,38 @@ execute_from_filesystem:
   /* 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 && (cmdflags & CMD_NO_FORK) && fifos_pending() > 0)
+  if (already_forked == 0 && (cmdflags & CMD_NO_FORK) && fifos_pending () > 0)
+    cmdflags &= ~CMD_NO_FORK;
+
+  if (dofork && already_forked && (subshell_environment & SUBSHELL_PIPE) &&
+       (cmdflags & CMD_NO_FORK) && fifos_pending () > 0)
+#if 0
     cmdflags &= ~CMD_NO_FORK;
+#else
+    ;  /* can't turn off nofork here, too many processes have the FIFOs open */
+#endif
 #endif
   result = execute_disk_command (words, simple_command->redirects, command_line,
                        pipe_in, pipe_out, async, fds_to_close,
                        cmdflags);
 
+#if 0
+/* If we forked but still have to fork again to run the disk command, we
+   did so because we created FIFOs. We can't just execve the command in case
+   it dies of a signal without a chance to clean up the FIFOs, so we fork
+   again, then make sure we wait for the child from execute_disk_command(),
+   unlink the FIFOs we created, and exit ourselves. */
+if (dofork && already_forked && (cmdflags & CMD_NO_FORK) == 0)
+  {
+    result = wait_for (last_made_pid, 0);
+#if defined (PROCESS_SUBSTITUTION)
+    if (fifos_pending ())
+      unlink_fifo_list ();
+#endif
+    sh_exit (result & 0xFF);
+  }
+#endif
+
  return_result:
   bind_lastarg (lastarg);
   FREE (command_line);
diff --git a/parse.y b/parse.y
index bb7e79eeafc988fffa35bfa49a5bf22fbd07fd9c..2ca3d600fe8b33ac1555808270952a60df7c6a1a 100644 (file)
--- a/parse.y
+++ b/parse.y
@@ -394,7 +394,7 @@ static FILE *yyerrstream;
 %type <command> cond_command
 %type <command> arith_for_command
 %type <command> coproc
-%type <command> comsub
+%type <command> comsub funsub
 %type <command> function_def function_body if_command elif_clause subshell
 %type <redirect> redirection redirection_list
 %type <element> simple_command_element
@@ -429,6 +429,14 @@ inputunit: simple_list simple_list_terminator
                          eof_encountered = 0;
                          YYACCEPT;
                        }
+       |       funsub
+                       {
+                         /* This is special; look at the production and how
+                            parse_comsub/parse_valsub sets token_to_read */
+                         global_command = $1;
+                         eof_encountered = 0;
+                         YYACCEPT;
+                       }
        |       '\n'
                        {
                          /* Case of regular command, but not a very
@@ -1043,7 +1051,9 @@ comsub:           DOLPAREN compound_list ')'
                        {
                          $$ = (COMMAND *)NULL;
                        }
-       |       DOLBRACE compound_list '}'
+       ;
+       
+funsub:                DOLBRACE compound_list '}'
                        {
                          $$ = $2;
                        }
@@ -3983,7 +3993,16 @@ parse_dollar_word:
          if (ch == '(')                /* ) */
            nestret = parse_comsub (0, '(', ')', &nestlen, (rflags|P_COMMAND) & ~P_DQUOTE);
          else if (ch == '{')           /* } */
-           nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE|P_DOLBRACE|rflags);
+           {
+             int npeek;
+
+             npeek = shell_getc (1);
+             shell_ungetc (npeek);
+             if (FUNSUB_CHAR (npeek))
+               nestret = parse_comsub (0, '{', '}', &nestlen, rflags|P_COMMAND);
+             else
+               nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE|P_DOLBRACE|rflags);
+           }
          else if (ch == '[')           /* ] */
            nestret = parse_matched_pair (0, '[', ']', &nestlen, rflags|P_ARITH);
 
@@ -4141,6 +4160,11 @@ dump_pflags (int flags)
       f &= ~PST_CMDSUBST;
       fprintf (stderr, "PST_CMDSUBST%s", f ? "|" : "");
     }
+  if (f & PST_FUNSUBST)
+    {
+      f &= ~PST_FUNSUBST;
+      fprintf (stderr, "PST_FUNSUBST%s", f ? "|" : "");
+    }
   if (f & PST_CASESTMT)
     {
       f &= ~PST_CASESTMT;
@@ -4248,7 +4272,7 @@ static char *
 parse_comsub (int qc, int open, int close, size_t *lenp, int flags)
 {
   int peekc, r;
-  int start_lineno, local_extglob, was_extpat;
+  int start_lineno, dolbrace_spec, local_extglob, was_extpat;
   char *ret, *tcmd;
   size_t retlen;
   sh_parser_state_t ps;
@@ -4261,9 +4285,20 @@ parse_comsub (int qc, int open, int close, size_t *lenp, int flags)
     {
       peekc = shell_getc (1);
       shell_ungetc (peekc);
-      if (peekc == '(')                /*)*/
+      if (peekc == '(')                /* ) */
        return (parse_matched_pair (qc, open, close, lenp, P_ARITH));
     }
+  else if (open == '{')                /* } */
+    {
+      peekc = shell_getc (1);
+      if (FUNSUB_CHAR (peekc))
+       dolbrace_spec = peekc;
+      else
+       {
+         shell_ungetc (peekc);
+         return parse_matched_pair (qc, open, close, lenp, flags);
+       }
+    }
 
 /*itrace("parse_comsub: qc = `%c' open = %c close = %c", qc, open, close);*/
 
@@ -4283,6 +4318,8 @@ parse_comsub (int qc, int open, int close, size_t *lenp, int flags)
   parser_state &= ~(PST_CASEPAT|PST_ALEXPNEXT|PST_SUBSHELL|PST_REDIRLIST);
   /* State flags we want to set for this run through the parser. */
   parser_state |= PST_CMDSUBST|PST_EOFTOKEN|PST_NOEXPAND;
+  if (open == '{')             /* } */
+    parser_state |= PST_FUNSUBST;
 
   /* leave pushed_string_list alone, since we might need to consume characters
      from it to satisfy this command substitution (in some perverse case). */
@@ -4312,7 +4349,7 @@ parse_comsub (int qc, int open, int close, size_t *lenp, int flags)
 #endif
 
   current_token = '\n';                                /* XXX */
-  token_to_read = DOLPAREN;                    /* let's trick the parser */
+  token_to_read = (open == '(') ? DOLPAREN : DOLBRACE; /* let's trick the parser ) */
 
   r = yyparse ();
 
@@ -4381,17 +4418,33 @@ INTERNAL_DEBUG(("current_token (%d) != shell_eof_token (%c)", current_token, she
 
   tcmd = print_comsub (parsed_command);                /* returns static memory */
   retlen = strlen (tcmd);
-  if (tcmd[0] == '(')                  /* ) need a space to prevent arithmetic expansion */
-    retlen++;
-  ret = xmalloc (retlen + 2);
-  if (tcmd[0] == '(')                  /* ) */
+  if (open == '(')                     /* ) */
     {
-      ret[0] = ' ';
-      strcpy (ret + 1, tcmd);
+      if (tcmd[0] == '(')              /* ) need a space to prevent arithmetic expansion */
+       retlen++;
+      ret = xmalloc (retlen + 2);
+      if (tcmd[0] == '(')                      /* ) */
+       {
+         ret[0] = ' ';
+         strcpy (ret + 1, tcmd);
+       }
+      else
+       strcpy (ret, tcmd);
     }
-  else
-    strcpy (ret, tcmd);
-  ret[retlen++] = ')';
+  else                                 /* open == '{' } */
+    {
+      int lastc;
+
+      lastc = tcmd[retlen - 1];
+      retlen++;
+      ret = xmalloc (retlen + 4);
+      ret[0] = (dolbrace_spec == '|') ? '|' : ' ';
+      strcpy (ret + 1, tcmd);          /* ( */
+      if (lastc != '\n' && lastc != ';' && lastc != '&' && lastc != ')')
+       ret[retlen++] = ';';
+      ret[retlen++] = ' ';
+    }
+  ret[retlen++] = close;
   ret[retlen] = '\0';
 
   dispose_command (parsed_command);
@@ -4412,7 +4465,7 @@ xparse_dolparen (const char *base, char *string, int *indp, int flags)
 {
   sh_parser_state_t ps;
   sh_input_line_state_t ls;
-  int orig_ind, nc, sflags, start_lineno, local_extglob;
+  int orig_ind, nc, sflags, start_lineno, local_extglob, funsub, closer;
   char *ret, *ep, *ostring;
 
 /*debug_parser(1);*/
@@ -4432,6 +4485,8 @@ xparse_dolparen (const char *base, char *string, int *indp, int flags)
 
 /*itrace("xparse_dolparen: size = %d shell_input_line = `%s' string=`%s'", shell_input_line_size, shell_input_line, string);*/
 
+  funsub = flags & SX_FUNSUB;
+
   sflags = SEVAL_NONINT|SEVAL_NOHIST|SEVAL_NOFREE;
   if (flags & SX_NOLONGJMP)
     sflags |= SEVAL_NOLONGJMP;
@@ -4443,10 +4498,12 @@ xparse_dolparen (const char *base, char *string, int *indp, int flags)
   pushed_string_list = (STRING_SAVER *)NULL;
 #endif
   /*(*/
-  parser_state |= PST_CMDSUBST|PST_EOFTOKEN;   /* allow instant ')' */ /*(*/
-  shell_eof_token = ')';
+  parser_state |= PST_CMDSUBST|PST_EOFTOKEN;   /* allow instant ')' */ /*{(*/
+  closer = shell_eof_token = funsub ? '}' : ')';
   if (flags & SX_COMPLETE)
     parser_state |= PST_NOERROR;
+  if (funsub)
+    parser_state |= PST_FUNSUBST;
 
   /* Don't expand aliases on this pass at all. Either parse_comsub() does it
      at parse time, in which case this string already has aliases expanded,
@@ -4458,7 +4515,10 @@ xparse_dolparen (const char *base, char *string, int *indp, int flags)
   local_extglob = extended_glob;
 #endif
 
-  token_to_read = DOLPAREN;                    /* let's trick the parser */
+  if (funsub && FUNSUB_CHAR (*string))
+    string++;
+
+  token_to_read = funsub ? DOLBRACE : DOLPAREN;                        /* let's trick the parser */
 
   nc = parse_string (string, "command substitution", sflags, (COMMAND **)NULL, &ep);
 
@@ -4485,7 +4545,7 @@ xparse_dolparen (const char *base, char *string, int *indp, int flags)
     {
       clear_shell_input_line ();       /* XXX */
       if (bash_input.type != st_string)        /* paranoia */
-       parser_state &= ~(PST_CMDSUBST|PST_EOFTOKEN);
+       parser_state &= ~(PST_CMDSUBST|PST_EOFTOKEN|PST_FUNSUBST);
       if ((flags & SX_NOLONGJMP) == 0)
        jump_to_top_level (-nc);        /* XXX */
     }
@@ -4495,7 +4555,7 @@ xparse_dolparen (const char *base, char *string, int *indp, int flags)
      and return it.  If flags & 1 (SX_NOALLOC) we can return NULL. */
 
   /*(*/
-  if (ep[-1] != ')')
+  if (ep[-1] != closer)
     {
 #if 0
       if (ep[-1] != '\n')
@@ -4516,7 +4576,7 @@ xparse_dolparen (const char *base, char *string, int *indp, int flags)
     itrace("xparse_dolparen:%d: *indp (%d) < orig_ind (%d), orig_string = `%s'", line_number, *indp, orig_ind, ostring);
 #endif
 
-  if (base[*indp] != ')' && (flags & SX_NOLONGJMP) == 0)
+  if (base[*indp] != closer && (flags & SX_NOLONGJMP) == 0)
     {
       /*(*/
       if ((flags & SX_NOERROR) == 0)
@@ -5096,6 +5156,24 @@ read_token_word (int character)
            }
        }
 
+      /* Just an awful special case. We want '}' to delimit the foreground
+        command substitution construct ${Ccommand; } but since it's a word
+        expansion, we need to join it with any potential following
+        characters. We fake things out here and treat a word beginning with
+        a close brace as the '}' reserved word, treat it as a separate
+        token, terminate the command substitution, and go on reading
+        characters into the same upper-layer token. */
+      if ((parser_state & PST_FUNSUBST) && token_index == 0 && quoted == 0 &&
+           reserved_word_acceptable (last_read_token) &&
+           MBTEST(character == '}'))
+       {
+         RESIZE_MALLOCED_BUFFER (token, token_index, 2,
+                                 token_buffer_size, TOKEN_DEFAULT_GROW_SIZE);
+         token[token_index++] = character;
+         all_digit_token = dollar_present = 0;
+         goto got_token;
+       }
+
       /* Parse a matched pair of quote characters. */
       if MBTEST(shellquote (character))
        {
@@ -5180,7 +5258,19 @@ read_token_word (int character)
                ((peek_char == '{' || peek_char == '[') && character == '$'))   /* ) ] } */
            {
              if (peek_char == '{')             /* } */
-               ttok = parse_matched_pair (cd, '{', '}', &ttoklen, P_FIRSTCLOSE|P_DOLBRACE);
+               {
+                 int npeek;
+                 npeek = shell_getc (1);
+                 shell_ungetc (npeek);
+                 if (FUNSUB_CHAR (npeek))
+                   {
+                     push_delimiter (dstack, peek_char);
+                     ttok = parse_comsub (cd, '{', '}', &ttoklen, P_COMMAND);
+                     pop_delimiter (dstack);
+                   }
+                 else
+                   ttok = parse_matched_pair (cd, '{', '}', &ttoklen, P_FIRSTCLOSE|P_DOLBRACE);
+               }
              else if (peek_char == '(')                /* ) */
                {
                  /* XXX - push and pop the `(' as a delimiter for use by
@@ -5587,6 +5677,7 @@ reserved_word_acceptable (int toksym)
     case WHILE:
     case 0:
     case DOLPAREN:
+    case DOLBRACE:
       return 1;
     default:
 #if defined (COPROCESS_SUPPORT)
@@ -5655,7 +5746,7 @@ reset_readline_prompt (void)
 static const int no_semi_successors[] = {
   '\n', '{', '(', ')', ';', '&', '|',
   CASE, DO, ELSE, IF, SEMI_SEMI, SEMI_AND, SEMI_SEMI_AND, THEN, UNTIL,
-  WHILE, AND_AND, OR_OR, IN, DOLPAREN,
+  WHILE, AND_AND, OR_OR, IN, DOLPAREN, DOLBRACE,
   0
 };
 
index a27dc06b0859088c0f8b4b27c52c11f847e3d200..3efdd798e04e31e5982d84a548b474de1f533109 100644 (file)
--- a/parser.h
+++ b/parser.h
@@ -52,6 +52,7 @@
 #define PST_NOERROR    0x800000        /* don't print error messages in yyerror */
 #define PST_STRING     0x1000000       /* parsing a string to a command or word list */
 #define PST_CMDBLTIN   0x2000000       /* last token was the `command' builtin */
+#define PST_FUNSUBST   0x4000000       /* parsing a foreground command substitution */
 
 /* Definition of the delimiter stack.  Needed by parse.y and bashhist.c. */
 struct dstack {
@@ -75,6 +76,10 @@ struct dstack {
 #define DOLBRACE_QUOTE 0x40    /* single quote is special in double quotes */
 #define DOLBRACE_QUOTE2        0x80    /* single quote is semi-special in double quotes */
 
+/* characters that can appear following ${ to introduce a function or value
+   substitution (this is mksh terminology and needs to be changed). */
+#define FUNSUB_CHAR(n) ((n) == ' ' || (n) == '\t' || (n) == '\n' || (n) == '|')
+
 /* variable declarations from parse.y */
 extern struct dstack dstack;
 
diff --git a/subst.c b/subst.c
index bdb5ba88e2ff2fb746f2047f982740d27a5d2b4b..c2ff4c1306126159b753224d31455692d8cacadc 100644 (file)
--- a/subst.c
+++ b/subst.c
@@ -934,6 +934,8 @@ add_one_character:
          si = i + 2;
          if (string[i + 1] == LPAREN)
            ret = extract_command_subst (string, &si, (flags & SX_COMPLETE));
+         else if (string[i + 1] == LBRACE && FUNSUB_CHAR (string[si]))
+           ret = extract_function_subst (string, &si, Q_DOUBLE_QUOTES, (flags & SX_COMPLETE));
          else
            ret = extract_dollar_brace_string (string, &si, Q_DOUBLE_QUOTES, 0);
 
@@ -1036,6 +1038,8 @@ skip_double_quoted (const char *string, size_t slen, int sind, int flags)
          si = i + 2;
          if (string[i + 1] == LPAREN)
            ret = extract_command_subst (string, &si, SX_NOALLOC|(flags&SX_COMPLETE));
+         else if (string[i + 1] == LBRACE && FUNSUB_CHAR (string[si]))
+           ret = extract_function_subst (string, &si, Q_DOUBLE_QUOTES, (flags & SX_COMPLETE));
          else
            ret = extract_dollar_brace_string (string, &si, Q_DOUBLE_QUOTES, SX_NOALLOC);
 
@@ -1256,6 +1260,25 @@ extract_command_subst (const char *string, int *sindex, int xflags)
     }
 }
 
+/* Take a ${Ccommand} where C is a character that introduces a function
+   substitution and extract the string. */
+char *
+extract_function_subst (const char *string, int *sindex, int quoted, int xflags)
+{
+  char *ret;
+  char *xstr;
+
+  if (string[*sindex] == LBRACE || (xflags & SX_COMPLETE))
+    return (extract_dollar_brace_string (string, sindex, quoted, xflags|SX_COMMAND));
+  else
+    {
+      xflags |= (no_longjmp_on_fatal_error ? SX_NOLONGJMP : 0);
+      xstr = (char *)string + *sindex;
+      ret = xparse_dolparen (string, xstr, sindex, xflags|SX_FUNSUB);
+      return ret;
+    }
+}
+
 /* Extract the $[ construct in STRING, and return a new string. (])
    Start extracting at (SINDEX) as if we had just seen "$[".
    Make (SINDEX) get the position of the matching "]". */
@@ -1392,6 +1415,16 @@ extract_delimited_string (const char *string, int *sindex, char *opener, char *a
           continue;
         }
 
+      /* Process alternate form of nested command substitution. */
+      if ((flags & SX_COMMAND) && string[i] == '$' && string[i+1] == LBRACE && FUNSUB_CHAR (string[i+2]))
+        {
+          si = i + 2;
+          t = extract_function_subst (string, &si, 0, flags|SX_NOALLOC);
+          CHECK_STRING_OVERRUN (i, si, slen, c);
+          i = si + 1;
+          continue;
+        }
+
       /* Process a nested OPENER. */
       if (STREQN (string + i, opener, len_opener))
        {
@@ -1641,16 +1674,22 @@ extract_heredoc_dolbrace_string (const char *string, int *sindex, int quoted, in
 
       /* Pass the contents of new-style command substitutions and
         arithmetic substitutions through verbatim. */
-      if (string[i] == '$' && string[i+1] == LPAREN)
+      if (string[i] == '$' && (string[i+1] == LPAREN || (string[i+1] == LBRACE && FUNSUB_CHAR (string[i+2]))))
        {
+         int open;
+
          si = i + 2;
-         t = extract_command_subst (string, &si, flags);
+         open = string[i+1];
+         if (open == LPAREN)
+           t = extract_command_subst (string, &si, flags);
+         else
+           t = extract_function_subst (string, &si, quoted, flags);
          CHECK_STRING_OVERRUN (i, si, slen, c);
 
          tlen = si - i - 2;
          RESIZE_MALLOCED_BUFFER (result, result_index, tlen + 4, result_size, 64);
          result[result_index++] = c;
-         result[result_index++] = LPAREN;
+         result[result_index++] = open;
          strncpy (result + result_index, t, tlen);
          result_index += tlen;
          result[result_index++] = string[si];
@@ -1854,6 +1893,19 @@ extract_dollar_brace_string (const char *string, int *sindex, int quoted, int fl
          continue;
        }
 
+      /* Pass the contents of foreground command substitutions (funsub/valsub)
+        through verbatim. */
+      if (string[i] == '$' && string[i+1] == LBRACE && FUNSUB_CHAR (string[i+2]))
+       {
+         si = i + 2;
+         t = extract_function_subst (string, &si, quoted, flags|SX_NOALLOC);
+
+         CHECK_STRING_OVERRUN (i, si, slen, c);
+
+         i = si + 1;
+         continue;
+       }
+
 #if defined (PROCESS_SUBSTITUTION)
       /* Technically this should only work at the start of a word */
       if ((string[i] == '<' || string[i] == '>') && string[i+1] == LPAREN)
@@ -2084,7 +2136,7 @@ skip_matched_pair (const char *string, int start, int open, int close, int flags
          if (string[si] == '\0')
            CQ_RETURN(si);
 
-         /* XXX - extract_command_subst here? */
+         /* XXX - extract_command_subst/extract_function_subst here? */
          if (string[i+1] == LPAREN)
            temp = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
          else
@@ -6693,6 +6745,52 @@ read_comsub (int fd, int quoted, int flags, int *rflag)
   return istring;
 }
 
+WORD_DESC *
+function_substitute (char *string, int quoted, int flags)
+{
+  pid_t old_pipeline_pgrp;
+  char *istring, *s;
+  int fd;
+  int result, fildes[2], function_value, pflags, rc, tflag, fork_flags;
+  int valsub;
+  WORD_DESC *ret;
+  sigset_t set, oset;
+
+  istring = (char *)NULL;
+
+  /* In the case of no command to run, just return NULL. */
+  for (s = string; s && *s && (shellblank (*s) || *s == '\n'); s++)
+    ;
+  if (s == 0 || *s == 0)
+    return ((WORD_DESC *)NULL);
+
+  if (valsub = (*string == '|'))
+    string++;
+
+  /* Flags to pass to parse_and_execute() */
+  pflags = (interactive && sourcelevel == 0) ? SEVAL_RESETLINE : 0;
+
+#if defined (JOB_CONTROL)
+  old_pipeline_pgrp = pipeline_pgrp;
+  /* Don't reset the pipeline pgrp if we're already a subshell in a pipeline or
+     we've already forked to run a disk command (and are expanding redirections,
+     for example). */
+  if ((subshell_environment & (SUBSHELL_FORK|SUBSHELL_PIPE)) == 0)
+    pipeline_pgrp = shell_pgrp;
+  save_pipeline (1);
+  stop_making_children ();
+#endif /* JOB_CONTROL */
+
+  last_command_exit_value = EXECUTION_FAILURE;
+  report_error ("bad substitution: %s", string);
+
+  pipeline_pgrp = old_pipeline_pgrp;
+  restore_pipeline (1);
+
+  /* exp_jump_to_top_level (DISCARD); */
+  return ((WORD_DESC *)NULL);
+}
+
 /* Perform command substitution on STRING.  This returns a WORD_DESC * with the
    contained string possibly quoted. */
 WORD_DESC *
@@ -10277,6 +10375,30 @@ param_expand (char *string, int *sindex, int quoted,
       break;
 
     case LBRACE:
+      /* Foreground command substitution. */
+      if (FUNSUB_CHAR (string[zindex + 1]))
+       {
+         /* We have to extract the contents of this command substitution. */
+         t_index = zindex + 1;
+         temp = extract_function_subst (string, &t_index, quoted, (pflags&PF_COMPLETE) ? SX_COMPLETE : 0);
+         zindex = t_index;
+
+         /* This is basically the same as the comsub code. */
+         if (pflags & PF_NOCOMSUB)
+         /* we need zindex+1 because string[zindex] == RPAREN */
+           temp1 = substring (string, *sindex, zindex+1);
+         else
+           {
+             tdesc = function_substitute (temp, quoted, pflags&PF_ASSIGNRHS);
+             temp1 = tdesc ? tdesc->word : (char *)NULL;
+             if (tdesc)
+               dispose_word_desc (tdesc);
+           }
+         FREE (temp);
+         temp = temp1;
+         break;
+       }
+
       tdesc = parameter_brace_expand (string, &zindex, quoted, pflags,
                                      quoted_dollar_at_p,
                                      contains_dollar_at);
diff --git a/subst.h b/subst.h
index a42d8c84c23091de2ab7d0c595c6f9dcc4c6b1cc..cb47cc9aee8eafe248689c86c8e98902b5b62a49 100644 (file)
--- a/subst.h
+++ b/subst.h
@@ -66,7 +66,7 @@
 #define SX_NOCTLESC    0x0010  /* don't honor CTLESC quoting */
 #define SX_NOESCCTLNUL 0x0020  /* don't let CTLESC quote CTLNUL */
 #define SX_NOLONGJMP   0x0040  /* don't longjmp on fatal error */
-#define SX_ARITHSUB    0x0080  /* extracting $(( ... )) (currently unused) */
+#define SX_FUNSUB      0x0080  /* extracting ${ command; }; passed to xparse_dolparen */
 #define SX_POSIXEXP    0x0100  /* extracting new Posix pattern removal expansions in extract_dollar_brace_string */
 #define SX_WORD                0x0200  /* extracting word in ${param op word} */
 #define SX_COMPLETE    0x0400  /* extracting word for completion */
@@ -86,6 +86,9 @@ extern void unquote_bang (char *);
    XFLAGS is additional flags to pass to other extraction functions, */
 extern char *extract_command_subst (const char *, int *, int);
 
+/* Placeholder */
+extern char *extract_function_subst (const char *, int *, int, int);
+
 /* Extract the $[ construct in STRING, and return a new string.
    Start extracting at (SINDEX) as if we had just seen "$[".
    Make (SINDEX) get the position just after the matching "]". */
@@ -274,6 +277,7 @@ extern WORD_LIST *expand_words_no_vars (WORD_LIST *);
 extern WORD_LIST *expand_words_shellexp (WORD_LIST *);
 
 extern WORD_DESC *command_substitute (char *, int, int);
+extern WORD_DESC *function_substitute (char *, int, int);
 extern char *pat_subst (char *, char *, char *, int);
 
 #if defined (PROCESS_SUBSTITUTION)
diff --git a/support/Dockerfile b/support/Dockerfile
new file mode 100644 (file)
index 0000000..5e493e2
--- /dev/null
@@ -0,0 +1,8 @@
+FROM alpine:3.17
+RUN apk add --no-cache bison coreutils gcc libc-dev make
+ncurses-dev libncurses5-dev libncursesw5-dev
+WORKDIR /tmp/bash
+COPY . .
+RUN ./configure --enable-readline --with-curses
+RUN make
+RUN make tests
index d06b1a33b58e118892478036906c38dd77ab5f7c..1a8b8a205144d15d4f86ee6e5817bc87bc0317a5 100644 (file)
@@ -78,6 +78,8 @@ env -i BASH_FUNC_x%%='() { _; } >_[${ $() }] { id; }' ${THIS_SH} -c : 2>/dev/nul
 env BASH_FUNC_x%%=$'() { _;}>_[$($())]\n{ echo vuln;}' ${THIS_SH} -c : 2>/dev/null
 eval 'x() { _;}>_[$($())] { echo vuln;}' 2>/dev/null
 
+echo this will fail now that '${ ' has syntactic meaning
+
 eval 'foo() { _; } >_[${ $() }] ;{ echo eval ok; }'
 
 # other tests fixed in bash43-030 concerning function name transformation