From 216913c290fd2b88b744c04c0a2ef21fd1410ba9 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 2 Apr 2018 12:35:04 +0200 Subject: ash: parser: Add syntax stack for recursive parsing This closes 10821. Upstream patch: From: Herbert Xu Date: Fri, 9 Mar 2018 00:14:02 +0800 parser: Add syntax stack for recursive parsing Without a stack of syntaxes we cannot correctly these two cases together: "${a#'$$'}" "${a#"${b-'$$'}"}" A recursive parser also helps in some other corner cases such as nested arithmetic expansion with paratheses. This patch adds a syntax stack allocated from the stack using alloca. As a side-effect this allows us to remove the naked backslashes for patterns within double-quotes, which means that EXP_QPAT also has to go. This patch also fixes removes any backslashes that precede right braces when they are present within a parameter expansion context, and backslashes that precede double quotes within inner double quotes inside a parameter expansion in a here-document context. The idea of a recursive parser is based on a patch by Harald van Dijk. Signed-off-by: Herbert Xu var_bash3, var_bash4 and var_bash6 tests are updated with the output given by bash-4.3.43 With this patch, the following tests now pass for ash: dollar_repl_slash_bash2.tests squote_in_varexp2.tests squote_in_varexp.tests var_bash4.tests function old new delta readtoken1 2615 2874 +259 synstack_push - 54 +54 evalvar 574 571 -3 rmescapes 330 310 -20 subevalvar 1279 1258 -21 argstr 1146 1107 -39 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 1/4 up/down: 313/-83) Total: 230 bytes Signed-off-by: Denys Vlasenko --- shell/ash.c | 231 ++++++++++++--------- shell/ash_test/ash-arith/arith_nested1.right | 1 + shell/ash_test/ash-arith/arith_nested1.tests | 1 + shell/ash_test/ash-quoting/squote_in_varexp3.right | 1 + shell/ash_test/ash-quoting/squote_in_varexp3.tests | 1 + shell/ash_test/ash-vars/var_bash3.right | 4 +- shell/ash_test/ash-vars/var_bash4.right | 16 +- shell/ash_test/ash-vars/var_bash6.right | 2 +- shell/ash_test/ash-vars/var_bash6.tests | 2 +- shell/ash_test/ash-vars/var_bash7.right | 1 + shell/ash_test/ash-vars/var_bash7.tests | 1 + shell/hush_test/hush-arith/arith_nested1.right | 1 + shell/hush_test/hush-arith/arith_nested1.tests | 1 + .../hush_test/hush-quoting/squote_in_varexp3.right | 1 + .../hush_test/hush-quoting/squote_in_varexp3.tests | 1 + shell/hush_test/hush-vars/var_bash3.right | 4 +- shell/hush_test/hush-vars/var_bash4.right | 16 +- shell/hush_test/hush-vars/var_bash6.right | 2 +- shell/hush_test/hush-vars/var_bash6.tests | 2 +- 19 files changed, 168 insertions(+), 121 deletions(-) create mode 100644 shell/ash_test/ash-arith/arith_nested1.right create mode 100755 shell/ash_test/ash-arith/arith_nested1.tests create mode 100644 shell/ash_test/ash-quoting/squote_in_varexp3.right create mode 100755 shell/ash_test/ash-quoting/squote_in_varexp3.tests create mode 100644 shell/ash_test/ash-vars/var_bash7.right create mode 100755 shell/ash_test/ash-vars/var_bash7.tests create mode 100644 shell/hush_test/hush-arith/arith_nested1.right create mode 100755 shell/hush_test/hush-arith/arith_nested1.tests create mode 100644 shell/hush_test/hush-quoting/squote_in_varexp3.right create mode 100755 shell/hush_test/hush-quoting/squote_in_varexp3.tests diff --git a/shell/ash.c b/shell/ash.c index cf1d062fb..97379cd92 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -5888,10 +5888,9 @@ static int substr_atoi(const char *s) * performs globbing, and thus diverges from what we do). */ #define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ -#define EXP_QPAT 0x20 /* pattern in quoted parameter expansion */ -#define EXP_VARTILDE2 0x40 /* expand tildes after colons only */ -#define EXP_WORD 0x80 /* expand word in parameter expansion */ -#define EXP_QUOTED 0x100 /* expand word in double quotes */ +#define EXP_VARTILDE2 0x20 /* expand tildes after colons only */ +#define EXP_WORD 0x40 /* expand word in parameter expansion */ +#define EXP_QUOTED 0x80 /* expand word in double quotes */ /* * rmescape() flags */ @@ -5901,7 +5900,7 @@ static int substr_atoi(const char *s) #define RMESCAPE_HEAP 0x10 /* Malloc strings instead of stalloc */ /* Add CTLESC when necessary. */ -#define QUOTES_ESC (EXP_FULL | EXP_CASE | EXP_QPAT) +#define QUOTES_ESC (EXP_FULL | EXP_CASE) /* Do not skip NUL characters. */ #define QUOTES_KEEPNUL EXP_TILDE @@ -6090,7 +6089,6 @@ rmescapes(char *str, int flag, int *slash_position) IF_BASH_PATTERN_SUBST('/',) CTLESC, CTLQUOTEMARK, '\0' }; char *p, *q, *r; - unsigned inquotes; unsigned protect_against_glob; unsigned globbing; @@ -6121,18 +6119,21 @@ rmescapes(char *str, int flag, int *slash_position) } } - inquotes = 0; globbing = flag & RMESCAPE_GLOB; protect_against_glob = globbing; while (*p) { if ((unsigned char)*p == CTLQUOTEMARK) { -// Note: both inquotes and protect_against_glob only affect whether +// Note: protect_against_glob only affect whether // CTLESC, gets converted to or to \ - inquotes = ~inquotes; p++; protect_against_glob = globbing; continue; } + if (*p == '\\') { + /* naked back slash */ + protect_against_glob = 0; + goto copy; + } if ((unsigned char)*p == CTLESC) { p++; #if DEBUG @@ -6168,10 +6169,6 @@ rmescapes(char *str, int flag, int *slash_position) *q++ = '\\'; } } - } else if (*p == '\\' && !inquotes) { - /* naked back slash */ - protect_against_glob = 0; - goto copy; } #if BASH_PATTERN_SUBST else if (slash_position && p == str + *slash_position) { @@ -6669,16 +6666,6 @@ argstr(char *p, int flags) case CTLESC: startloc++; length++; - - /* - * Quoted parameter expansion pattern: remove quote - * unless inside inner quotes or we have a literal - * backslash. - */ - if (((flags | inquotes) & (EXP_QPAT | EXP_QUOTED)) == - EXP_QPAT && *p != '\\') - break; - goto addquote; case CTLVAR: TRACE(("argstr: evalvar('%s')\n", p)); @@ -6869,15 +6856,24 @@ subevalvar(char *p, char *varname, int strloc, int subtype, } #endif argstr_flags = EXP_TILDE; - if (subtype != VSASSIGN && subtype != VSQUESTION) - argstr_flags |= (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE); + if (subtype != VSASSIGN + && subtype != VSQUESTION +#if BASH_SUBSTR + && subtype != VSSUBSTR +#endif + ) { + /* EXP_CASE keeps CTLESC's */ + argstr_flags = EXP_TILDE | EXP_CASE; + } argstr(p, argstr_flags); + //bb_error_msg("str0:'%s'", (char *)stackblock() + strloc); #if BASH_PATTERN_SUBST slash_pos = -1; if (repl) { slash_pos = expdest - ((char *)stackblock() + strloc); STPUTC('/', expdest); - argstr(repl + 1, argstr_flags); + //bb_error_msg("repl+1:'%s'", repl + 1); + argstr(repl + 1, EXP_TILDE); /* EXP_TILDE: echo "${v/x/~}" expands ~ ! */ *repl = '/'; } #endif @@ -10669,6 +10665,34 @@ pgetc_eatbnl(void) return c; } +struct synstack { + smalluint syntax; + uint8_t innerdq :1; + uint8_t varpushed :1; + uint8_t dblquote :1; + int varnest; /* levels of variables expansion */ + int dqvarnest; /* levels of variables expansion within double quotes */ + int parenlevel; /* levels of parens in arithmetic */ + struct synstack *prev; + struct synstack *next; +}; + +static void +synstack_push(struct synstack **stack, struct synstack *next, int syntax) +{ + memset(next, 0, sizeof(*next)); + next->syntax = syntax; + next->next = *stack; + (*stack)->prev = next; + *stack = next; +} + +static ALWAYS_INLINE void +synstack_pop(struct synstack **stack) +{ + *stack = (*stack)->next; +} + /* * To handle the "." command, a stack of input files is used. Pushfile * adds a new entry to the stack and popfile restores the previous level. @@ -11928,19 +11952,13 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) size_t len; struct nodelist *bqlist; smallint quotef; - smallint dblquote; smallint oldstyle; - IF_FEATURE_SH_MATH(smallint prevsyntax;) /* syntax before arithmetic */ smallint pssyntax; /* we are expanding a prompt string */ - int varnest; /* levels of variables expansion */ - IF_FEATURE_SH_MATH(int arinest;) /* levels of arithmetic expansion */ - IF_FEATURE_SH_MATH(int parenlevel;) /* levels of parens in arithmetic */ - int dqvarnest; /* levels of variables expansion within double quotes */ IF_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;) + /* syntax stack */ + struct synstack synbase = { .syntax = syntax }; + struct synstack *synstack = &synbase; - bqlist = NULL; - quotef = 0; - IF_FEATURE_SH_MATH(prevsyntax = 0;) #if ENABLE_ASH_EXPAND_PRMT pssyntax = (syntax == PSSYNTAX); if (pssyntax) @@ -11948,11 +11966,10 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) #else pssyntax = 0; /* constant */ #endif - dblquote = (syntax == DQSYNTAX); - varnest = 0; - IF_FEATURE_SH_MATH(arinest = 0;) - IF_FEATURE_SH_MATH(parenlevel = 0;) - dqvarnest = 0; + if (syntax == DQSYNTAX) + synstack->dblquote = 1; + quotef = 0; + bqlist = NULL; STARTSTACKSTR(out); loop: @@ -11960,9 +11977,9 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) CHECKEND(); /* set c to PEOF if at end of here document */ for (;;) { /* until end of line or end of word */ CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */ - switch (SIT(c, syntax)) { + switch (SIT(c, synstack->syntax)) { case CNL: /* '\n' */ - if (syntax == BASESYNTAX) + if (synstack->syntax == BASESYNTAX) goto endword; /* exit outer loop */ USTPUTC(c, out); nlprompt(); @@ -11982,13 +11999,13 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) if (c & 0x100) { /* Unknown escape. Encode as '\z' */ c = (unsigned char)c; - if (eofmark == NULL || dblquote) + if (eofmark == NULL || synstack->dblquote) USTPUTC(CTLESC, out); USTPUTC('\\', out); } } #endif - if (eofmark == NULL || dblquote) + if (eofmark == NULL || synstack->dblquote) USTPUTC(CTLESC, out); USTPUTC(c, out); break; @@ -12008,20 +12025,13 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) /* Backslash is retained if we are in "str" * and next char isn't dquote-special. */ - if (dblquote + if (synstack->dblquote && c != '\\' && c != '`' && c != '$' - && (c != '"' || eofmark != NULL) + && (c != '"' || (eofmark != NULL && !synstack->varnest)) + && (c != '}' || !synstack->varnest) ) { -//dash survives not doing USTPUTC(CTLESC), but merely by chance: -//Example: "\z" gets encoded as "\z". -//rmescapes() then emits "\", "\z", protecting z from globbing. -//But it's wrong, should protect _both_ from globbing: -//everything in double quotes is not globbed. -//Unlike dash, we have a fix in rmescapes() which emits bare "z" -//for "z" since "z" is not glob-special (else unicode may break), -//and glob would see "\z" and eat "\". Thus: USTPUTC(CTLESC, out); /* protect '\' from glob */ USTPUTC('\\', out); } @@ -12031,56 +12041,62 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) } break; case CSQUOTE: - syntax = SQSYNTAX; + synstack->syntax = SQSYNTAX; quotemark: if (eofmark == NULL) { USTPUTC(CTLQUOTEMARK, out); } break; case CDQUOTE: - syntax = DQSYNTAX; - dblquote = 1; + synstack->syntax = DQSYNTAX; + synstack->dblquote = 1; + toggledq: + if (synstack->varnest) + synstack->innerdq ^= 1; goto quotemark; case CENDQUOTE: IF_BASH_DOLLAR_SQUOTE(bash_dollar_squote = 0;) - if (eofmark != NULL && varnest == 0) { + if (eofmark != NULL && synstack->varnest == 0) { USTPUTC(c, out); - } else { - if (dqvarnest == 0) { - syntax = BASESYNTAX; - dblquote = 0; - } - quotef = 1; - goto quotemark; + break; } - break; + + if (synstack->dqvarnest == 0) { + synstack->syntax = BASESYNTAX; + synstack->dblquote = 0; + } + + quotef = 1; + + if (c == '"') + goto toggledq; + + goto quotemark; case CVAR: /* '$' */ PARSESUB(); /* parse substitution */ break; case CENDVAR: /* '}' */ - if (varnest > 0) { - varnest--; - if (dqvarnest > 0) { - dqvarnest--; - } + if (!synstack->innerdq && synstack->varnest > 0) { + if (!--synstack->varnest && synstack->varpushed) + synstack_pop(&synstack); + else if (synstack->dqvarnest > 0) + synstack->dqvarnest--; c = CTLENDVAR; } USTPUTC(c, out); break; #if ENABLE_FEATURE_SH_MATH case CLP: /* '(' in arithmetic */ - parenlevel++; + synstack->parenlevel++; USTPUTC(c, out); break; case CRP: /* ')' in arithmetic */ - if (parenlevel > 0) { - parenlevel--; + if (synstack->parenlevel > 0) { + synstack->parenlevel--; } else { if (pgetc_eatbnl() == ')') { c = CTLENDARI; - if (--arinest == 0) { - syntax = prevsyntax; - } + synstack_pop(&synstack); } else { /* * unbalanced parens @@ -12106,7 +12122,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) case CIGN: break; default: - if (varnest == 0) { + if (synstack->varnest == 0) { #if BASH_REDIR_OUTPUT if (c == '&') { //Can't call pgetc_eatbnl() here, this requires three-deep pungetc() @@ -12125,12 +12141,12 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) endword: #if ENABLE_FEATURE_SH_MATH - if (syntax == ARISYNTAX) + if (synstack->syntax == ARISYNTAX) raise_error_syntax("missing '))'"); #endif - if (syntax != BASESYNTAX && eofmark == NULL) + if (synstack->syntax != BASESYNTAX && eofmark == NULL) raise_error_syntax("unterminated quoted string"); - if (varnest != 0) { + if (synstack->varnest != 0) { /* { */ raise_error_syntax("missing '}'"); } @@ -12312,7 +12328,7 @@ parsesub: { || (c != '(' && c != '{' && !is_name(c) && !is_special(c)) ) { #if BASH_DOLLAR_SQUOTE - if (syntax != DQSYNTAX && c == '\'') + if (synstack->syntax != DQSYNTAX && c == '\'') bash_dollar_squote = 1; else #endif @@ -12332,6 +12348,8 @@ parsesub: { } } else { /* $VAR, $, ${...}, or PEOA/PEOF */ + smalluint newsyn = synstack->syntax; + USTPUTC(CTLVAR, out); typeloc = out - (char *)stackblock(); STADJUST(1, out); @@ -12390,6 +12408,8 @@ parsesub: { static const char types[] ALIGN1 = "}-+?="; /* ${VAR...} but not $VAR or ${#VAR} */ /* c == first char after VAR */ + int cc = c; + switch (c) { case ':': c = pgetc_eatbnl(); @@ -12414,21 +12434,24 @@ parsesub: { break; } case '%': - case '#': { - int cc = c; + case '#': subtype = (c == '#' ? VSTRIMLEFT : VSTRIMRIGHT); c = pgetc_eatbnl(); - if (c != cc) - goto badsub; - subtype++; + if (c == cc) + subtype++; + else + pungetc(); + + newsyn = BASESYNTAX; break; - } #if BASH_PATTERN_SUBST case '/': /* ${v/[/]pattern/repl} */ //TODO: encode pattern and repl separately. -// Currently ${v/$var_with_slash/repl} is horribly broken +// Currently cases like: v=1;echo ${v/$((1/1))/ONE} +// are broken (should print "ONE") subtype = VSREPLACE; + newsyn = BASESYNTAX; c = pgetc_eatbnl(); if (c != '/') goto badsub; @@ -12440,11 +12463,24 @@ parsesub: { badsub: pungetc(); } + + if (newsyn == ARISYNTAX && subtype > VSNORMAL) + newsyn = DQSYNTAX; + + if (newsyn != synstack->syntax) { + synstack_push(&synstack, + synstack->prev ?: alloca(sizeof(*synstack)), + newsyn); + + synstack->varpushed = 1; + synstack->dblquote = newsyn != BASESYNTAX; + } + ((unsigned char *)stackblock())[typeloc] = subtype; if (subtype != VSNORMAL) { - varnest++; - if (dblquote) - dqvarnest++; + synstack->varnest++; + if (synstack->dblquote) + synstack->dqvarnest++; } STPUTC('=', out); } @@ -12501,7 +12537,7 @@ parsebackq: { case '\\': pc = pgetc(); /* or pgetc_eatbnl()? why (example)? */ if (pc != '\\' && pc != '`' && pc != '$' - && (!dblquote || pc != '"') + && (!synstack->dblquote || pc != '"') ) { STPUTC('\\', pout); } @@ -12576,10 +12612,11 @@ parsebackq: { * Parse an arithmetic expansion (indicate start of one and set state) */ parsearith: { - if (++arinest == 1) { - prevsyntax = syntax; - syntax = ARISYNTAX; - } + + synstack_push(&synstack, + synstack->prev ?: alloca(sizeof(*synstack)), + ARISYNTAX); + synstack->dblquote = 1; USTPUTC(CTLARI, out); goto parsearith_return; } diff --git a/shell/ash_test/ash-arith/arith_nested1.right b/shell/ash_test/ash-arith/arith_nested1.right new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/shell/ash_test/ash-arith/arith_nested1.right @@ -0,0 +1 @@ +1 diff --git a/shell/ash_test/ash-arith/arith_nested1.tests b/shell/ash_test/ash-arith/arith_nested1.tests new file mode 100755 index 000000000..28571b833 --- /dev/null +++ b/shell/ash_test/ash-arith/arith_nested1.tests @@ -0,0 +1 @@ +echo $(( ( $((1)) ) )) diff --git a/shell/ash_test/ash-quoting/squote_in_varexp3.right b/shell/ash_test/ash-quoting/squote_in_varexp3.right new file mode 100644 index 000000000..223b7836f --- /dev/null +++ b/shell/ash_test/ash-quoting/squote_in_varexp3.right @@ -0,0 +1 @@ +B diff --git a/shell/ash_test/ash-quoting/squote_in_varexp3.tests b/shell/ash_test/ash-quoting/squote_in_varexp3.tests new file mode 100755 index 000000000..028a88fd9 --- /dev/null +++ b/shell/ash_test/ash-quoting/squote_in_varexp3.tests @@ -0,0 +1 @@ +x=\'B; echo "${x#\'}" diff --git a/shell/ash_test/ash-vars/var_bash3.right b/shell/ash_test/ash-vars/var_bash3.right index a97c850ea..8899d981c 100644 --- a/shell/ash_test/ash-vars/var_bash3.right +++ b/shell/ash_test/ash-vars/var_bash3.right @@ -1,6 +1,6 @@ 1 a041#c 2 a041#c -3 a\041#c +3 a041#c 4 a\041#c 5 a\041#c 6 a\041#c @@ -17,4 +17,4 @@ 17 a\tc 18 a\tc 19 atc -20 a\tc +20 atc diff --git a/shell/ash_test/ash-vars/var_bash4.right b/shell/ash_test/ash-vars/var_bash4.right index 0ef1bf661..9067e58e6 100644 --- a/shell/ash_test/ash-vars/var_bash4.right +++ b/shell/ash_test/ash-vars/var_bash4.right @@ -3,26 +3,26 @@ Replace str: _\\_\z_ Pattern: single backslash and star: "replace literal star" Unquoted: a_\_z_b\*c Unquoted =: a_\_z_b\*c -Quoted: a_\_\z_b\*c -Quoted =: a_\_\z_b\*c +Quoted: a_\_z_b\*c +Quoted =: a_\_z_b\*c Pattern: double backslash and star: "replace backslash and everything after it" Unquoted: a*b_\_z_ Unquoted =: a*b_\_z_ -Quoted: a*b_\_\z_ -Quoted =: a*b_\_\z_ +Quoted: a*b_\_z_ +Quoted =: a*b_\_z_ Source: a\bc Replace str: _\\_\z_ Pattern: single backslash and b: "replace b" Unquoted: a\_\_z_c Unquoted =: a\_\_z_c -Quoted: a\_\_\z_c -Quoted =: a\_\_\z_c +Quoted: a\_\_z_c +Quoted =: a\_\_z_c Pattern: double backslash and b: "replace backslash and b" Unquoted: a_\_z_c Unquoted =: a_\_z_c -Quoted: a_\_\z_c -Quoted =: a_\_\z_c +Quoted: a_\_z_c +Quoted =: a_\_z_c Source: a\bc Replace str: _\\_\z_ (as variable $s) diff --git a/shell/ash_test/ash-vars/var_bash6.right b/shell/ash_test/ash-vars/var_bash6.right index 63fc23df8..115ff8b04 100644 --- a/shell/ash_test/ash-vars/var_bash6.right +++ b/shell/ash_test/ash-vars/var_bash6.right @@ -1,5 +1,5 @@ Expected Actual a*z : a*z -\z : \z +z : z a1z a2z: a1z a2z z : z diff --git a/shell/ash_test/ash-vars/var_bash6.tests b/shell/ash_test/ash-vars/var_bash6.tests index cf2e4f020..686834177 100755 --- a/shell/ash_test/ash-vars/var_bash6.tests +++ b/shell/ash_test/ash-vars/var_bash6.tests @@ -3,7 +3,7 @@ >a1z; >a2z; echo 'Expected' 'Actual' v='a bz'; echo 'a*z :' "${v/a*z/a*z}" -v='a bz'; echo '\z :' "${v/a*z/\z}" +v='a bz'; echo 'z :' "${v/a*z/\z}" v='a bz'; echo 'a1z a2z:' ${v/a*z/a*z} v='a bz'; echo 'z :' ${v/a*z/\z} rm a1z a2z diff --git a/shell/ash_test/ash-vars/var_bash7.right b/shell/ash_test/ash-vars/var_bash7.right new file mode 100644 index 000000000..223b7836f --- /dev/null +++ b/shell/ash_test/ash-vars/var_bash7.right @@ -0,0 +1 @@ +B diff --git a/shell/ash_test/ash-vars/var_bash7.tests b/shell/ash_test/ash-vars/var_bash7.tests new file mode 100755 index 000000000..c4ce03f7f --- /dev/null +++ b/shell/ash_test/ash-vars/var_bash7.tests @@ -0,0 +1 @@ +x=AB; echo "${x#$'\x41'}" diff --git a/shell/hush_test/hush-arith/arith_nested1.right b/shell/hush_test/hush-arith/arith_nested1.right new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/shell/hush_test/hush-arith/arith_nested1.right @@ -0,0 +1 @@ +1 diff --git a/shell/hush_test/hush-arith/arith_nested1.tests b/shell/hush_test/hush-arith/arith_nested1.tests new file mode 100755 index 000000000..28571b833 --- /dev/null +++ b/shell/hush_test/hush-arith/arith_nested1.tests @@ -0,0 +1 @@ +echo $(( ( $((1)) ) )) diff --git a/shell/hush_test/hush-quoting/squote_in_varexp3.right b/shell/hush_test/hush-quoting/squote_in_varexp3.right new file mode 100644 index 000000000..223b7836f --- /dev/null +++ b/shell/hush_test/hush-quoting/squote_in_varexp3.right @@ -0,0 +1 @@ +B diff --git a/shell/hush_test/hush-quoting/squote_in_varexp3.tests b/shell/hush_test/hush-quoting/squote_in_varexp3.tests new file mode 100755 index 000000000..028a88fd9 --- /dev/null +++ b/shell/hush_test/hush-quoting/squote_in_varexp3.tests @@ -0,0 +1 @@ +x=\'B; echo "${x#\'}" diff --git a/shell/hush_test/hush-vars/var_bash3.right b/shell/hush_test/hush-vars/var_bash3.right index a97c850ea..8899d981c 100644 --- a/shell/hush_test/hush-vars/var_bash3.right +++ b/shell/hush_test/hush-vars/var_bash3.right @@ -1,6 +1,6 @@ 1 a041#c 2 a041#c -3 a\041#c +3 a041#c 4 a\041#c 5 a\041#c 6 a\041#c @@ -17,4 +17,4 @@ 17 a\tc 18 a\tc 19 atc -20 a\tc +20 atc diff --git a/shell/hush_test/hush-vars/var_bash4.right b/shell/hush_test/hush-vars/var_bash4.right index 0ef1bf661..9067e58e6 100644 --- a/shell/hush_test/hush-vars/var_bash4.right +++ b/shell/hush_test/hush-vars/var_bash4.right @@ -3,26 +3,26 @@ Replace str: _\\_\z_ Pattern: single backslash and star: "replace literal star" Unquoted: a_\_z_b\*c Unquoted =: a_\_z_b\*c -Quoted: a_\_\z_b\*c -Quoted =: a_\_\z_b\*c +Quoted: a_\_z_b\*c +Quoted =: a_\_z_b\*c Pattern: double backslash and star: "replace backslash and everything after it" Unquoted: a*b_\_z_ Unquoted =: a*b_\_z_ -Quoted: a*b_\_\z_ -Quoted =: a*b_\_\z_ +Quoted: a*b_\_z_ +Quoted =: a*b_\_z_ Source: a\bc Replace str: _\\_\z_ Pattern: single backslash and b: "replace b" Unquoted: a\_\_z_c Unquoted =: a\_\_z_c -Quoted: a\_\_\z_c -Quoted =: a\_\_\z_c +Quoted: a\_\_z_c +Quoted =: a\_\_z_c Pattern: double backslash and b: "replace backslash and b" Unquoted: a_\_z_c Unquoted =: a_\_z_c -Quoted: a_\_\z_c -Quoted =: a_\_\z_c +Quoted: a_\_z_c +Quoted =: a_\_z_c Source: a\bc Replace str: _\\_\z_ (as variable $s) diff --git a/shell/hush_test/hush-vars/var_bash6.right b/shell/hush_test/hush-vars/var_bash6.right index 63fc23df8..115ff8b04 100644 --- a/shell/hush_test/hush-vars/var_bash6.right +++ b/shell/hush_test/hush-vars/var_bash6.right @@ -1,5 +1,5 @@ Expected Actual a*z : a*z -\z : \z +z : z a1z a2z: a1z a2z z : z diff --git a/shell/hush_test/hush-vars/var_bash6.tests b/shell/hush_test/hush-vars/var_bash6.tests index cf2e4f020..686834177 100755 --- a/shell/hush_test/hush-vars/var_bash6.tests +++ b/shell/hush_test/hush-vars/var_bash6.tests @@ -3,7 +3,7 @@ >a1z; >a2z; echo 'Expected' 'Actual' v='a bz'; echo 'a*z :' "${v/a*z/a*z}" -v='a bz'; echo '\z :' "${v/a*z/\z}" +v='a bz'; echo 'z :' "${v/a*z/\z}" v='a bz'; echo 'a1z a2z:' ${v/a*z/a*z} v='a bz'; echo 'z :' ${v/a*z/\z} rm a1z a2z -- cgit v1.2.3-55-g6feb From 8b536eb40d7b4e65e3a0223ada7246f538ac6e98 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 2 Apr 2018 13:26:16 +0200 Subject: hush: remove stray debugging printout Signed-off-by: Denys Vlasenko --- shell/hush.c | 1 - 1 file changed, 1 deletion(-) diff --git a/shell/hush.c b/shell/hush.c index 06fe0e405..1779009e0 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -4240,7 +4240,6 @@ static int fetch_heredocs(int heredoc_cnt, struct parse_context *ctx, struct in_ redir->rd_type = REDIRECT_HEREDOC2; /* redir->rd_dup is (ab)used to indicate <<- */ -bb_error_msg("redir->rd_filename:'%s'", redir->rd_filename); p = fetch_till_str(&ctx->as_string, input, redir->rd_filename, redir->rd_dup); if (!p) { -- cgit v1.2.3-55-g6feb From c4c2012284c3a3a45843e9400379c84855d853ef Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 2 Apr 2018 13:29:20 +0200 Subject: ash: parser: Fix single-quoted patterns in here-documents Upstream commit: From: Herbert Xu Date: Fri, 9 Mar 2018 23:07:53 +0800 parser: Fix single-quoted patterns in here-documents The script x=* cat <<- EOF ${x#'*'} EOF prints * instead of nothing as it should. The problem is that when we're in sqsyntax context in a here-document, we won't add CTLESC as we should. This patch fixes it: Reported-by: Harald van Dijk Signed-off-by: Herbert Xu Signed-off-by: Denys Vlasenko --- shell/ash.c | 2 +- shell/ash_test/ash-heredoc/heredoc_var_expand1.right | 4 ++++ shell/ash_test/ash-heredoc/heredoc_var_expand1.tests | 11 +++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 shell/ash_test/ash-heredoc/heredoc_var_expand1.right create mode 100755 shell/ash_test/ash-heredoc/heredoc_var_expand1.tests diff --git a/shell/ash.c b/shell/ash.c index 97379cd92..70a278f42 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -12005,7 +12005,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) } } #endif - if (eofmark == NULL || synstack->dblquote) + if (!eofmark || synstack->dblquote || synstack->varnest) USTPUTC(CTLESC, out); USTPUTC(c, out); break; diff --git a/shell/ash_test/ash-heredoc/heredoc_var_expand1.right b/shell/ash_test/ash-heredoc/heredoc_var_expand1.right new file mode 100644 index 000000000..eb221832d --- /dev/null +++ b/shell/ash_test/ash-heredoc/heredoc_var_expand1.right @@ -0,0 +1,4 @@ + +Ok1:0 + +Ok2:0 diff --git a/shell/ash_test/ash-heredoc/heredoc_var_expand1.tests b/shell/ash_test/ash-heredoc/heredoc_var_expand1.tests new file mode 100755 index 000000000..3b00bab7b --- /dev/null +++ b/shell/ash_test/ash-heredoc/heredoc_var_expand1.tests @@ -0,0 +1,11 @@ +x='*' + +cat <<- EOF + ${x#'*'} +EOF +echo Ok1:$? + +cat < Date: Mon, 2 Apr 2018 13:34:57 +0200 Subject: ash: redir: Fix typo in noclobber code Upstream commit "redir: Fix typo in noclobber code" Signed-off-by: Denys Vlasenko --- shell/ash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/ash.c b/shell/ash.c index 70a278f42..35ea58f3a 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -5391,7 +5391,7 @@ openredirect(union node *redir) f = open(fname, O_WRONLY, 0666); if (f < 0) goto ecreate; - if (fstat(f, &sb) < 0 && S_ISREG(sb.st_mode)) { + if (!fstat(f, &sb) && S_ISREG(sb.st_mode)) { close(f); errno = EEXIST; goto ecreate; -- cgit v1.2.3-55-g6feb From 9a95df90463ee0eddc0585f0e5affa827701fdfb Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 2 Apr 2018 14:27:50 +0200 Subject: ash: expand: Fix bugs with words connected to the right of $@ Upstream email: This is actually composed of two bugs. First of all our tracking of quotemark is wrong so anything after "$@" becomes quoted. Once we fix that then the problem is that the first space character after "$@" is not recognised as an IFS. This patch fixes both. Signed-off-by: Herbert Xu Signed-off-by: Denys Vlasenko --- shell/ash.c | 11 +++++++---- shell/ash_test/ash-parsing/starquoted3.right | 2 ++ shell/ash_test/ash-parsing/starquoted3.tests | 1 + shell/hush_test/hush-parsing/starquoted3.right | 2 ++ shell/hush_test/hush-parsing/starquoted3.tests | 1 + 5 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 shell/ash_test/ash-parsing/starquoted3.right create mode 100755 shell/ash_test/ash-parsing/starquoted3.tests create mode 100644 shell/hush_test/hush-parsing/starquoted3.right create mode 100755 shell/hush_test/hush-parsing/starquoted3.tests diff --git a/shell/ash.c b/shell/ash.c index 35ea58f3a..d82eba15f 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -5975,7 +5975,10 @@ ifsbreakup(char *string, struct arglist *arglist) realifs = ifsset() ? ifsval() : defifs; ifsp = &ifsfirst; do { + int afternul; + p = string + ifsp->begoff; + afternul = nulonly; nulonly = ifsp->nulonly; ifs = nulonly ? nullstr : realifs; ifsspc = 0; @@ -5987,7 +5990,7 @@ ifsbreakup(char *string, struct arglist *arglist) p++; continue; } - if (!nulonly) + if (!(afternul || nulonly)) ifsspc = (strchr(defifs, *p) != NULL); /* Ignore IFS whitespace at start */ if (q == start && ifsspc) { @@ -6650,12 +6653,12 @@ argstr(char *p, int flags) case CTLENDVAR: /* ??? */ goto breakloop; case CTLQUOTEMARK: - inquotes ^= EXP_QUOTED; /* "$@" syntax adherence hack */ - if (inquotes && !memcmp(p, dolatstr + 1, DOLATSTRLEN - 1)) { - p = evalvar(p + 1, flags | inquotes) + 1; + if (!inquotes && !memcmp(p, dolatstr + 1, DOLATSTRLEN - 1)) { + p = evalvar(p + 1, flags | EXP_QUOTED) + 1; goto start; } + inquotes ^= EXP_QUOTED; addquote: if (flags & QUOTES_ESC) { p--; diff --git a/shell/ash_test/ash-parsing/starquoted3.right b/shell/ash_test/ash-parsing/starquoted3.right new file mode 100644 index 000000000..fea246c14 --- /dev/null +++ b/shell/ash_test/ash-parsing/starquoted3.right @@ -0,0 +1,2 @@ + +<> diff --git a/shell/ash_test/ash-parsing/starquoted3.tests b/shell/ash_test/ash-parsing/starquoted3.tests new file mode 100755 index 000000000..8eefe4245 --- /dev/null +++ b/shell/ash_test/ash-parsing/starquoted3.tests @@ -0,0 +1 @@ +set -- a ""; space=" "; printf "<%s>\n" "$@"$space diff --git a/shell/hush_test/hush-parsing/starquoted3.right b/shell/hush_test/hush-parsing/starquoted3.right new file mode 100644 index 000000000..fea246c14 --- /dev/null +++ b/shell/hush_test/hush-parsing/starquoted3.right @@ -0,0 +1,2 @@ + +<> diff --git a/shell/hush_test/hush-parsing/starquoted3.tests b/shell/hush_test/hush-parsing/starquoted3.tests new file mode 100755 index 000000000..8eefe4245 --- /dev/null +++ b/shell/hush_test/hush-parsing/starquoted3.tests @@ -0,0 +1 @@ +set -- a ""; space=" "; printf "<%s>\n" "$@"$space -- cgit v1.2.3-55-g6feb From abf755615e5f20c3bbe7534fa29c72fd684ea616 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 2 Apr 2018 17:25:18 +0200 Subject: hush: fix a backslash-removal bug in case function old new delta run_list 1270 1053 -217 Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-quoting/bkslash_case2.right | 3 +++ shell/ash_test/ash-quoting/bkslash_case2.tests | 13 +++++++++++++ shell/hush.c | 8 +++++--- shell/hush_test/hush-quoting/bkslash_case2.right | 3 +++ shell/hush_test/hush-quoting/bkslash_case2.tests | 13 +++++++++++++ 5 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 shell/ash_test/ash-quoting/bkslash_case2.right create mode 100755 shell/ash_test/ash-quoting/bkslash_case2.tests create mode 100644 shell/hush_test/hush-quoting/bkslash_case2.right create mode 100755 shell/hush_test/hush-quoting/bkslash_case2.tests diff --git a/shell/ash_test/ash-quoting/bkslash_case2.right b/shell/ash_test/ash-quoting/bkslash_case2.right new file mode 100644 index 000000000..8d2038bff --- /dev/null +++ b/shell/ash_test/ash-quoting/bkslash_case2.right @@ -0,0 +1,3 @@ +ok1 +ok2 +Ok:0 diff --git a/shell/ash_test/ash-quoting/bkslash_case2.tests b/shell/ash_test/ash-quoting/bkslash_case2.tests new file mode 100755 index 000000000..348ddc236 --- /dev/null +++ b/shell/ash_test/ash-quoting/bkslash_case2.tests @@ -0,0 +1,13 @@ +x='\abc' + +case "$x" in +\\*) echo ok1;; +*) echo BUG1;; +esac + +case $x in +\\*) echo ok2;; +*) echo BUG2;; +esac + +echo Ok:$? diff --git a/shell/hush.c b/shell/hush.c index 1779009e0..867a921ec 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -6347,7 +6347,7 @@ static char *expand_string_to_string(const char *str, int do_unbackslash) return (char*)list; } -#if ENABLE_HUSH_CASE +#if 0 static char* expand_strvec_to_string(char **argv) { char **list; @@ -8731,8 +8731,10 @@ static int run_list(struct pipe *pi) #if ENABLE_HUSH_CASE if (rword == RES_CASE) { debug_printf_exec("CASE cond_code:%d\n", cond_code); - case_word = expand_strvec_to_string(pi->cmds->argv); - unbackslash(case_word); + case_word = expand_string_to_string(pi->cmds->argv[0], 1); + debug_printf_exec("CASE word1:'%s'\n", case_word); + //unbackslash(case_word); + //debug_printf_exec("CASE word2:'%s'\n", case_word); continue; } if (rword == RES_MATCH) { diff --git a/shell/hush_test/hush-quoting/bkslash_case2.right b/shell/hush_test/hush-quoting/bkslash_case2.right new file mode 100644 index 000000000..8d2038bff --- /dev/null +++ b/shell/hush_test/hush-quoting/bkslash_case2.right @@ -0,0 +1,3 @@ +ok1 +ok2 +Ok:0 diff --git a/shell/hush_test/hush-quoting/bkslash_case2.tests b/shell/hush_test/hush-quoting/bkslash_case2.tests new file mode 100755 index 000000000..348ddc236 --- /dev/null +++ b/shell/hush_test/hush-quoting/bkslash_case2.tests @@ -0,0 +1,13 @@ +x='\abc' + +case "$x" in +\\*) echo ok1;; +*) echo BUG1;; +esac + +case $x in +\\*) echo ok2;; +*) echo BUG2;; +esac + +echo Ok:$? -- cgit v1.2.3-55-g6feb From f50e14632f7be56da7a38937c887f77812803f70 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 2 Apr 2018 21:00:59 +0200 Subject: ash: parser: Fix parameter expansion inside inner double quotes Upstream email: parser: Fix parameter expansion inside inner double quotes The parsing of parameter expansion inside inner double quotes breaks because we never look for ENDVAR while innerdq is true. echo "${x#"${x+''}"''} This patch fixes it by pushing the syntax stack if innerdq is true and we enter a new parameter expansion. This patch also fixes a corner case where a bad substitution error occurs within arithmetic expansion. Reported-by: Denys Vlasenko Fixes: ab1cecb40478 (" parser: Add syntax stack for recursive...") Signed-off-by: Herbert Xu function old new delta readtoken1 2880 2898 +18 Signed-off-by: Denys Vlasenko --- shell/ash.c | 6 ++++-- shell/ash_test/ash-quoting/quote_in_varexp1.right | 2 ++ shell/ash_test/ash-quoting/quote_in_varexp1.tests | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 shell/ash_test/ash-quoting/quote_in_varexp1.right create mode 100755 shell/ash_test/ash-quoting/quote_in_varexp1.tests diff --git a/shell/ash.c b/shell/ash.c index d82eba15f..ed1a4416c 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -12467,10 +12467,12 @@ parsesub: { pungetc(); } - if (newsyn == ARISYNTAX && subtype > VSNORMAL) + if (newsyn == ARISYNTAX) newsyn = DQSYNTAX; - if (newsyn != synstack->syntax) { + if ((newsyn != synstack->syntax || synstack->innerdq) + && subtype != VSNORMAL + ) { synstack_push(&synstack, synstack->prev ?: alloca(sizeof(*synstack)), newsyn); diff --git a/shell/ash_test/ash-quoting/quote_in_varexp1.right b/shell/ash_test/ash-quoting/quote_in_varexp1.right new file mode 100644 index 000000000..99a0aea7c --- /dev/null +++ b/shell/ash_test/ash-quoting/quote_in_varexp1.right @@ -0,0 +1,2 @@ +'' +Ok:0 diff --git a/shell/ash_test/ash-quoting/quote_in_varexp1.tests b/shell/ash_test/ash-quoting/quote_in_varexp1.tests new file mode 100755 index 000000000..1b97b0556 --- /dev/null +++ b/shell/ash_test/ash-quoting/quote_in_varexp1.tests @@ -0,0 +1,2 @@ +x="''''"; echo "${x#"${x+''}"''}" +echo Ok:$? -- cgit v1.2.3-55-g6feb From 11752d46d18ff4a04a9f6736d129a82756a65a22 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 3 Apr 2018 08:20:58 +0200 Subject: hush: one-word, no-globbing handling of local/export/readonly args function old new delta done_word 738 790 +52 Signed-off-by: Denys Vlasenko --- shell/hush.c | 53 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index 867a921ec..184d720f5 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -79,20 +79,6 @@ * Some builtins mandated by standards: * newgrp [GRP]: not a builtin in bash but a suid binary * which spawns a new shell with new group ID - * In bash, export builtin is special, its arguments are assignments - * and therefore expansion of them should be "one-word" expansion: - * $ export i=`echo 'a b'` # export has one arg: "i=a b" - * compare with: - * $ ls i=`echo 'a b'` # ls has two args: "i=a" and "b" - * ls: cannot access i=a: No such file or directory - * ls: cannot access b: No such file or directory - * Note1: same applies to local builtin. - * Note2: bash 3.2.33(1) does this only if export word itself - * is not quoted: - * $ export i=`echo 'aaa bbb'`; echo "$i" - * aaa bbb - * $ "export" i=`echo 'aaa bbb'`; echo "$i" - * aaa */ //config:config HUSH //config: bool "hush (64 kb)" @@ -630,8 +616,10 @@ struct command { smallint cmd_type; /* CMD_xxx */ #define CMD_NORMAL 0 #define CMD_SUBSHELL 1 -#if BASH_TEST2 -/* used for "[[ EXPR ]]" */ +#if BASH_TEST2 || ENABLE_HUSH_LOCAL || ENABLE_HUSH_EXPORT || ENABLE_HUSH_READONLY +/* used for "[[ EXPR ]]", and to prevent word splitting and globbing in + * "export v=t*" + */ # define CMD_SINGLEWORD_NOGLOB 2 #endif #if ENABLE_HUSH_FUNCTIONS @@ -3933,14 +3921,37 @@ static int done_word(o_string *word, struct parse_context *ctx) (ctx->ctx_res_w == RES_SNTX)); return (ctx->ctx_res_w == RES_SNTX); } -# if BASH_TEST2 - if (strcmp(word->data, "[[") == 0) { +# if defined(CMD_SINGLEWORD_NOGLOB) + if (0 +# if BASH_TEST2 + || strcmp(word->data, "[[") == 0 +# endif + /* In bash, local/export/readonly are special, args + * are assignments and therefore expansion of them + * should be "one-word" expansion: + * $ export i=`echo 'a b'` # one arg: "i=a b" + * compare with: + * $ ls i=`echo 'a b'` # two args: "i=a" and "b" + * ls: cannot access i=a: No such file or directory + * ls: cannot access b: No such file or directory + * Note: bash 3.2.33(1) does this only if export word + * itself is not quoted: + * $ export i=`echo 'aaa bbb'`; echo "$i" + * aaa bbb + * $ "export" i=`echo 'aaa bbb'`; echo "$i" + * aaa + */ + IF_HUSH_LOCAL( || strcmp(word->data, "local") == 0) + IF_HUSH_EXPORT( || strcmp(word->data, "export") == 0) + IF_HUSH_READONLY( || strcmp(word->data, "readonly") == 0) + ) { command->cmd_type = CMD_SINGLEWORD_NOGLOB; } /* fall through */ # endif } -#endif +#endif /* HAS_KEYWORDS */ + if (command->group) { /* "{ echo foo; } echo bar" - bad */ syntax_error_at(word->data); @@ -6299,7 +6310,7 @@ static char **expand_strvec_to_strvec(char **argv) return expand_variables(argv, EXP_FLAG_GLOB | EXP_FLAG_ESC_GLOB_CHARS); } -#if BASH_TEST2 +#if defined(CMD_SINGLEWORD_NOGLOB) static char **expand_strvec_to_strvec_singleword_noglob(char **argv) { return expand_variables(argv, EXP_FLAG_SINGLEWORD); @@ -8292,7 +8303,7 @@ static NOINLINE int run_pipe(struct pipe *pi) } /* Expand the rest into (possibly) many strings each */ -#if BASH_TEST2 +#if defined(CMD_SINGLEWORD_NOGLOB) if (command->cmd_type == CMD_SINGLEWORD_NOGLOB) { argv_expanded = expand_strvec_to_strvec_singleword_noglob(argv + command->assignment_cnt); } else -- cgit v1.2.3-55-g6feb From 5fa0505f8a74848ce4d2a7a4ed905e1bb8af3fe6 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 3 Apr 2018 11:21:13 +0200 Subject: hush: fix "set -e; false || x=1; echo OK" Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-misc/assignment5.right | 5 +++++ shell/ash_test/ash-misc/assignment5.tests | 9 +++++++++ shell/hush.c | 22 +++++++++++++++------- shell/hush_test/hush-misc/assignment5.right | 5 +++++ shell/hush_test/hush-misc/assignment5.tests | 9 +++++++++ 5 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 shell/ash_test/ash-misc/assignment5.right create mode 100755 shell/ash_test/ash-misc/assignment5.tests create mode 100644 shell/hush_test/hush-misc/assignment5.right create mode 100755 shell/hush_test/hush-misc/assignment5.tests diff --git a/shell/ash_test/ash-misc/assignment5.right b/shell/ash_test/ash-misc/assignment5.right new file mode 100644 index 000000000..a91554c09 --- /dev/null +++ b/shell/ash_test/ash-misc/assignment5.right @@ -0,0 +1,5 @@ +Zero1:0 +Zero2:0 +Zero3:0 +Zero4:0 x:1 y:1 +Three:3 x:1 y:1 diff --git a/shell/ash_test/ash-misc/assignment5.tests b/shell/ash_test/ash-misc/assignment5.tests new file mode 100755 index 000000000..0b8104285 --- /dev/null +++ b/shell/ash_test/ash-misc/assignment5.tests @@ -0,0 +1,9 @@ +true; a=1; echo Zero1:$? +false; a=1; echo Zero2:$? +false || a=1; echo Zero3:$? + +false || x=$? y=`echo $?`; echo Zero4:$? x:$x y:$y +false || x=$? y=`echo $?; exit 3`; echo Three:$? x:$x y:$y + +#ash sets z=1 instead of z=3. disabled for now +#false || x=$? y=`echo $?; exit 3` z=`echo $?`; echo x:$x y:$y z:$z diff --git a/shell/hush.c b/shell/hush.c index 184d720f5..b64993faa 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -894,8 +894,9 @@ struct globals { # define G_flag_return_in_progress 0 #endif smallint exiting; /* used to prevent EXIT trap recursion */ - /* These four support $?, $#, and $1 */ + /* These support $?, $#, and $1 */ smalluint last_exitcode; + smalluint expand_exitcode; smalluint last_bg_pid_exitcode; #if ENABLE_HUSH_SET /* are global_argv and global_argv[1..n] malloced? (note: not [0]) */ @@ -6209,6 +6210,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) * and $IFS-split */ debug_printf_subst("SUBST '%s' first_ch %x\n", arg, first_ch); G.last_exitcode = process_command_subs(&subst_result, arg); + G.expand_exitcode = G.last_exitcode; debug_printf_subst("SUBST RES:%d '%s'\n", G.last_exitcode, subst_result.data); val = subst_result.data; goto store_val; @@ -8245,9 +8247,11 @@ static NOINLINE int run_pipe(struct pipe *pi) #endif if (argv[command->assignment_cnt] == NULL) { - /* Assignments, but no command */ - /* Ensure redirects take effect (that is, create files). - * Try "a=t >file" */ + /* Assignments, but no command. + * Ensure redirects take effect (that is, create files). + * Try "a=t >file" + */ + G.expand_exitcode = 0; #if 0 /* A few cases in testsuite fail with this code. FIXME */ rcode = redirect_and_varexp_helper(&new_env, /*old_vars:*/ NULL, command, &squirrel, /*argv_expanded:*/ NULL); /* Set shell variables */ @@ -8265,7 +8269,7 @@ static NOINLINE int run_pipe(struct pipe *pi) * if evaluating assignment value set $?, retain it. * Try "false; q=`exit 2`; echo $?" - should print 2: */ if (rcode == 0) - rcode = G.last_exitcode; + rcode = G.expand_exitcode; /* Exit, _skipping_ variable restoring code: */ goto clean_up_and_ret0; @@ -8292,9 +8296,13 @@ static NOINLINE int run_pipe(struct pipe *pi) bb_putchar_stderr('\n'); /* Redirect error sets $? to 1. Otherwise, * if evaluating assignment value set $?, retain it. - * Try "false; q=`exit 2`; echo $?" - should print 2: */ + * Else, clear $?: + * false; q=`exit 2`; echo $? - should print 2 + * false; x=1; echo $? - should print 0 + * Because of the 2nd case, we can't just use G.last_exitcode. + */ if (rcode == 0) - rcode = G.last_exitcode; + rcode = G.expand_exitcode; IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) debug_leave(); debug_printf_exec("run_pipe: return %d\n", rcode); diff --git a/shell/hush_test/hush-misc/assignment5.right b/shell/hush_test/hush-misc/assignment5.right new file mode 100644 index 000000000..a91554c09 --- /dev/null +++ b/shell/hush_test/hush-misc/assignment5.right @@ -0,0 +1,5 @@ +Zero1:0 +Zero2:0 +Zero3:0 +Zero4:0 x:1 y:1 +Three:3 x:1 y:1 diff --git a/shell/hush_test/hush-misc/assignment5.tests b/shell/hush_test/hush-misc/assignment5.tests new file mode 100755 index 000000000..0b8104285 --- /dev/null +++ b/shell/hush_test/hush-misc/assignment5.tests @@ -0,0 +1,9 @@ +true; a=1; echo Zero1:$? +false; a=1; echo Zero2:$? +false || a=1; echo Zero3:$? + +false || x=$? y=`echo $?`; echo Zero4:$? x:$x y:$y +false || x=$? y=`echo $?; exit 3`; echo Three:$? x:$x y:$y + +#ash sets z=1 instead of z=3. disabled for now +#false || x=$? y=`echo $?; exit 3` z=`echo $?`; echo x:$x y:$y z:$z -- cgit v1.2.3-55-g6feb From 49015a60cb334ecd0b069f27833a3d50853509fa Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 3 Apr 2018 13:02:43 +0200 Subject: hush: fix mishandling of "true | f() { echo QWE; }" function old new delta run_pipe 1820 1826 +6 Signed-off-by: Denys Vlasenko --- shell/hush.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/shell/hush.c b/shell/hush.c index b64993faa..94e429c0d 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -7705,6 +7705,15 @@ static void pseudo_exec(nommu_save_t *nommu_save, struct command *command, char **argv_expanded) { +#if ENABLE_HUSH_FUNCTIONS + if (command->cmd_type == CMD_FUNCDEF) { + /* Ignore funcdefs in pipes: + * true | f() { cmd } + */ + _exit(0); + } +#endif + if (command->argv) { pseudo_exec_argv(nommu_save, command->argv, command->assignment_cnt, argv_expanded); -- cgit v1.2.3-55-g6feb From fbf44854a3b7a32a0a0ff1a03f4163d25f2d62af Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 3 Apr 2018 14:56:52 +0200 Subject: hush: support "f() (cmd)" functions Many other shells support this construct function old new delta parse_stream 2950 3018 +68 Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-misc/func5.right | 3 -- shell/ash_test/ash-misc/func5.tests | 5 -- shell/ash_test/ash-misc/func_compound1.right | 3 ++ shell/ash_test/ash-misc/func_compound1.tests | 4 ++ shell/hush.c | 79 ++++++++++++++++++---------- shell/hush_test/hush-misc/func5.tests | 5 +- 6 files changed, 59 insertions(+), 40 deletions(-) create mode 100644 shell/ash_test/ash-misc/func_compound1.right create mode 100755 shell/ash_test/ash-misc/func_compound1.tests diff --git a/shell/ash_test/ash-misc/func5.right b/shell/ash_test/ash-misc/func5.right index 2c9d316b3..01e79c32a 100644 --- a/shell/ash_test/ash-misc/func5.right +++ b/shell/ash_test/ash-misc/func5.right @@ -1,6 +1,3 @@ 1 2 3 -1 -2 -3 diff --git a/shell/ash_test/ash-misc/func5.tests b/shell/ash_test/ash-misc/func5.tests index e967208cc..5c33560bc 100755 --- a/shell/ash_test/ash-misc/func5.tests +++ b/shell/ash_test/ash-misc/func5.tests @@ -6,8 +6,3 @@ f 2 f() ( echo $1 ) f 3 - -f() for i in 1 2 3; do - echo $i -done -f diff --git a/shell/ash_test/ash-misc/func_compound1.right b/shell/ash_test/ash-misc/func_compound1.right new file mode 100644 index 000000000..01e79c32a --- /dev/null +++ b/shell/ash_test/ash-misc/func_compound1.right @@ -0,0 +1,3 @@ +1 +2 +3 diff --git a/shell/ash_test/ash-misc/func_compound1.tests b/shell/ash_test/ash-misc/func_compound1.tests new file mode 100755 index 000000000..20c8bf18b --- /dev/null +++ b/shell/ash_test/ash-misc/func_compound1.tests @@ -0,0 +1,4 @@ +f() for i in 1 2 3; do + echo $i +done +f diff --git a/shell/hush.c b/shell/hush.c index 94e429c0d..335107283 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -4297,6 +4297,11 @@ static int parse_group(o_string *dest, struct parse_context *ctx, /* dest contains characters seen prior to ( or {. * Typically it's empty, but for function defs, * it contains function name (without '()'). */ +#if BB_MMU +# define as_string NULL +#else + char *as_string = NULL; +#endif struct pipe *pipe_list; int endch; struct command *command = ctx->command; @@ -4325,7 +4330,7 @@ static int parse_group(o_string *dest, struct parse_context *ctx, do ch = i_getch(input); while (ch == ' ' || ch == '\t' || ch == '\n'); - if (ch != '{') { + if (ch != '{' && ch != '(') { syntax_error_unexpected_ch(ch); return 1; } @@ -4347,13 +4352,13 @@ static int parse_group(o_string *dest, struct parse_context *ctx, } #endif -#if ENABLE_HUSH_FUNCTIONS - skip: -#endif + IF_HUSH_FUNCTIONS(skip:) + endch = '}'; if (ch == '(') { endch = ')'; - command->cmd_type = CMD_SUBSHELL; + IF_HUSH_FUNCTIONS(if (command->cmd_type != CMD_FUNCDEF)) + command->cmd_type = CMD_SUBSHELL; } else { /* bash does not allow "{echo...", requires whitespace */ ch = i_peek(input); @@ -4369,38 +4374,54 @@ static int parse_group(o_string *dest, struct parse_context *ctx, } } - { -#if BB_MMU -# define as_string NULL -#else - char *as_string = NULL; -#endif - pipe_list = parse_stream(&as_string, input, endch); + pipe_list = parse_stream(&as_string, input, endch); #if !BB_MMU - if (as_string) - o_addstr(&ctx->as_string, as_string); + if (as_string) + o_addstr(&ctx->as_string, as_string); #endif - /* empty ()/{} or parse error? */ - if (!pipe_list || pipe_list == ERR_PTR) { - /* parse_stream already emitted error msg */ - if (!BB_MMU) - free(as_string); - debug_printf_parse("parse_group return 1: " - "parse_stream returned %p\n", pipe_list); - return 1; - } - command->group = pipe_list; + + /* empty ()/{} or parse error? */ + if (!pipe_list || pipe_list == ERR_PTR) { + /* parse_stream already emitted error msg */ + if (!BB_MMU) + free(as_string); + debug_printf_parse("parse_group return 1: " + "parse_stream returned %p\n", pipe_list); + return 1; + } #if !BB_MMU - as_string[strlen(as_string) - 1] = '\0'; /* plink ')' or '}' */ - command->group_as_string = as_string; - debug_printf_parse("end of group, remembering as:'%s'\n", - command->group_as_string); + as_string[strlen(as_string) - 1] = '\0'; /* plink ')' or '}' */ + command->group_as_string = as_string; + debug_printf_parse("end of group, remembering as:'%s'\n", + command->group_as_string); #endif -#undef as_string + +#if ENABLE_HUSH_FUNCTIONS + /* Convert "f() (cmds)" to "f() {(cmds)}" */ + if (command->cmd_type == CMD_FUNCDEF && endch == ')') { + struct command *cmd2; + + cmd2 = xzalloc(sizeof(*cmd2)); + cmd2->cmd_type = CMD_SUBSHELL; + cmd2->group = pipe_list; +# if !BB_MMU +//UNTESTED! + cmd2->group_as_string = command->group_as_string; + command->group_as_string = xasprintf("(%s)", command->group_as_string); +# endif + + pipe_list = new_pipe(); + pipe_list->cmds = cmd2; + pipe_list->num_cmds = 1; } +#endif + + command->group = pipe_list; + debug_printf_parse("parse_group return 0\n"); return 0; /* command remains "open", available for possible redirects */ +#undef as_string } #if ENABLE_HUSH_TICK || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_DOLLAR_OPS diff --git a/shell/hush_test/hush-misc/func5.tests b/shell/hush_test/hush-misc/func5.tests index 9c5f9fa48..5c33560bc 100755 --- a/shell/hush_test/hush-misc/func5.tests +++ b/shell/hush_test/hush-misc/func5.tests @@ -1,9 +1,8 @@ f() { echo $1; } f 1 -# hush fails on this syntax, but i've never seen anyone use it ... -#f() ( echo $1; ) +f() ( echo $1; ) f 2 -#f() ( echo $1 ) +f() ( echo $1 ) f 3 -- cgit v1.2.3-55-g6feb From ee1fd1246e7d25990531dbbb2d267605b8914d5d Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 4 Apr 2018 13:59:53 +0200 Subject: ash: unbreak PS1 parsing after "ash: parser: Add syntax stack..." commit Signed-off-by: Denys Vlasenko --- shell/ash.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shell/ash.c b/shell/ash.c index ed1a4416c..558601543 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -11959,7 +11959,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) smallint pssyntax; /* we are expanding a prompt string */ IF_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;) /* syntax stack */ - struct synstack synbase = { .syntax = syntax }; + struct synstack synbase = { }; struct synstack *synstack = &synbase; #if ENABLE_ASH_EXPAND_PRMT @@ -11969,6 +11969,8 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) #else pssyntax = 0; /* constant */ #endif + synstack->syntax = syntax; + if (syntax == DQSYNTAX) synstack->dblquote = 1; quotef = 0; -- cgit v1.2.3-55-g6feb From c29c2e60d8677cdec142a88609a3d040e4719439 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 4 Apr 2018 17:02:32 +0200 Subject: sort: FEATURE_SORT_OPTIMIZE_MEMORY On sorting all kernel/linux/arch/ *.[ch] files, this reduces memory usage by 6%. yes | head -99999999 | sort goes down from 1900Mb to 380 Mb. function old new delta sort_main 862 1098 +236 Signed-off-by: Denys Vlasenko --- coreutils/sort.c | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 6 deletions(-) diff --git a/coreutils/sort.c b/coreutils/sort.c index b39297a26..b8bb6871d 100644 --- a/coreutils/sort.c +++ b/coreutils/sort.c @@ -18,16 +18,24 @@ //config: sort is used to sort lines of text in specified files. //config: //config:config FEATURE_SORT_BIG -//config: bool "Full SuSv3 compliant sort (support -ktcsbdfiozgM)" +//config: bool "Full SuSv3 compliant sort (support -ktcbdfiozgM)" //config: default y //config: depends on SORT //config: help -//config: Without this, sort only supports -r, -u, and an integer version +//config: Without this, sort only supports -r, -u, -s, and an integer version //config: of -n. Selecting this adds sort keys, floating point support, and //config: more. This adds a little over 3k to a nonstatic build on x86. //config: //config: The SuSv3 sort standard is available at: //config: http://www.opengroup.org/onlinepubs/007904975/utilities/sort.html +//config: +//config:config FEATURE_SORT_OPTIMIZE_MEMORY +//config: bool "Use less memory (but might be slower)" +//config: default n # defaults to N since we are size-paranoid tribe +//config: depends on SORT +//config: help +//config: Attempt to use less memory (by storing only one copy +//config: of duplicated lines, and such). Useful if you work on huge files. //applet:IF_SORT(APPLET_NOEXEC(sort, sort, BB_DIR_USR_BIN, BB_SUID_DROP, sort)) @@ -46,19 +54,17 @@ //usage: "\n -f Ignore case" //usage: "\n -i Ignore unprintable characters" //usage: "\n -d Dictionary order (blank or alphanumeric only)" -//usage: "\n -g General numerical sort" -//usage: "\n -M Sort month" //usage: ) //-h, --human-numeric-sort: compare human readable numbers (e.g., 2K 1G) //usage: "\n -n Sort numbers" //usage: IF_FEATURE_SORT_BIG( +//usage: "\n -g General numerical sort" +//usage: "\n -M Sort month" //usage: "\n -t CHAR Field separator" //usage: "\n -k N[,M] Sort by Nth field" //usage: ) //usage: "\n -r Reverse sort order" -//usage: IF_FEATURE_SORT_BIG( //usage: "\n -s Stable (don't sort ties alphabetically)" -//usage: ) //usage: "\n -u Suppress duplicate lines" //usage: IF_FEATURE_SORT_BIG( //usage: "\n -z Lines are terminated by NUL, not newline" @@ -404,6 +410,14 @@ int sort_main(int argc UNUSED_PARAM, char **argv) int i; int linecount; unsigned opts; +#if ENABLE_FEATURE_SORT_OPTIMIZE_MEMORY + bool can_drop_dups; + size_t prev_len = 0; + /* Postpone optimizing if the input is small, < 16k lines: + * even just free()ing duplicate lines takes time. + */ + size_t count_to_optimize_dups = 0x3fff; +#endif xfunc_error_retval = 2; @@ -412,6 +426,29 @@ int sort_main(int argc UNUSED_PARAM, char **argv) sort_opt_str, &str_ignored, &str_ignored, &str_o, &lst_k, &str_t ); +#if ENABLE_FEATURE_SORT_OPTIMIZE_MEMORY + /* Can drop dups only if -u but no "complicating" options, + * IOW: if we do a full line compares. Safe options: + * -o FILE Output to FILE + * -z Lines are terminated by NUL, not newline + * -r Reverse sort order + * -s Stable (don't sort ties alphabetically) + * Not sure about these: + * -b Ignore leading blanks + * -f Ignore case + * -i Ignore unprintable characters + * -d Dictionary order (blank or alphanumeric only) + * -n Sort numbers + * -g General numerical sort + * -M Sort month + */ + can_drop_dups = ((opts & ~(FLAG_o|FLAG_z|FLAG_r|FLAG_s)) == FLAG_u); + /* Stable sort needs every line to be uniquely allocated, + * disable optimization to reuse strings: + */ + if (opts & FLAG_s) + count_to_optimize_dups = (size_t)-1L; +#endif /* global b strips leading and trailing spaces */ if (opts & FLAG_b) option_mask32 |= FLAG_bb; @@ -489,6 +526,41 @@ int sort_main(int argc UNUSED_PARAM, char **argv) char *line = GET_LINE(fp); if (!line) break; + +#if ENABLE_FEATURE_SORT_OPTIMIZE_MEMORY + if (count_to_optimize_dups != 0) + count_to_optimize_dups--; + if (count_to_optimize_dups == 0) { + size_t len; + char *new_line; + char first = *line; + + /* On kernel/linux/arch/ *.[ch] files, + * this reduces memory usage by 6%. + * yes | head -99999999 | sort + * goes down from 1900Mb to 380 Mb. + */ + if (first == '\0' || first == '\n') { + len = !(first == '\0'); + new_line = (char*)"\n" + 1 - len; + goto replace; + } + len = strlen(line); + if (len <= prev_len) { + new_line = lines[linecount-1] + (prev_len - len); + if (strcmp(line, new_line) == 0) { + if (can_drop_dups && prev_len == len) { + free(line); + continue; + } + replace: + free(line); + line = new_line; + } + } + prev_len = len; + } +#endif lines = xrealloc_vector(lines, 6, linecount); lines[linecount++] = line; } @@ -510,6 +582,8 @@ int sort_main(int argc UNUSED_PARAM, char **argv) } return EXIT_SUCCESS; } +#else +//TODO: lighter version which only drops total dups if can_drop_dups == true #endif /* For stable sort, store original line position beyond terminating NUL */ -- cgit v1.2.3-55-g6feb From 759ca8a4cb57f797e92e02db84673057ce447125 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 4 Apr 2018 17:07:21 +0200 Subject: sort: move misplaced comment Signed-off-by: Denys Vlasenko --- coreutils/sort.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coreutils/sort.c b/coreutils/sort.c index b8bb6871d..9909a44a8 100644 --- a/coreutils/sort.c +++ b/coreutils/sort.c @@ -560,6 +560,8 @@ int sort_main(int argc UNUSED_PARAM, char **argv) } prev_len = len; } +#else +//TODO: lighter version which only drops total dups if can_drop_dups == true #endif lines = xrealloc_vector(lines, 6, linecount); lines[linecount++] = line; @@ -582,8 +584,6 @@ int sort_main(int argc UNUSED_PARAM, char **argv) } return EXIT_SUCCESS; } -#else -//TODO: lighter version which only drops total dups if can_drop_dups == true #endif /* For stable sort, store original line position beyond terminating NUL */ -- cgit v1.2.3-55-g6feb From 61407807ab6b9505041a74ad96ffb4aeab937d36 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 4 Apr 2018 21:14:28 +0200 Subject: hush: fix for readonly vars in "ro=A ro=B cmd" case Signed-off-by: Denys Vlasenko --- shell/hush.c | 26 +++++++++++++------------- shell/hush_test/hush-vars/readonly3.right | 4 ++++ shell/hush_test/hush-vars/readonly3.tests | 4 ++++ 3 files changed, 21 insertions(+), 13 deletions(-) create mode 100644 shell/hush_test/hush-vars/readonly3.right create mode 100755 shell/hush_test/hush-vars/readonly3.tests diff --git a/shell/hush.c b/shell/hush.c index 335107283..641531209 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -2134,7 +2134,7 @@ static const char* FAST_FUNC get_local_var_value(const char *name) #define SETFLAG_LOCAL_SHIFT 3 static int set_local_var(char *str, unsigned flags) { - struct variable **var_pp; + struct variable **cur_pp; struct variable *cur; char *free_me = NULL; char *eq_sign; @@ -2155,10 +2155,10 @@ static int set_local_var(char *str, unsigned flags) } #endif - var_pp = &G.top_var; - while ((cur = *var_pp) != NULL) { + cur_pp = &G.top_var; + while ((cur = *cur_pp) != NULL) { if (strncmp(cur->varstr, str, name_len) != 0) { - var_pp = &cur->next; + cur_pp = &cur->next; continue; } @@ -2182,7 +2182,7 @@ static int set_local_var(char *str, unsigned flags) * and existing one is global, or local * from enclosing function. * Remove and save old one: */ - *var_pp = cur->next; + *cur_pp = cur->next; cur->next = *G.shadowed_vars_pp; *G.shadowed_vars_pp = cur; /* bash 3.2.33(1) and exported vars: @@ -2226,8 +2226,8 @@ static int set_local_var(char *str, unsigned flags) /* Not found - create new variable struct */ cur = xzalloc(sizeof(*cur)); IF_HUSH_LOCAL(cur->func_nest_level = local_lvl;) - cur->next = *var_pp; - *var_pp = cur; + cur->next = *cur_pp; + *cur_pp = cur; set_str_and_exp: cur->varstr = str; @@ -2272,7 +2272,7 @@ static void set_pwd_var(unsigned flag) static int unset_local_var_len(const char *name, int name_len) { struct variable *cur; - struct variable **var_pp; + struct variable **cur_pp; if (!name) return EXIT_SUCCESS; @@ -2286,14 +2286,14 @@ static int unset_local_var_len(const char *name, int name_len) G.lineno_var = NULL; #endif - var_pp = &G.top_var; - while ((cur = *var_pp) != NULL) { + cur_pp = &G.top_var; + while ((cur = *cur_pp) != NULL) { if (strncmp(cur->varstr, name, name_len) == 0 && cur->varstr[name_len] == '=') { if (cur->flg_read_only) { bb_error_msg("%s: readonly variable", name); return EXIT_FAILURE; } - *var_pp = cur->next; + *cur_pp = cur->next; debug_printf_env("%s: unsetenv '%s'\n", __func__, cur->varstr); bb_unsetenv(cur->varstr); if (name_len == 3 && cur->varstr[0] == 'P' && cur->varstr[1] == 'S') @@ -2303,7 +2303,7 @@ static int unset_local_var_len(const char *name, int name_len) free(cur); return EXIT_SUCCESS; } - var_pp = &cur->next; + cur_pp = &cur->next; } return EXIT_SUCCESS; } @@ -2402,8 +2402,8 @@ static struct variable *set_vars_and_save_old(char **strings) } set_local_var(*s, SETFLAG_EXPORT); } - next: s++; + next: ; } return old; } diff --git a/shell/hush_test/hush-vars/readonly3.right b/shell/hush_test/hush-vars/readonly3.right new file mode 100644 index 000000000..acd931243 --- /dev/null +++ b/shell/hush_test/hush-vars/readonly3.right @@ -0,0 +1,4 @@ +hush: v=2: readonly variable +hush: v=3: readonly variable +1 +Ok:1 diff --git a/shell/hush_test/hush-vars/readonly3.tests b/shell/hush_test/hush-vars/readonly3.tests new file mode 100755 index 000000000..17780cfda --- /dev/null +++ b/shell/hush_test/hush-vars/readonly3.tests @@ -0,0 +1,4 @@ +readonly v=1 +# there was a bug causing second assignment to be not checked +v=2 v=3 echo $v +echo Ok:$v -- cgit v1.2.3-55-g6feb From 332e4115c978094b3630cde62a7154a4fcd564d8 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 4 Apr 2018 22:32:59 +0200 Subject: hush: make var nesting code independent of "local" support Also, add code to abort at ~65000 function recursion depth. SEGVing is not as nice as exiting with a message (and restoring termios!): $ f() { echo -n .; f; }; f ........hush: fatal recursion (depth 65281) function old new delta run_pipe 1826 1890 +64 pseudo_exec_argv 544 554 +10 parse_and_run_file 71 80 +9 i_getch 104 107 +3 done_command 99 102 +3 set_local_var 508 510 +2 helper_export_local 214 215 +1 builtin_local 49 46 -3 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 7/1 up/down: 92/-3) Total: 89 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 101 ++++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 61 insertions(+), 40 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index 641531209..9fb9b5f32 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -730,10 +730,8 @@ struct parse_context { struct variable { struct variable *next; char *varstr; /* points to "name=" portion */ -#if ENABLE_HUSH_LOCAL - unsigned func_nest_level; -#endif int max_len; /* if > 0, name is part of initial env; else name is malloced */ + uint16_t var_nest_level; smallint flg_export; /* putenv should be done on this var */ smallint flg_read_only; }; @@ -922,12 +920,13 @@ struct globals { const char *cwd; struct variable *top_var; char **expanded_assignments; + struct variable **shadowed_vars_pp; + unsigned var_nest_level; #if ENABLE_HUSH_FUNCTIONS - struct function *top_func; # if ENABLE_HUSH_LOCAL - struct variable **shadowed_vars_pp; - unsigned func_nest_level; + unsigned func_nest_level; /* solely to prevent "local v" in non-functions */ # endif + struct function *top_func; #endif /* Signal and trap handling */ #if ENABLE_HUSH_FAST @@ -2131,7 +2130,7 @@ static const char* FAST_FUNC get_local_var_value(const char *name) #define SETFLAG_EXPORT (1 << 0) #define SETFLAG_UNEXPORT (1 << 1) #define SETFLAG_MAKE_RO (1 << 2) -#define SETFLAG_LOCAL_SHIFT 3 +#define SETFLAG_VARLVL_SHIFT 3 static int set_local_var(char *str, unsigned flags) { struct variable **cur_pp; @@ -2139,7 +2138,7 @@ static int set_local_var(char *str, unsigned flags) char *free_me = NULL; char *eq_sign; int name_len; - IF_HUSH_LOCAL(unsigned local_lvl = (flags >> SETFLAG_LOCAL_SHIFT);) + unsigned local_lvl = (flags >> SETFLAG_VARLVL_SHIFT); eq_sign = strchr(str, '='); if (!eq_sign) { /* not expected to ever happen? */ @@ -2176,8 +2175,7 @@ static int set_local_var(char *str, unsigned flags) unsetenv(str); *eq_sign = '='; } -#if ENABLE_HUSH_LOCAL - if (cur->func_nest_level < local_lvl) { + if (cur->var_nest_level < local_lvl) { /* New variable is declared as local, * and existing one is global, or local * from enclosing function. @@ -2197,7 +2195,7 @@ static int set_local_var(char *str, unsigned flags) flags |= SETFLAG_EXPORT; break; } -#endif + if (strcmp(cur->varstr + name_len, eq_sign + 1) == 0) { free_and_exp: free(str); @@ -2225,7 +2223,7 @@ static int set_local_var(char *str, unsigned flags) /* Not found - create new variable struct */ cur = xzalloc(sizeof(*cur)); - IF_HUSH_LOCAL(cur->func_nest_level = local_lvl;) + cur->var_nest_level = local_lvl; cur->next = *cur_pp; *cur_pp = cur; @@ -5509,7 +5507,7 @@ static struct pipe *parse_stream(char **pstring, goto parse_error2; default: if (HUSH_DEBUG) - bb_error_msg_and_die("BUG: unexpected %c\n", ch); + bb_error_msg_and_die("BUG: unexpected %c", ch); } } /* while (1) */ @@ -7362,8 +7360,9 @@ static void exec_function(char ***to_free, // for "more correctness" we might want to close those extra fds here: //? close_saved_fds_and_FILE_fds(); - /* "we are in function, ok to use return" */ + /* "we are in a function, ok to use return" */ G_flag_return_in_progress = -1; + G.var_nest_level++; IF_HUSH_LOCAL(G.func_nest_level++;) /* On MMU, funcp->body is always non-NULL */ @@ -7383,6 +7382,47 @@ static void exec_function(char ***to_free, # endif } +static void enter_var_nest_level(void) +{ + G.var_nest_level++; + + /* Try: f() { echo -n .; f; }; f + * struct variable::var_nest_level is uint16_t, + * thus limiting recursion to < 2^16. + * In any case, with 8 Mbyte stack SEGV happens + * not too long after 2^16 recursions anyway. + */ + if (G.var_nest_level > 0xff00) + bb_error_msg_and_die("fatal recursion (depth %u)", G.var_nest_level); +} + +static void leave_var_nest_level(void) +{ + struct variable *cur; + struct variable **cur_pp; + + cur_pp = &G.top_var; + while ((cur = *cur_pp) != NULL) { + if (cur->var_nest_level < G.var_nest_level) { + cur_pp = &cur->next; + continue; + } + /* Unexport */ + if (cur->flg_export) + bb_unsetenv(cur->varstr); + /* Remove from global list */ + *cur_pp = cur->next; + /* Free */ + if (!cur->max_len) + free(cur->varstr); + free(cur); + } + + G.var_nest_level--; + if (HUSH_DEBUG && (int)G.var_nest_level < 0) + bb_error_msg_and_die("BUG: nesting underflow"); +} + static int run_function(const struct function *funcp, char **argv) { int rc; @@ -7394,6 +7434,8 @@ static int run_function(const struct function *funcp, char **argv) /* "we are in function, ok to use return" */ sv_flg = G_flag_return_in_progress; G_flag_return_in_progress = -1; + + enter_var_nest_level(); IF_HUSH_LOCAL(G.func_nest_level++;) /* On MMU, funcp->body is always non-NULL */ @@ -7408,30 +7450,9 @@ static int run_function(const struct function *funcp, char **argv) rc = run_list(funcp->body); } -# if ENABLE_HUSH_LOCAL - { - struct variable *var; - struct variable **var_pp; + IF_HUSH_LOCAL(G.func_nest_level--;) + leave_var_nest_level(); - var_pp = &G.top_var; - while ((var = *var_pp) != NULL) { - if (var->func_nest_level < G.func_nest_level) { - var_pp = &var->next; - continue; - } - /* Unexport */ - if (var->flg_export) - bb_unsetenv(var->varstr); - /* Remove from global list */ - *var_pp = var->next; - /* Free */ - if (!var->max_len) - free(var->varstr); - free(var); - } - G.func_nest_level--; - } -# endif G_flag_return_in_progress = sv_flg; restore_G_args(&sv, argv); @@ -9959,8 +9980,8 @@ static int helper_export_local(char **argv, unsigned flags) # if ENABLE_HUSH_LOCAL /* Is this "local" bltin? */ if (!(flags & (SETFLAG_EXPORT|SETFLAG_UNEXPORT|SETFLAG_MAKE_RO))) { - unsigned lvl = flags >> SETFLAG_LOCAL_SHIFT; - if (var && var->func_nest_level == lvl) { + unsigned lvl = flags >> SETFLAG_VARLVL_SHIFT; + if (var && var->var_nest_level == lvl) { /* "local x=abc; ...; local x" - ignore second local decl */ continue; } @@ -10045,7 +10066,7 @@ static int FAST_FUNC builtin_local(char **argv) return EXIT_FAILURE; /* bash compat */ } argv++; - return helper_export_local(argv, G.func_nest_level << SETFLAG_LOCAL_SHIFT); + return helper_export_local(argv, G.var_nest_level << SETFLAG_VARLVL_SHIFT); } #endif -- cgit v1.2.3-55-g6feb From d358b0b65dae83d52e511a126757e2aa7b1881b2 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 5 Apr 2018 00:51:55 +0200 Subject: hush: fix a bug where we don't properly handle f() { a=A; b=B; }; a= f function old new delta unset_local_var 20 274 +254 leave_var_nest_level - 98 +98 set_vars_and_save_old 128 164 +36 enter_var_nest_level - 32 +32 builtin_local 46 50 +4 pseudo_exec_argv 554 544 -10 redirect_and_varexp_helper 77 64 -13 run_pipe 1890 1641 -249 unset_local_var_len 267 - -267 ------------------------------------------------------------------------------ (add/remove: 2/1 grow/shrink: 3/3 up/down: 424/-539) Total: -115 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 153 +++++++++++++--------------- shell/hush_test/hush-vars/var_nested1.right | 3 + shell/hush_test/hush-vars/var_nested1.tests | 16 +++ shell/hush_test/hush-vars/var_nested2.right | 1 + shell/hush_test/hush-vars/var_nested2.tests | 2 + 5 files changed, 93 insertions(+), 82 deletions(-) create mode 100644 shell/hush_test/hush-vars/var_nested1.right create mode 100755 shell/hush_test/hush-vars/var_nested1.tests create mode 100644 shell/hush_test/hush-vars/var_nested2.right create mode 100755 shell/hush_test/hush-vars/var_nested2.tests diff --git a/shell/hush.c b/shell/hush.c index 9fb9b5f32..00d86b4e4 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -475,7 +475,6 @@ static const char hush_version_str[] ALIGN1 = "HUSH_VERSION="BB_VER; */ #if !BB_MMU typedef struct nommu_save_t { - char **new_env; struct variable *old_vars; char **argv; char **argv_from_re_execing; @@ -2176,10 +2175,16 @@ static int set_local_var(char *str, unsigned flags) *eq_sign = '='; } if (cur->var_nest_level < local_lvl) { - /* New variable is declared as local, + /* New variable is local ("local VAR=VAL" or + * "VAR=VAL cmd") * and existing one is global, or local - * from enclosing function. - * Remove and save old one: */ + * on a lower level that new one. + * Remove and save old one: + */ + debug_printf_env("shadowing %s'%s'/%u by '%s'/%u\n", + cur->flg_export ? "exported " : "", + cur->varstr, cur->var_nest_level, str, local_lvl + ); *cur_pp = cur->next; cur->next = *G.shadowed_vars_pp; *G.shadowed_vars_pp = cur; @@ -2197,6 +2202,7 @@ static int set_local_var(char *str, unsigned flags) } if (strcmp(cur->varstr + name_len, eq_sign + 1) == 0) { + debug_printf_env("assignement '%s' does not change anything\n", str); free_and_exp: free(str); goto exp; @@ -2204,6 +2210,7 @@ static int set_local_var(char *str, unsigned flags) if (cur->max_len != 0) { if (cur->max_len >= strlen(str)) { /* This one is from startup env, reuse space */ + debug_printf_env("reusing startup env for '%s'\n", str); strcpy(cur->varstr, str); goto free_and_exp; } @@ -2221,7 +2228,7 @@ static int set_local_var(char *str, unsigned flags) goto set_str_and_exp; } - /* Not found - create new variable struct */ + /* Not found or shadowed - create new variable struct */ cur = xzalloc(sizeof(*cur)); cur->var_nest_level = local_lvl; cur->next = *cur_pp; @@ -2250,7 +2257,7 @@ static int set_local_var(char *str, unsigned flags) /* unsetenv was already done */ } else { int i; - debug_printf_env("%s: putenv '%s'\n", __func__, cur->varstr); + debug_printf_env("%s: putenv '%s'/%u\n", __func__, cur->varstr, cur->var_nest_level); i = putenv(cur->varstr); /* only now we can free old exported malloced string */ free(free_me); @@ -2313,21 +2320,6 @@ static int unset_local_var(const char *name) } #endif -static void unset_vars(char **strings) -{ - char **v; - - if (!strings) - return; - v = strings; - while (*v) { - const char *eq = strchrnul(*v, '='); - unset_local_var_len(*v, (int)(eq - *v)); - v++; - } - free(strings); -} - #if BASH_HOSTNAME_VAR || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_READ || ENABLE_HUSH_GETOPTS static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val) { @@ -2349,15 +2341,20 @@ static void add_vars(struct variable *var) var->next = G.top_var; G.top_var = var; if (var->flg_export) { - debug_printf_env("%s: restoring exported '%s'\n", __func__, var->varstr); + debug_printf_env("%s: restoring exported '%s'/%u\n", __func__, var->varstr, var->var_nest_level); putenv(var->varstr); } else { - debug_printf_env("%s: restoring variable '%s'\n", __func__, var->varstr); + debug_printf_env("%s: restoring variable '%s'/%u\n", __func__, var->varstr, var->var_nest_level); } var = next; } } +/* We put strings[i] into variable table and possibly putenv them. + * If variable is read only, we can free the strings[i] + * which attempts to overwrite it. + * The strings[] vector itself is freed. + */ static struct variable *set_vars_and_save_old(char **strings) { char **s; @@ -2365,6 +2362,7 @@ static struct variable *set_vars_and_save_old(char **strings) if (!strings) return old; + s = strings; while (*s) { struct variable *var_p; @@ -2398,11 +2396,15 @@ static struct variable *set_vars_and_save_old(char **strings) var_p->next = old; old = var_p; } - set_local_var(*s, SETFLAG_EXPORT); + //bb_error_msg("G.var_nest_level:%d", G.var_nest_level); + set_local_var(*s, (G.var_nest_level << SETFLAG_VARLVL_SHIFT) | SETFLAG_EXPORT); + } else if (HUSH_DEBUG) { + bb_error_msg_and_die("BUG in varexp4"); } s++; next: ; } + free(strings); return old; } @@ -6339,7 +6341,7 @@ static char **expand_strvec_to_strvec_singleword_noglob(char **argv) #endif /* Used for expansion of right hand of assignments, - * $((...)), heredocs, variable espansion parts. + * $((...)), heredocs, variable expansion parts. * * NB: should NOT do globbing! * "export v=/bin/c*; env | grep ^v=" outputs "v=/bin/c*" @@ -7385,6 +7387,7 @@ static void exec_function(char ***to_free, static void enter_var_nest_level(void) { G.var_nest_level++; + debug_printf_env("var_nest_level++ %u\n", G.var_nest_level); /* Try: f() { echo -n .; f; }; f * struct variable::var_nest_level is uint16_t, @@ -7408,17 +7411,22 @@ static void leave_var_nest_level(void) continue; } /* Unexport */ - if (cur->flg_export) + if (cur->flg_export) { + debug_printf_env("unexporting nested '%s'/%u\n", cur->varstr, cur->var_nest_level); bb_unsetenv(cur->varstr); + } /* Remove from global list */ *cur_pp = cur->next; /* Free */ - if (!cur->max_len) + if (!cur->max_len) { + debug_printf_env("freeing nested '%s'/%u\n", cur->varstr, cur->var_nest_level); free(cur->varstr); + } free(cur); } G.var_nest_level--; + debug_printf_env("var_nest_level-- %u\n", G.var_nest_level); if (HUSH_DEBUG && (int)G.var_nest_level < 0) bb_error_msg_and_die("BUG: nesting underflow"); } @@ -7431,11 +7439,12 @@ static int run_function(const struct function *funcp, char **argv) save_and_replace_G_args(&sv, argv); - /* "we are in function, ok to use return" */ + /* "We are in function, ok to use return" */ sv_flg = G_flag_return_in_progress; G_flag_return_in_progress = -1; - enter_var_nest_level(); + /* Make "local" variables properly shadow previous ones */ + IF_HUSH_LOCAL(enter_var_nest_level();) IF_HUSH_LOCAL(G.func_nest_level++;) /* On MMU, funcp->body is always non-NULL */ @@ -7451,7 +7460,7 @@ static int run_function(const struct function *funcp, char **argv) } IF_HUSH_LOCAL(G.func_nest_level--;) - leave_var_nest_level(); + IF_HUSH_LOCAL(leave_var_nest_level();) G_flag_return_in_progress = sv_flg; @@ -7608,11 +7617,9 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, #if BB_MMU set_vars_and_save_old(new_env); - free(new_env); /* optional */ - /* we can also destroy set_vars_and_save_old's return value, + /* we can destroy set_vars_and_save_old's return value, * to save memory */ #else - nommu_save->new_env = new_env; nommu_save->old_vars = set_vars_and_save_old(new_env); #endif @@ -8174,10 +8181,10 @@ static int checkjobs_and_fg_shell(struct pipe *fg_pipe) * subshell: ( list ) [&] */ #if !ENABLE_HUSH_MODE_X -#define redirect_and_varexp_helper(new_env_p, old_vars_p, command, squirrel, argv_expanded) \ - redirect_and_varexp_helper(new_env_p, old_vars_p, command, squirrel) +#define redirect_and_varexp_helper(old_vars_p, command, squirrel, argv_expanded) \ + redirect_and_varexp_helper(old_vars_p, command, squirrel) #endif -static int redirect_and_varexp_helper(char ***new_env_p, +static int redirect_and_varexp_helper( struct variable **old_vars_p, struct command *command, struct squirrel **sqp, @@ -8190,11 +8197,10 @@ static int redirect_and_varexp_helper(char ***new_env_p, int rcode = setup_redirects(command, sqp); if (rcode == 0) { char **new_env = expand_assignments(command->argv, command->assignment_cnt); - *new_env_p = new_env; dump_cmd_in_x_mode(new_env); dump_cmd_in_x_mode(argv_expanded); - if (old_vars_p) - *old_vars_p = set_vars_and_save_old(new_env); + /* this takes ownership of new_env[i] elements, and frees new_env: */ + *old_vars_p = set_vars_and_save_old(new_env); } return rcode; } @@ -8284,13 +8290,9 @@ static NOINLINE int run_pipe(struct pipe *pi) argv = command->argv ? command->argv : (char **) &null_ptr; { const struct built_in_command *x; -#if ENABLE_HUSH_FUNCTIONS - const struct function *funcp; -#else - enum { funcp = 0 }; -#endif - char **new_env = NULL; - struct variable *old_vars = NULL; + IF_HUSH_FUNCTIONS(const struct function *funcp;) + IF_NOT_HUSH_FUNCTIONS(enum { funcp = 0 };) + struct variable *old_vars; #if ENABLE_HUSH_LINENO_VAR if (G.lineno_var) @@ -8303,28 +8305,6 @@ static NOINLINE int run_pipe(struct pipe *pi) * Try "a=t >file" */ G.expand_exitcode = 0; -#if 0 /* A few cases in testsuite fail with this code. FIXME */ - rcode = redirect_and_varexp_helper(&new_env, /*old_vars:*/ NULL, command, &squirrel, /*argv_expanded:*/ NULL); - /* Set shell variables */ - if (new_env) { - argv = new_env; - while (*argv) { - if (set_local_var(*argv, /*flag:*/ 0)) { - /* assignment to readonly var / putenv error? */ - rcode = 1; - } - argv++; - } - } - /* Redirect error sets $? to 1. Otherwise, - * if evaluating assignment value set $?, retain it. - * Try "false; q=`exit 2`; echo $?" - should print 2: */ - if (rcode == 0) - rcode = G.expand_exitcode; - /* Exit, _skipping_ variable restoring code: */ - goto clean_up_and_ret0; - -#else /* Older, bigger, but more correct code */ rcode = setup_redirects(command, &squirrel); restore_redirects(squirrel); @@ -8358,7 +8338,6 @@ static NOINLINE int run_pipe(struct pipe *pi) debug_leave(); debug_printf_exec("run_pipe: return %d\n", rcode); return rcode; -#endif } /* Expand the rest into (possibly) many strings each */ @@ -8372,6 +8351,7 @@ static NOINLINE int run_pipe(struct pipe *pi) } /* if someone gives us an empty string: `cmd with empty output` */ +//TODO: what about: var=EXPR `` >FILE ? will var be set? Will FILE be created? if (!argv_expanded[0]) { free(argv_expanded); debug_leave(); @@ -8394,7 +8374,14 @@ static NOINLINE int run_pipe(struct pipe *pi) goto clean_up_and_ret1; } } - rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, &squirrel, argv_expanded); + + /* Without bumping var nesting level, this leaks + * exported $a: + * a=b true; env | grep ^a= + */ + enter_var_nest_level(); + rcode = redirect_and_varexp_helper(&old_vars, command, &squirrel, argv_expanded); + if (rcode == 0) { if (!funcp) { debug_printf_exec(": builtin '%s' '%s'...\n", @@ -8405,24 +8392,23 @@ static NOINLINE int run_pipe(struct pipe *pi) } #if ENABLE_HUSH_FUNCTIONS else { -# if ENABLE_HUSH_LOCAL - struct variable **sv; - sv = G.shadowed_vars_pp; + struct variable **sv = G.shadowed_vars_pp; + /* Make "local" builtins in the called function + * stash shadowed variables in old_vars list + */ G.shadowed_vars_pp = &old_vars; -# endif + debug_printf_exec(": function '%s' '%s'...\n", funcp->name, argv_expanded[1]); rcode = run_function(funcp, argv_expanded) & 0xff; -# if ENABLE_HUSH_LOCAL + G.shadowed_vars_pp = sv; -# endif } #endif } clean_up_and_ret: - unset_vars(new_env); + leave_var_nest_level(); add_vars(old_vars); -/* clean_up_and_ret0: */ restore_redirects(squirrel); /* * Try "usleep 99999999" + ^C + "echo $?" @@ -8453,7 +8439,8 @@ static NOINLINE int run_pipe(struct pipe *pi) if (ENABLE_FEATURE_SH_NOFORK && NUM_APPLETS > 1) { int n = find_applet_by_name(argv_expanded[0]); if (n >= 0 && APPLET_IS_NOFORK(n)) { - rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, &squirrel, argv_expanded); + enter_var_nest_level(); + rcode = redirect_and_varexp_helper(&old_vars, command, &squirrel, argv_expanded); if (rcode == 0) { debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", argv_expanded[0], argv_expanded[1]); @@ -8487,7 +8474,6 @@ static NOINLINE int run_pipe(struct pipe *pi) struct fd_pair pipefds; #if !BB_MMU volatile nommu_save_t nommu_save; - nommu_save.new_env = NULL; nommu_save.old_vars = NULL; nommu_save.argv = NULL; nommu_save.argv_from_re_execing = NULL; @@ -8580,7 +8566,6 @@ static NOINLINE int run_pipe(struct pipe *pi) /* Clean up after vforked child */ free(nommu_save.argv); free(nommu_save.argv_from_re_execing); - unset_vars(nommu_save.new_env); add_vars(nommu_save.old_vars); #endif free(argv_expanded); @@ -10066,7 +10051,11 @@ static int FAST_FUNC builtin_local(char **argv) return EXIT_FAILURE; /* bash compat */ } argv++; - return helper_export_local(argv, G.var_nest_level << SETFLAG_VARLVL_SHIFT); + /* Since all builtins run in a nested variable level, + * need to use level - 1 here. Or else the variable will be removed at once + * after builtin returns. + */ + return helper_export_local(argv, (G.var_nest_level - 1) << SETFLAG_VARLVL_SHIFT); } #endif diff --git a/shell/hush_test/hush-vars/var_nested1.right b/shell/hush_test/hush-vars/var_nested1.right new file mode 100644 index 000000000..f4bc5f4b6 --- /dev/null +++ b/shell/hush_test/hush-vars/var_nested1.right @@ -0,0 +1,3 @@ +Expected:AB Actual:AB +Expected:Ab Actual:Ab +Expected:ab Actual:ab diff --git a/shell/hush_test/hush-vars/var_nested1.tests b/shell/hush_test/hush-vars/var_nested1.tests new file mode 100755 index 000000000..59e4a14fa --- /dev/null +++ b/shell/hush_test/hush-vars/var_nested1.tests @@ -0,0 +1,16 @@ +f() { a=A; b=B; } + +a=a +b=b +f +echo Expected:AB Actual:$a$b + +a=a +b=b +b= f +echo Expected:Ab Actual:$a$b + +a=a +b=b +a= b= f +echo Expected:ab Actual:$a$b diff --git a/shell/hush_test/hush-vars/var_nested2.right b/shell/hush_test/hush-vars/var_nested2.right new file mode 100644 index 000000000..c930d971c --- /dev/null +++ b/shell/hush_test/hush-vars/var_nested2.right @@ -0,0 +1 @@ +aB diff --git a/shell/hush_test/hush-vars/var_nested2.tests b/shell/hush_test/hush-vars/var_nested2.tests new file mode 100755 index 000000000..e8865861e --- /dev/null +++ b/shell/hush_test/hush-vars/var_nested2.tests @@ -0,0 +1,2 @@ +# the bug was easier to trigger in one-liner form +a=a; b=b; f() { a=A; b=B; }; a= f; echo $a$b -- cgit v1.2.3-55-g6feb From 34f6b12330a13194057b49a74ca14362c2a332fa Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 5 Apr 2018 11:30:17 +0200 Subject: hush: make run_pipe code simpler to understand, no logic changes function old new delta run_pipe 1641 1651 +10 Signed-off-by: Denys Vlasenko --- shell/hush.c | 123 +++++++++++++++++++++++++++-------------------------------- 1 file changed, 57 insertions(+), 66 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index 00d86b4e4..3ad6a9724 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -7599,9 +7599,8 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, { const struct built_in_command *x; char **new_env; -#if ENABLE_HUSH_COMMAND - char opt_vV = 0; -#endif + IF_HUSH_COMMAND(char opt_vV = 0;) + IF_HUSH_FUNCTIONS(const struct function *funcp;) new_env = expand_assignments(argv, assignment_cnt); dump_cmd_in_x_mode(new_env); @@ -7640,12 +7639,9 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, #if ENABLE_HUSH_FUNCTIONS /* Check if the command matches any functions (this goes before bltins) */ - { - const struct function *funcp = find_function(argv[0]); - if (funcp) { - exec_function(&nommu_save->argv_from_re_execing, funcp, argv); - } - } + funcp = find_function(argv[0]); + if (funcp) + exec_function(&nommu_save->argv_from_re_execing, funcp, argv); #endif #if ENABLE_HUSH_COMMAND @@ -8366,22 +8362,18 @@ static NOINLINE int run_pipe(struct pipe *pi) if (!funcp) x = find_builtin(argv_expanded[0]); if (x || funcp) { - if (!funcp) { - if (x->b_function == builtin_exec && argv_expanded[1] == NULL) { - debug_printf("exec with redirects only\n"); - rcode = setup_redirects(command, NULL); - /* rcode=1 can be if redir file can't be opened */ - goto clean_up_and_ret1; - } + if (x && x->b_function == builtin_exec && argv_expanded[1] == NULL) { + debug_printf("exec with redirects only\n"); + rcode = setup_redirects(command, NULL); + /* rcode=1 can be if redir file can't be opened */ + goto clean_up_and_ret1; } - /* Without bumping var nesting level, this leaks - * exported $a: + /* Bump var nesting, or this will leak exported $a: * a=b true; env | grep ^a= */ enter_var_nest_level(); rcode = redirect_and_varexp_helper(&old_vars, command, &squirrel, argv_expanded); - if (rcode == 0) { if (!funcp) { debug_printf_exec(": builtin '%s' '%s'...\n", @@ -8406,58 +8398,57 @@ static NOINLINE int run_pipe(struct pipe *pi) } #endif } - clean_up_and_ret: - leave_var_nest_level(); - add_vars(old_vars); - restore_redirects(squirrel); - /* - * Try "usleep 99999999" + ^C + "echo $?" - * with FEATURE_SH_NOFORK=y. - */ - if (!funcp) { - /* It was builtin or nofork. - * if this would be a real fork/execed program, - * it should have died if a fatal sig was received. - * But OTOH, there was no separate process, - * the sig was sent to _shell_, not to non-existing - * child. - * Let's just handle ^C only, this one is obvious: - * we aren't ok with exitcode 0 when ^C was pressed - * during builtin/nofork. + } else + if (ENABLE_FEATURE_SH_NOFORK && NUM_APPLETS > 1) { + int n = find_applet_by_name(argv_expanded[0]); + if (n < 0 || !APPLET_IS_NOFORK(n)) + goto must_fork; + + enter_var_nest_level(); + rcode = redirect_and_varexp_helper(&old_vars, command, &squirrel, argv_expanded); + if (rcode == 0) { + debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", + argv_expanded[0], argv_expanded[1]); + /* + * Note: signals (^C) can't interrupt here. + * We remember them and they will be acted upon + * after applet returns. + * This makes applets which can run for a long time + * and/or wait for user input ineligible for NOFORK: + * for example, "yes" or "rm" (rm -i waits for input). */ - if (sigismember(&G.pending_set, SIGINT)) - rcode = 128 + SIGINT; + rcode = run_nofork_applet(n, argv_expanded); } - clean_up_and_ret1: - free(argv_expanded); - IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) - debug_leave(); - debug_printf_exec("run_pipe return %d\n", rcode); - return rcode; } - if (ENABLE_FEATURE_SH_NOFORK && NUM_APPLETS > 1) { - int n = find_applet_by_name(argv_expanded[0]); - if (n >= 0 && APPLET_IS_NOFORK(n)) { - enter_var_nest_level(); - rcode = redirect_and_varexp_helper(&old_vars, command, &squirrel, argv_expanded); - if (rcode == 0) { - debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", - argv_expanded[0], argv_expanded[1]); - /* - * Note: signals (^C) can't interrupt here. - * We remember them and they will be acted upon - * after applet returns. - * This makes applets which can run for a long time - * and/or wait for user input ineligible for NOFORK: - * for example, "yes" or "rm" (rm -i waits for input). - */ - rcode = run_nofork_applet(n, argv_expanded); - } - goto clean_up_and_ret; - } + leave_var_nest_level(); + add_vars(old_vars); + restore_redirects(squirrel); + + /* + * Try "usleep 99999999" + ^C + "echo $?" + * with FEATURE_SH_NOFORK=y. + */ + if (!funcp) { + /* It was builtin or nofork. + * if this would be a real fork/execed program, + * it should have died if a fatal sig was received. + * But OTOH, there was no separate process, + * the sig was sent to _shell_, not to non-existing + * child. + * Let's just handle ^C only, this one is obvious: + * we aren't ok with exitcode 0 when ^C was pressed + * during builtin/nofork. + */ + if (sigismember(&G.pending_set, SIGINT)) + rcode = 128 + SIGINT; } - /* It is neither builtin nor applet. We must fork. */ + clean_up_and_ret1: + free(argv_expanded); + IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) + debug_leave(); + debug_printf_exec("run_pipe return %d\n", rcode); + return rcode; } must_fork: -- cgit v1.2.3-55-g6feb From 4e1dc539e97300c44589eff8baffd12c95a13d5f Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 5 Apr 2018 13:10:34 +0200 Subject: hush: "no logic changes" in last commit was not true, fix it up Signed-off-by: Denys Vlasenko --- shell/hush.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shell/hush.c b/shell/hush.c index 3ad6a9724..4740929e8 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -8419,7 +8419,8 @@ static NOINLINE int run_pipe(struct pipe *pi) */ rcode = run_nofork_applet(n, argv_expanded); } - } + } else + goto must_fork; leave_var_nest_level(); add_vars(old_vars); -- cgit v1.2.3-55-g6feb From 929a41d5770c0531f037c2e7db25bf98f9029c9e Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 5 Apr 2018 14:09:14 +0200 Subject: hush: less mind-bending set_vars_and_save_old() function old new delta run_pipe 1651 1701 +50 set_local_var 510 557 +47 pseudo_exec_argv 544 581 +37 redirect_and_varexp_helper 64 56 -8 set_vars_and_save_old 164 149 -15 unset_local_var 274 256 -18 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 3/3 up/down: 134/-41) Total: 93 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 172 +++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 101 insertions(+), 71 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index 4740929e8..24ae237d7 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -2140,19 +2140,10 @@ static int set_local_var(char *str, unsigned flags) unsigned local_lvl = (flags >> SETFLAG_VARLVL_SHIFT); eq_sign = strchr(str, '='); - if (!eq_sign) { /* not expected to ever happen? */ - free(str); - return -1; - } + if (HUSH_DEBUG && !eq_sign) + bb_error_msg_and_die("BUG in setvar"); name_len = eq_sign - str + 1; /* including '=' */ -#if ENABLE_HUSH_LINENO_VAR - if (G.lineno_var) { - if (name_len == 7 && strncmp("LINENO", str, 6) == 0) - G.lineno_var = NULL; - } -#endif - cur_pp = &G.top_var; while ((cur = *cur_pp) != NULL) { if (strncmp(cur->varstr, str, name_len) != 0) { @@ -2175,19 +2166,6 @@ static int set_local_var(char *str, unsigned flags) *eq_sign = '='; } if (cur->var_nest_level < local_lvl) { - /* New variable is local ("local VAR=VAL" or - * "VAR=VAL cmd") - * and existing one is global, or local - * on a lower level that new one. - * Remove and save old one: - */ - debug_printf_env("shadowing %s'%s'/%u by '%s'/%u\n", - cur->flg_export ? "exported " : "", - cur->varstr, cur->var_nest_level, str, local_lvl - ); - *cur_pp = cur->next; - cur->next = *G.shadowed_vars_pp; - *G.shadowed_vars_pp = cur; /* bash 3.2.33(1) and exported vars: * # export z=z * # f() { local z=a; env | grep ^z; } @@ -2198,6 +2176,31 @@ static int set_local_var(char *str, unsigned flags) */ if (cur->flg_export) flags |= SETFLAG_EXPORT; + /* New variable is local ("local VAR=VAL" or + * "VAR=VAL cmd") + * and existing one is global, or local + * on a lower level that new one. + * Remove it from global variable list: + */ + *cur_pp = cur->next; + if (G.shadowed_vars_pp) { + /* Save in "shadowed" list */ + debug_printf_env("shadowing %s'%s'/%u by '%s'/%u\n", + cur->flg_export ? "exported " : "", + cur->varstr, cur->var_nest_level, str, local_lvl + ); + cur->next = *G.shadowed_vars_pp; + *G.shadowed_vars_pp = cur; + } else { + /* Came from pseudo_exec_argv(), no need to save: delete it */ + debug_printf_env("shadow-deleting %s'%s'/%u by '%s'/%u\n", + cur->flg_export ? "exported " : "", + cur->varstr, cur->var_nest_level, str, local_lvl + ); + if (cur->max_len == 0) /* allocated "VAR=VAL"? */ + free_me = cur->varstr; /* then free it later */ + free(cur); + } break; } @@ -2207,8 +2210,10 @@ static int set_local_var(char *str, unsigned flags) free(str); goto exp; } + + /* Replace the value in the found "struct variable" */ if (cur->max_len != 0) { - if (cur->max_len >= strlen(str)) { + if (cur->max_len >= strnlen(str, cur->max_len + 1)) { /* This one is from startup env, reuse space */ debug_printf_env("reusing startup env for '%s'\n", str); strcpy(cur->varstr, str); @@ -2244,13 +2249,6 @@ static int set_local_var(char *str, unsigned flags) #endif if (flags & SETFLAG_EXPORT) cur->flg_export = 1; - if (name_len == 4 && cur->varstr[0] == 'P' && cur->varstr[1] == 'S') - cmdedit_update_prompt(); -#if ENABLE_HUSH_GETOPTS - /* defoptindvar is a "OPTIND=..." constant string */ - if (strncmp(cur->varstr, defoptindvar, 7) == 0) - G.getopt_count = 0; -#endif if (cur->flg_export) { if (flags & SETFLAG_UNEXPORT) { cur->flg_export = 0; @@ -2265,6 +2263,23 @@ static int set_local_var(char *str, unsigned flags) } } free(free_me); + + /* Handle special names */ + if (name_len == 4 && cur->varstr[0] == 'P' && cur->varstr[1] == 'S') + cmdedit_update_prompt(); + else + if ((ENABLE_HUSH_LINENO_VAR || ENABLE_HUSH_GETOPTS) && name_len == 7) { +#if ENABLE_HUSH_LINENO_VAR + if (G.lineno_var && strncmp(cur->varstr, "LINENO", 6) == 0) + G.lineno_var = NULL; +#endif +#if ENABLE_HUSH_GETOPTS + /* defoptindvar is a "OPTIND=..." constant string */ + if (strncmp(cur->varstr, defoptindvar, 7) == 0) + G.getopt_count = 0; +#endif + } + return 0; } @@ -2282,14 +2297,16 @@ static int unset_local_var_len(const char *name, int name_len) if (!name) return EXIT_SUCCESS; + if ((ENABLE_HUSH_LINENO_VAR || ENABLE_HUSH_GETOPTS) && name_len == 6) { #if ENABLE_HUSH_GETOPTS - if (name_len == 6 && strncmp(name, "OPTIND", 6) == 0) - G.getopt_count = 0; + if (strncmp(name, "OPTIND", 6) == 0) + G.getopt_count = 0; #endif #if ENABLE_HUSH_LINENO_VAR - if (name_len == 6 && G.lineno_var && strncmp(name, "LINENO", 6) == 0) - G.lineno_var = NULL; + if (G.lineno_var && strncmp(name, "LINENO", 6) == 0) + G.lineno_var = NULL; #endif + } cur_pp = &G.top_var; while ((cur = *cur_pp) != NULL) { @@ -2355,13 +2372,12 @@ static void add_vars(struct variable *var) * which attempts to overwrite it. * The strings[] vector itself is freed. */ -static struct variable *set_vars_and_save_old(char **strings) +static void set_vars_and_save_old(char **strings) { char **s; - struct variable *old = NULL; if (!strings) - return old; + return; s = strings; while (*s) { @@ -2389,12 +2405,10 @@ static struct variable *set_vars_and_save_old(char **strings) do { *p = p[1]; p++; } while (*p); goto next; } - /* Remove variable from global linked list */ - debug_printf_env("%s: removing '%s'\n", __func__, var_p->varstr); - *var_pp = var_p->next; - /* Add it to returned list */ - var_p->next = old; - old = var_p; + /* below, set_local_var() with nest level will + * "shadow" (remove) this variable from + * global linked list. + */ } //bb_error_msg("G.var_nest_level:%d", G.var_nest_level); set_local_var(*s, (G.var_nest_level << SETFLAG_VARLVL_SHIFT) | SETFLAG_EXPORT); @@ -2405,7 +2419,6 @@ static struct variable *set_vars_and_save_old(char **strings) next: ; } free(strings); - return old; } @@ -7598,6 +7611,7 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, char **argv_expanded) { const struct built_in_command *x; + struct variable **sv_shadowed; char **new_env; IF_HUSH_COMMAND(char opt_vV = 0;) IF_HUSH_FUNCTIONS(const struct function *funcp;) @@ -7614,13 +7628,14 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, _exit(EXIT_SUCCESS); } + sv_shadowed = G.shadowed_vars_pp; #if BB_MMU - set_vars_and_save_old(new_env); - /* we can destroy set_vars_and_save_old's return value, - * to save memory */ + G.shadowed_vars_pp = NULL; /* "don't save, free them instead" */ #else - nommu_save->old_vars = set_vars_and_save_old(new_env); + G.shadowed_vars_pp = &nommu_save->old_vars; #endif + set_vars_and_save_old(new_env); + G.shadowed_vars_pp = sv_shadowed; if (argv_expanded) { argv = argv_expanded; @@ -8181,7 +8196,6 @@ static int checkjobs_and_fg_shell(struct pipe *fg_pipe) redirect_and_varexp_helper(old_vars_p, command, squirrel) #endif static int redirect_and_varexp_helper( - struct variable **old_vars_p, struct command *command, struct squirrel **sqp, char **argv_expanded) @@ -8196,7 +8210,7 @@ static int redirect_and_varexp_helper( dump_cmd_in_x_mode(new_env); dump_cmd_in_x_mode(argv_expanded); /* this takes ownership of new_env[i] elements, and frees new_env: */ - *old_vars_p = set_vars_and_save_old(new_env); + set_vars_and_save_old(new_env); } return rcode; } @@ -8288,6 +8302,7 @@ static NOINLINE int run_pipe(struct pipe *pi) const struct built_in_command *x; IF_HUSH_FUNCTIONS(const struct function *funcp;) IF_NOT_HUSH_FUNCTIONS(enum { funcp = 0 };) + struct variable **sv_shadowed; struct variable *old_vars; #if ENABLE_HUSH_LINENO_VAR @@ -8338,13 +8353,11 @@ static NOINLINE int run_pipe(struct pipe *pi) /* Expand the rest into (possibly) many strings each */ #if defined(CMD_SINGLEWORD_NOGLOB) - if (command->cmd_type == CMD_SINGLEWORD_NOGLOB) { + if (command->cmd_type == CMD_SINGLEWORD_NOGLOB) argv_expanded = expand_strvec_to_strvec_singleword_noglob(argv + command->assignment_cnt); - } else + else #endif - { argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt); - } /* if someone gives us an empty string: `cmd with empty output` */ //TODO: what about: var=EXPR `` >FILE ? will var be set? Will FILE be created? @@ -8354,17 +8367,19 @@ static NOINLINE int run_pipe(struct pipe *pi) return G.last_exitcode; } -#if ENABLE_HUSH_FUNCTIONS + old_vars = NULL; + sv_shadowed = G.shadowed_vars_pp; + /* Check if argv[0] matches any functions (this goes before bltins) */ - funcp = find_function(argv_expanded[0]); -#endif - x = NULL; - if (!funcp) + IF_HUSH_FUNCTIONS(funcp = find_function(argv_expanded[0]);) + IF_HUSH_FUNCTIONS(x = NULL;) + IF_HUSH_FUNCTIONS(if (!funcp)) x = find_builtin(argv_expanded[0]); if (x || funcp) { if (x && x->b_function == builtin_exec && argv_expanded[1] == NULL) { debug_printf("exec with redirects only\n"); rcode = setup_redirects(command, NULL); +//TODO: what about: var=EXPR exec >FILE ? will var be set? /* rcode=1 can be if redir file can't be opened */ goto clean_up_and_ret1; } @@ -8373,9 +8388,22 @@ static NOINLINE int run_pipe(struct pipe *pi) * a=b true; env | grep ^a= */ enter_var_nest_level(); - rcode = redirect_and_varexp_helper(&old_vars, command, &squirrel, argv_expanded); + /* Collect all variables "shadowed" by helper + * (IOW: old vars overridden by "var1=val1 var2=val2 cmd..." syntax) + * into old_vars list: + */ + G.shadowed_vars_pp = &old_vars; + rcode = redirect_and_varexp_helper(command, &squirrel, argv_expanded); if (rcode == 0) { if (!funcp) { + /* Do not collect *to old_vars list* vars shadowed + * by e.g. "local VAR" builtin (collect them + * in the previously nested list instead): + * don't want them to be restored immediately + * after "local" completes. + */ + G.shadowed_vars_pp = sv_shadowed; + debug_printf_exec(": builtin '%s' '%s'...\n", x->b_cmd, argv_expanded[1]); fflush_all(); @@ -8384,17 +8412,15 @@ static NOINLINE int run_pipe(struct pipe *pi) } #if ENABLE_HUSH_FUNCTIONS else { - struct variable **sv = G.shadowed_vars_pp; - /* Make "local" builtins in the called function - * stash shadowed variables in old_vars list - */ - G.shadowed_vars_pp = &old_vars; - debug_printf_exec(": function '%s' '%s'...\n", funcp->name, argv_expanded[1]); rcode = run_function(funcp, argv_expanded) & 0xff; - - G.shadowed_vars_pp = sv; + /* + * But do collect *to old_vars list* vars shadowed + * within function execution. To that end, restore + * this pointer _after_ function run: + */ + G.shadowed_vars_pp = sv_shadowed; } #endif } @@ -8405,7 +8431,11 @@ static NOINLINE int run_pipe(struct pipe *pi) goto must_fork; enter_var_nest_level(); - rcode = redirect_and_varexp_helper(&old_vars, command, &squirrel, argv_expanded); + /* Collect all variables "shadowed" by helper into old_vars list */ + G.shadowed_vars_pp = &old_vars; + rcode = redirect_and_varexp_helper(command, &squirrel, argv_expanded); + G.shadowed_vars_pp = sv_shadowed; + if (rcode == 0) { debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", argv_expanded[0], argv_expanded[1]); -- cgit v1.2.3-55-g6feb From 41d8f1081378ec79586d59e7d2a31380b6f95577 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 5 Apr 2018 14:41:21 +0200 Subject: hush: fix corner cases with exec in empty expansions Cases like these: var=val exec >redir var=val `` >redir function old new delta run_pipe 1701 1723 +22 redirect_and_varexp_helper 56 55 -1 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 1/1 up/down: 22/-1) Total: 21 bytes Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-redir/redir_exec1.right | 2 ++ shell/ash_test/ash-redir/redir_exec1.tests | 2 ++ shell/hush.c | 43 +++++++++++++++++----------- shell/hush_test/hush-redir/redir_exec1.right | 3 ++ shell/hush_test/hush-redir/redir_exec1.tests | 2 ++ 5 files changed, 35 insertions(+), 17 deletions(-) create mode 100644 shell/ash_test/ash-redir/redir_exec1.right create mode 100755 shell/ash_test/ash-redir/redir_exec1.tests create mode 100644 shell/hush_test/hush-redir/redir_exec1.right create mode 100755 shell/hush_test/hush-redir/redir_exec1.tests diff --git a/shell/ash_test/ash-redir/redir_exec1.right b/shell/ash_test/ash-redir/redir_exec1.right new file mode 100644 index 000000000..d4393d10c --- /dev/null +++ b/shell/ash_test/ash-redir/redir_exec1.right @@ -0,0 +1,2 @@ +redir_exec1.tests: line 1: can't create /cant/be/created: nonexistent directory +First diff --git a/shell/ash_test/ash-redir/redir_exec1.tests b/shell/ash_test/ash-redir/redir_exec1.tests new file mode 100755 index 000000000..290e1cb39 --- /dev/null +++ b/shell/ash_test/ash-redir/redir_exec1.tests @@ -0,0 +1,2 @@ +v=`echo First >&2` exec >/cant/be/created +echo One:$? diff --git a/shell/hush.c b/shell/hush.c index 24ae237d7..43702360a 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -8200,19 +8200,21 @@ static int redirect_and_varexp_helper( struct squirrel **sqp, char **argv_expanded) { + /* Assignments occur before redirects. Try: + * a=`sleep 1` sleep 2 3>/qwe/rty + */ + + char **new_env = expand_assignments(command->argv, command->assignment_cnt); + dump_cmd_in_x_mode(new_env); + dump_cmd_in_x_mode(argv_expanded); + /* this takes ownership of new_env[i] elements, and frees new_env: */ + set_vars_and_save_old(new_env); + /* setup_redirects acts on file descriptors, not FILEs. * This is perfect for work that comes after exec(). * Is it really safe for inline use? Experimentally, * things seem to work. */ - int rcode = setup_redirects(command, sqp); - if (rcode == 0) { - char **new_env = expand_assignments(command->argv, command->assignment_cnt); - dump_cmd_in_x_mode(new_env); - dump_cmd_in_x_mode(argv_expanded); - /* this takes ownership of new_env[i] elements, and frees new_env: */ - set_vars_and_save_old(new_env); - } - return rcode; + return setup_redirects(command, sqp); } static NOINLINE int run_pipe(struct pipe *pi) { @@ -8315,6 +8317,7 @@ static NOINLINE int run_pipe(struct pipe *pi) * Ensure redirects take effect (that is, create files). * Try "a=t >file" */ + only_assignments: G.expand_exitcode = 0; rcode = setup_redirects(command, &squirrel); @@ -8359,12 +8362,10 @@ static NOINLINE int run_pipe(struct pipe *pi) #endif argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt); - /* if someone gives us an empty string: `cmd with empty output` */ -//TODO: what about: var=EXPR `` >FILE ? will var be set? Will FILE be created? + /* If someone gives us an empty string: `cmd with empty output` */ if (!argv_expanded[0]) { free(argv_expanded); - debug_leave(); - return G.last_exitcode; + goto only_assignments; } old_vars = NULL; @@ -8378,9 +8379,17 @@ static NOINLINE int run_pipe(struct pipe *pi) if (x || funcp) { if (x && x->b_function == builtin_exec && argv_expanded[1] == NULL) { debug_printf("exec with redirects only\n"); - rcode = setup_redirects(command, NULL); -//TODO: what about: var=EXPR exec >FILE ? will var be set? + /* + * Variable assignments are executed, but then "forgotten": + * a=`sleep 1;echo A` exec 3>&-; echo $a + * sleeps, but prints nothing. + */ + enter_var_nest_level(); + G.shadowed_vars_pp = &old_vars; + rcode = redirect_and_varexp_helper(command, /*squirrel:*/ NULL, argv_expanded); + G.shadowed_vars_pp = sv_shadowed; /* rcode=1 can be if redir file can't be opened */ + goto clean_up_and_ret1; } @@ -8452,9 +8461,10 @@ static NOINLINE int run_pipe(struct pipe *pi) } else goto must_fork; + restore_redirects(squirrel); + clean_up_and_ret1: leave_var_nest_level(); add_vars(old_vars); - restore_redirects(squirrel); /* * Try "usleep 99999999" + ^C + "echo $?" @@ -8474,7 +8484,6 @@ static NOINLINE int run_pipe(struct pipe *pi) if (sigismember(&G.pending_set, SIGINT)) rcode = 128 + SIGINT; } - clean_up_and_ret1: free(argv_expanded); IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) debug_leave(); diff --git a/shell/hush_test/hush-redir/redir_exec1.right b/shell/hush_test/hush-redir/redir_exec1.right new file mode 100644 index 000000000..6ff8fc832 --- /dev/null +++ b/shell/hush_test/hush-redir/redir_exec1.right @@ -0,0 +1,3 @@ +First +hush: can't open '/cant/be/created': No such file or directory +One:1 diff --git a/shell/hush_test/hush-redir/redir_exec1.tests b/shell/hush_test/hush-redir/redir_exec1.tests new file mode 100755 index 000000000..290e1cb39 --- /dev/null +++ b/shell/hush_test/hush-redir/redir_exec1.tests @@ -0,0 +1,2 @@ +v=`echo First >&2` exec >/cant/be/created +echo One:$? -- cgit v1.2.3-55-g6feb From 21b7f1b6b67f191ca187910a5fd4cd2cb1eb5353 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 5 Apr 2018 15:15:53 +0200 Subject: hush: fix a few more corner cases with empty-expanding `cmds` See added testcases function old new delta run_pipe 1723 1784 +61 Signed-off-by: Denys Vlasenko --- shell/hush.c | 20 ++++++++++++-------- shell/hush_test/hush-psubst/falsetick2.right | 1 + shell/hush_test/hush-psubst/falsetick2.tests | 3 +++ shell/hush_test/hush-redir/redir_backquote1.right | 11 +++++++++++ shell/hush_test/hush-redir/redir_backquote1.tests | 19 +++++++++++++++++++ 5 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 shell/hush_test/hush-psubst/falsetick2.right create mode 100755 shell/hush_test/hush-psubst/falsetick2.tests create mode 100644 shell/hush_test/hush-redir/redir_backquote1.right create mode 100755 shell/hush_test/hush-redir/redir_backquote1.tests diff --git a/shell/hush.c b/shell/hush.c index 43702360a..577faf466 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -608,7 +608,7 @@ typedef enum redir_type { struct command { pid_t pid; /* 0 if exited */ - int assignment_cnt; /* how many argv[i] are assignments? */ + unsigned assignment_cnt; /* how many argv[i] are assignments? */ #if ENABLE_HUSH_LINENO_VAR unsigned lineno; #endif @@ -8317,25 +8317,26 @@ static NOINLINE int run_pipe(struct pipe *pi) * Ensure redirects take effect (that is, create files). * Try "a=t >file" */ - only_assignments: + unsigned i; G.expand_exitcode = 0; - + only_assignments: rcode = setup_redirects(command, &squirrel); restore_redirects(squirrel); + /* Set shell variables */ if (G_x_mode) bb_putchar_stderr('+'); - while (*argv) { - char *p = expand_string_to_string(*argv, /*unbackslash:*/ 1); + i = 0; + while (i < command->assignment_cnt) { + char *p = expand_string_to_string(argv[i], /*unbackslash:*/ 1); if (G_x_mode) fprintf(stderr, " %s", p); - debug_printf_exec("set shell var:'%s'->'%s'\n", - *argv, p); + debug_printf_env("set shell var:'%s'->'%s'\n", *argv, p); if (set_local_var(p, /*flag:*/ 0)) { /* assignment to readonly var / putenv error? */ rcode = 1; } - argv++; + i++; } if (G_x_mode) bb_putchar_stderr('\n'); @@ -8365,6 +8366,8 @@ static NOINLINE int run_pipe(struct pipe *pi) /* If someone gives us an empty string: `cmd with empty output` */ if (!argv_expanded[0]) { free(argv_expanded); + /* `false` still has to set exitcode 1 */ + G.expand_exitcode = G.last_exitcode; goto only_assignments; } @@ -10021,6 +10024,7 @@ static int helper_export_local(char **argv, unsigned flags) /* (Un)exporting/making local NAME=VALUE */ name = xstrdup(name); } + debug_printf_env("%s: set_local_var('%s')\n", __func__, name); if (set_local_var(name, flags)) return EXIT_FAILURE; } while (*++argv); diff --git a/shell/hush_test/hush-psubst/falsetick2.right b/shell/hush_test/hush-psubst/falsetick2.right new file mode 100644 index 000000000..670f560f1 --- /dev/null +++ b/shell/hush_test/hush-psubst/falsetick2.right @@ -0,0 +1 @@ +Two:2 v:[] diff --git a/shell/hush_test/hush-psubst/falsetick2.tests b/shell/hush_test/hush-psubst/falsetick2.tests new file mode 100755 index 000000000..cfbd1a5de --- /dev/null +++ b/shell/hush_test/hush-psubst/falsetick2.tests @@ -0,0 +1,3 @@ +v=v +v=`exit 2` `false` +echo Two:$? v:"[$v]" diff --git a/shell/hush_test/hush-redir/redir_backquote1.right b/shell/hush_test/hush-redir/redir_backquote1.right new file mode 100644 index 000000000..810cc2314 --- /dev/null +++ b/shell/hush_test/hush-redir/redir_backquote1.right @@ -0,0 +1,11 @@ +hush: can't open '/cant/be/created': No such file or directory +First +One:1 v1:[] +hush: can't open '/cant/be/created': No such file or directory +Second +One:1 v2:[] +Third +Zero:0 v3:[] +Fourth +Zero:0 v4:[] +Zero:0 v5:[1] diff --git a/shell/hush_test/hush-redir/redir_backquote1.tests b/shell/hush_test/hush-redir/redir_backquote1.tests new file mode 100755 index 000000000..41bb4913c --- /dev/null +++ b/shell/hush_test/hush-redir/redir_backquote1.tests @@ -0,0 +1,19 @@ +v=v +v=`echo First >&2` `` >/cant/be/created +echo One:$? v1:"[$v]" + +v=v +v=`echo Second >&2` `true` >/cant/be/created +echo One:$? v2:"[$v]" + +v=v +v=`echo Third >&2` `true` 2>/dev/null +echo Zero:$? v3:"[$v]" + +v=v +v=`echo Fourth >&2` `false` 2>/dev/null +echo Zero:$? v4:"[$v]" + +v=v +v=`echo $?` `false` 2>/dev/null +echo Zero:$? v5:"[$v]" -- cgit v1.2.3-55-g6feb From d878ccca9cc2537f4046a618aa9122967aa2ce3a Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 5 Apr 2018 15:21:34 +0200 Subject: placate gcc 8.0.1 sprintf overflow warnings in config tools Signed-off-by: Denys Vlasenko --- scripts/kconfig/confdata.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/kconfig/confdata.c b/scripts/kconfig/confdata.c index 8f4ecbd33..b05b96e45 100644 --- a/scripts/kconfig/confdata.c +++ b/scripts/kconfig/confdata.c @@ -334,7 +334,9 @@ int conf_write(const char *name) struct symbol *sym; struct menu *menu; const char *basename; - char dirname[128], tmpname[128], newname[128]; + char dirname[128]; + char tmpname[256]; + char newname[256]; int type, l; const char *str; time_t now; -- cgit v1.2.3-55-g6feb From f2ed39b93075f384f9e82f5130f94174b05dc300 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 5 Apr 2018 16:46:49 +0200 Subject: hush: implement "hush -s" function old new delta hush_main 1015 1031 +16 packed_usage 32757 32745 -12 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 1/1 up/down: 16/-12) Total: 4 bytes Signed-off-by: Denys Vlasenko --- shell/ash.c | 2 +- shell/hush.c | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/shell/ash.c b/shell/ash.c index 558601543..56fba4a57 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -13948,7 +13948,7 @@ init(void) //usage:#define ash_trivial_usage -//usage: "[-/+OPTIONS] [-/+o OPT]... [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]" +//usage: "[-/+OPTIONS] [-/+o OPT]... [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS] / -s [ARGS]]" //usage:#define ash_full_usage "\n\n" //usage: "Unix shell interpreter" diff --git a/shell/hush.c b/shell/hush.c index 577faf466..42e311839 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -312,13 +312,13 @@ //kbuild:lib-$(CONFIG_BASH_IS_HUSH) += hush.o match.o shell_common.o //kbuild:lib-$(CONFIG_HUSH_RANDOM_SUPPORT) += random.o -/* -i (interactive) and -s (read stdin) are also accepted, - * but currently do nothing, therefore aren't shown in help. +/* -i (interactive) is also accepted, + * but does nothing, therefore not shown in help. * NOMMU-specific options are not meant to be used by users, * therefore we don't show them either. */ //usage:#define hush_trivial_usage -//usage: "[-enxl] [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]" +//usage: "[-enxl] [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS] / -s [ARGS]]" //usage:#define hush_full_usage "\n\n" //usage: "Unix shell interpreter" @@ -9150,9 +9150,9 @@ int hush_main(int argc, char **argv) { enum { OPT_login = (1 << 0), + OPT_s = (1 << 1), }; unsigned flags; - int opt; unsigned builtin_argc; char **e; struct variable *cur_var; @@ -9275,7 +9275,7 @@ int hush_main(int argc, char **argv) flags = (argv[0] && argv[0][0] == '-') ? OPT_login : 0; builtin_argc = 0; while (1) { - opt = getopt(argc, argv, "+c:exinsl" + int opt = getopt(argc, argv, "+c:exinsl" #if !BB_MMU "<:$:R:V:" # if ENABLE_HUSH_FUNCTIONS @@ -9333,8 +9333,7 @@ int hush_main(int argc, char **argv) /* G_interactive_fd++; */ break; case 's': - /* "-s" means "read from stdin", but this is how we always - * operate, so simply do nothing here. */ + flags |= OPT_s; break; case 'l': flags |= OPT_login; @@ -9437,7 +9436,8 @@ int hush_main(int argc, char **argv) */ } - if (G.global_argv[1]) { + /* -s is: hush -s ARGV1 ARGV2 (no SCRIPT) */ + if (!(flags & OPT_s) && G.global_argv[1]) { FILE *input; /* * "bash