From: Chet Ramey Date: Tue, 9 May 2023 14:33:56 +0000 (-0400) Subject: subshells should not inherit FIFOs; initial framework for nofork (foreground) command... X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=c375f8f45f4882db3098f9b54a19b75d2b176537;p=thirdparty%2Fbash.git subshells should not inherit FIFOs; initial framework for nofork (foreground) command substitutions --- diff --git a/CWRU/CWRU.chlog b/CWRU/CWRU.chlog index 9c287897f..c8b379bfb 100644 --- a/CWRU/CWRU.chlog +++ b/CWRU/CWRU.chlog @@ -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 + +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 diff --git a/Makefile.in b/Makefile.in index 115c6a210..7f990bfa5 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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 diff --git a/doc/bash.1 b/doc/bash.1 index bee814ae1..06408e0c7 100644 --- a/doc/bash.1 +++ b/doc/bash.1 @@ -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. diff --git a/doc/bashref.texi b/doc/bashref.texi index 3263a73ca..ff9c2f412 100644 --- a/doc/bashref.texi +++ b/doc/bashref.texi @@ -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: diff --git a/execute_cmd.c b/execute_cmd.c index 77cecb317..c55fd92e4 100644 --- a/execute_cmd.c +++ b/execute_cmd.c @@ -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 bb7e79eea..2ca3d600f 100644 --- a/parse.y +++ b/parse.y @@ -394,7 +394,7 @@ static FILE *yyerrstream; %type cond_command %type arith_for_command %type coproc -%type comsub +%type comsub funsub %type function_def function_body if_command elif_clause subshell %type redirection redirection_list %type 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 }; diff --git a/parser.h b/parser.h index a27dc06b0..3efdd798e 100644 --- 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 bdb5ba88e..c2ff4c130 100644 --- 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 a42d8c84c..cb47cc9ae 100644 --- 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 index 000000000..5e493e2cf --- /dev/null +++ b/support/Dockerfile @@ -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 diff --git a/tests/exportfunc.tests b/tests/exportfunc.tests index d06b1a33b..1a8b8a205 100644 --- a/tests/exportfunc.tests +++ b/tests/exportfunc.tests @@ -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