From 621fc50e83f7446a060f0b9689dc8dc59ee0743a Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 24 Jul 2017 12:42:17 +0200 Subject: hush: fix a case when redirect to a closed fd #1 is not restoring (closing) it function old new delta setup_redirects 200 245 +45 append_squirrel - 41 +41 save_fds_on_redirect 256 221 -35 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 1/1 up/down: 86/-35) Total: 51 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 34 +++++++++++++++++++++++++++------- shell/hush_test/hush-redir/redir.right | 2 ++ shell/hush_test/hush-redir/redir.tests | 6 ++++++ 3 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 shell/hush_test/hush-redir/redir.right create mode 100755 shell/hush_test/hush-redir/redir.tests (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 309ed2139..20b092398 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -6643,8 +6643,18 @@ struct squirrel { /* moved_to = -1: fd was opened by redirect; close orig_fd after redir */ }; +static struct squirrel *append_squirrel(struct squirrel *sq, int i, int orig, int moved) +{ + sq = xrealloc(sq, (i + 2) * sizeof(sq[0])); + sq[i].orig_fd = orig; + sq[i].moved_to = moved; + sq[i+1].orig_fd = -1; /* end marker */ + return sq; +} + static struct squirrel *add_squirrel(struct squirrel *sq, int fd, int avoid_fd) { + int moved_to; int i = 0; if (sq) while (sq[i].orig_fd >= 0) { @@ -6664,15 +6674,12 @@ static struct squirrel *add_squirrel(struct squirrel *sq, int fd, int avoid_fd) i++; } - sq = xrealloc(sq, (i + 2) * sizeof(sq[0])); - sq[i].orig_fd = fd; /* If this fd is open, we move and remember it; if it's closed, moved_to = -1 */ - sq[i].moved_to = fcntl_F_DUPFD(fd, avoid_fd); - debug_printf_redir("redirect_fd %d: previous fd is moved to %d (-1 if it was closed)\n", fd, sq[i].moved_to); - if (sq[i].moved_to < 0 && errno != EBADF) + moved_to = fcntl_F_DUPFD(fd, avoid_fd); + debug_printf_redir("redirect_fd %d: previous fd is moved to %d (-1 if it was closed)\n", fd, moved_to); + if (moved_to < 0 && errno != EBADF) xfunc_die(); - sq[i+1].orig_fd = -1; /* end marker */ - return sq; + return append_squirrel(sq, i, fd, moved_to); } /* fd: redirect wants this fd to be used (e.g. 3>file). @@ -6778,6 +6785,19 @@ static int setup_redirects(struct command *prog, struct squirrel **sqp) */ return 1; } + if (openfd == redir->rd_fd && sqp) { + /* open() gave us precisely the fd we wanted. + * This means that this fd was not busy + * (not opened to anywhere). + * Remember to close it on restore: + */ + struct squirrel *sq = *sqp; + int i = 0; + if (sq) while (sq[i].orig_fd >= 0) + i++; + *sqp = append_squirrel(sq, i, openfd, -1); /* -1 = "it was closed" */ + debug_printf_redir("redir to previously closed fd %d\n", openfd); + } } else { /* "rd_fd<*>rd_dup" or "rd_fd<*>-" cases */ openfd = redir->rd_dup; diff --git a/shell/hush_test/hush-redir/redir.right b/shell/hush_test/hush-redir/redir.right new file mode 100644 index 000000000..4de5ec701 --- /dev/null +++ b/shell/hush_test/hush-redir/redir.right @@ -0,0 +1,2 @@ +hush: write error: Bad file descriptor +TEST diff --git a/shell/hush_test/hush-redir/redir.tests b/shell/hush_test/hush-redir/redir.tests new file mode 100755 index 000000000..7a1a66806 --- /dev/null +++ b/shell/hush_test/hush-redir/redir.tests @@ -0,0 +1,6 @@ +# test: closed fds should stay closed +exec 1>&- +echo TEST >TEST +echo JUNK # lost: stdout is closed +cat TEST >&2 +rm TEST -- cgit v1.2.3-55-g6feb From 4a1d8f6d6e4e1cafc4c2b611126401bf85812a64 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 24 Jul 2017 12:49:49 +0200 Subject: ash: add most of hush process subst tests ash passes these. Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-psubst/emptytick.right | 17 +++++++++++++++++ shell/ash_test/ash-psubst/emptytick.tests | 16 ++++++++++++++++ shell/ash_test/ash-psubst/tick.right | 2 ++ shell/ash_test/ash-psubst/tick.tests | 4 ++++ shell/ash_test/ash-psubst/tick2.right | 1 + shell/ash_test/ash-psubst/tick2.tests | 5 +++++ shell/ash_test/ash-psubst/tick3.right | 6 ++++++ shell/ash_test/ash-psubst/tick3.tests | 14 ++++++++++++++ shell/ash_test/ash-psubst/tick4.right | 7 +++++++ shell/ash_test/ash-psubst/tick4.tests | 7 +++++++ shell/ash_test/ash-psubst/tick_huge.right | 3 +++ shell/ash_test/ash-psubst/tick_huge.tests | 7 +++++++ 12 files changed, 89 insertions(+) create mode 100644 shell/ash_test/ash-psubst/emptytick.right create mode 100755 shell/ash_test/ash-psubst/emptytick.tests create mode 100644 shell/ash_test/ash-psubst/tick.right create mode 100755 shell/ash_test/ash-psubst/tick.tests create mode 100644 shell/ash_test/ash-psubst/tick2.right create mode 100755 shell/ash_test/ash-psubst/tick2.tests create mode 100644 shell/ash_test/ash-psubst/tick3.right create mode 100755 shell/ash_test/ash-psubst/tick3.tests create mode 100644 shell/ash_test/ash-psubst/tick4.right create mode 100755 shell/ash_test/ash-psubst/tick4.tests create mode 100644 shell/ash_test/ash-psubst/tick_huge.right create mode 100755 shell/ash_test/ash-psubst/tick_huge.tests (limited to 'shell') diff --git a/shell/ash_test/ash-psubst/emptytick.right b/shell/ash_test/ash-psubst/emptytick.right new file mode 100644 index 000000000..7629deba6 --- /dev/null +++ b/shell/ash_test/ash-psubst/emptytick.right @@ -0,0 +1,17 @@ +0 +0 +./emptytick.tests: line 3: : Permission denied +127 +./emptytick.tests: line 4: : Permission denied +127 +0 +0 +0 +0 +./emptytick.tests: line 10: : Permission denied +127 +./emptytick.tests: line 11: : Permission denied +127 +0 +0 +./emptytick.tests: exec: line 15: : Permission denied diff --git a/shell/ash_test/ash-psubst/emptytick.tests b/shell/ash_test/ash-psubst/emptytick.tests new file mode 100755 index 000000000..eaffafb22 --- /dev/null +++ b/shell/ash_test/ash-psubst/emptytick.tests @@ -0,0 +1,16 @@ +true; ``; echo $? +false; ``; echo $? +true; `""`; echo $? +false; `""`; echo $? +true; ` `; echo $? +false; ` `; echo $? + +true; $(); echo $? +false; $(); echo $? +true; $(""); echo $? +false; $(""); echo $? +true; $( ); echo $? +false; $( ); echo $? + +exec ''; echo $? +echo Not reached diff --git a/shell/ash_test/ash-psubst/tick.right b/shell/ash_test/ash-psubst/tick.right new file mode 100644 index 000000000..6ed281c75 --- /dev/null +++ b/shell/ash_test/ash-psubst/tick.right @@ -0,0 +1,2 @@ +1 +1 diff --git a/shell/ash_test/ash-psubst/tick.tests b/shell/ash_test/ash-psubst/tick.tests new file mode 100755 index 000000000..1f749a9cd --- /dev/null +++ b/shell/ash_test/ash-psubst/tick.tests @@ -0,0 +1,4 @@ +true +false; echo `echo $?` +true +{ false; echo `echo $?`; } diff --git a/shell/ash_test/ash-psubst/tick2.right b/shell/ash_test/ash-psubst/tick2.right new file mode 100644 index 000000000..216c883b8 --- /dev/null +++ b/shell/ash_test/ash-psubst/tick2.right @@ -0,0 +1 @@ +BAZ diff --git a/shell/ash_test/ash-psubst/tick2.tests b/shell/ash_test/ash-psubst/tick2.tests new file mode 100755 index 000000000..db4e944fe --- /dev/null +++ b/shell/ash_test/ash-psubst/tick2.tests @@ -0,0 +1,5 @@ +if false; then + echo "FOO" + tmp=`echo BAR >&2` +fi +echo BAZ diff --git a/shell/ash_test/ash-psubst/tick3.right b/shell/ash_test/ash-psubst/tick3.right new file mode 100644 index 000000000..00f267ae5 --- /dev/null +++ b/shell/ash_test/ash-psubst/tick3.right @@ -0,0 +1,6 @@ +\TESTZZBEST +$TEST +Q +a\bc +11-$a-\t-\-\"-`-\--\z-\*-\?-22 33-$a-\t-\-"-`-\--\z-\*-\?-44 +done:0 diff --git a/shell/ash_test/ash-psubst/tick3.tests b/shell/ash_test/ash-psubst/tick3.tests new file mode 100755 index 000000000..3aeb241c3 --- /dev/null +++ b/shell/ash_test/ash-psubst/tick3.tests @@ -0,0 +1,14 @@ +test "$CONFIG_FEATURE_FANCY_ECHO" = "y" || exit 77 + +TEST=Q +# \` is special +echo `echo '\'TEST\`echo ZZ\`BEST` +# \$ and \\ are special +echo `echo \\$TEST` +echo `echo \$TEST` +echo a`echo \\\\b`c + +# \" is not special if in unquoted `cmd` (passed verbatim WITH \), +# but is special in quoted one +echo `echo 11'-$a-\t-\\-\"-\`-\--\z-\*-\?-'22` "`echo 33'-$a-\t-\\-\"-\`-\--\z-\*-\?-'44`" +echo done:$? diff --git a/shell/ash_test/ash-psubst/tick4.right b/shell/ash_test/ash-psubst/tick4.right new file mode 100644 index 000000000..d8030eafd --- /dev/null +++ b/shell/ash_test/ash-psubst/tick4.right @@ -0,0 +1,7 @@ +(TEST) BEST +TEST) BEST +((TEST) BEST +) +abc +a)c +OK: 0 diff --git a/shell/ash_test/ash-psubst/tick4.tests b/shell/ash_test/ash-psubst/tick4.tests new file mode 100755 index 000000000..f2305fb3d --- /dev/null +++ b/shell/ash_test/ash-psubst/tick4.tests @@ -0,0 +1,7 @@ +echo $(echo '(TEST)' BEST) +echo $(echo 'TEST)' BEST) +echo $(echo \(\(TEST\) BEST) +echo $(echo \)) +echo $(echo a"`echo "b"`"c ) +echo $(echo a"`echo ")"`"c ) +echo OK: $? diff --git a/shell/ash_test/ash-psubst/tick_huge.right b/shell/ash_test/ash-psubst/tick_huge.right new file mode 100644 index 000000000..11740f674 --- /dev/null +++ b/shell/ash_test/ash-psubst/tick_huge.right @@ -0,0 +1,3 @@ +546ed3f5c81c780d3ab86ada14824237 - +546ed3f5c81c780d3ab86ada14824237 - +End diff --git a/shell/ash_test/ash-psubst/tick_huge.tests b/shell/ash_test/ash-psubst/tick_huge.tests new file mode 100755 index 000000000..acce92fb2 --- /dev/null +++ b/shell/ash_test/ash-psubst/tick_huge.tests @@ -0,0 +1,7 @@ +# This creates 120k file +yes "123456789 123456789 123456789 123456789" | head -3000 >>"$0.tmp" + +echo "`cat $0.tmp`" | md5sum +rm "$0.tmp" +yes "123456789 123456789 123456789 123456789" | head -3000 | md5sum +echo End -- cgit v1.2.3-55-g6feb From ca50caacad8354fe97eb0da23075521156c0c0d6 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 24 Jul 2017 18:51:40 +0200 Subject: shell: some additions to *sh-misc/* tests Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-misc/assignment2.right | 2 ++ shell/ash_test/ash-misc/assignment2.tests | 3 +++ shell/ash_test/ash-misc/empty_args.right | 6 ++++++ shell/ash_test/ash-misc/empty_args.tests | 9 +++++++++ shell/ash_test/ash-misc/env_and_func.right | 2 ++ shell/ash_test/ash-misc/env_and_func.tests | 8 ++++++++ shell/hush_test/hush-misc/env_and_func.tests | 4 ++++ 7 files changed, 34 insertions(+) create mode 100644 shell/ash_test/ash-misc/assignment2.right create mode 100755 shell/ash_test/ash-misc/assignment2.tests create mode 100644 shell/ash_test/ash-misc/empty_args.right create mode 100755 shell/ash_test/ash-misc/empty_args.tests create mode 100644 shell/ash_test/ash-misc/env_and_func.right create mode 100755 shell/ash_test/ash-misc/env_and_func.tests (limited to 'shell') diff --git a/shell/ash_test/ash-misc/assignment2.right b/shell/ash_test/ash-misc/assignment2.right new file mode 100644 index 000000000..179c71c5a --- /dev/null +++ b/shell/ash_test/ash-misc/assignment2.right @@ -0,0 +1,2 @@ +./assignment2.tests: line 2: a=b: not found +127 diff --git a/shell/ash_test/ash-misc/assignment2.tests b/shell/ash_test/ash-misc/assignment2.tests new file mode 100755 index 000000000..f6938434c --- /dev/null +++ b/shell/ash_test/ash-misc/assignment2.tests @@ -0,0 +1,3 @@ +# This must not be interpreted as an assignment +a''=b true +echo $? diff --git a/shell/ash_test/ash-misc/empty_args.right b/shell/ash_test/ash-misc/empty_args.right new file mode 100644 index 000000000..968b5a4d9 --- /dev/null +++ b/shell/ash_test/ash-misc/empty_args.right @@ -0,0 +1,6 @@ +Null 0th arg: +./empty_args.tests: line 2: : Permission denied +127 +Null 1st arg: +0 +Null arg in exec: diff --git a/shell/ash_test/ash-misc/empty_args.tests b/shell/ash_test/ash-misc/empty_args.tests new file mode 100755 index 000000000..efce5494a --- /dev/null +++ b/shell/ash_test/ash-misc/empty_args.tests @@ -0,0 +1,9 @@ +echo Null 0th arg: +"" +echo $? +echo Null 1st arg: +# printf without args would print usage info +printf "" +echo $? +echo Null arg in exec: +exec printf "" diff --git a/shell/ash_test/ash-misc/env_and_func.right b/shell/ash_test/ash-misc/env_and_func.right new file mode 100644 index 000000000..5fc3488ae --- /dev/null +++ b/shell/ash_test/ash-misc/env_and_func.right @@ -0,0 +1,2 @@ +var=val +var=val diff --git a/shell/ash_test/ash-misc/env_and_func.tests b/shell/ash_test/ash-misc/env_and_func.tests new file mode 100755 index 000000000..3efef1a41 --- /dev/null +++ b/shell/ash_test/ash-misc/env_and_func.tests @@ -0,0 +1,8 @@ +var=old +f() { echo "var=$var"; } +# bash: POSIXLY_CORRECT behavior is to "leak" new variable values +# out of function invocations (similar to "special builtins" behavior); +# but in "bash mode", they don't leak. +# hush does not "leak" values. ash does. +var=val f +echo "var=$var" diff --git a/shell/hush_test/hush-misc/env_and_func.tests b/shell/hush_test/hush-misc/env_and_func.tests index 1d4eaf3a7..3efef1a41 100755 --- a/shell/hush_test/hush-misc/env_and_func.tests +++ b/shell/hush_test/hush-misc/env_and_func.tests @@ -1,4 +1,8 @@ var=old f() { echo "var=$var"; } +# bash: POSIXLY_CORRECT behavior is to "leak" new variable values +# out of function invocations (similar to "special builtins" behavior); +# but in "bash mode", they don't leak. +# hush does not "leak" values. ash does. var=val f echo "var=$var" -- cgit v1.2.3-55-g6feb From 8d2191c6aedf60c29dc0a6ee8c452fee7e460ee7 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 24 Jul 2017 19:42:46 +0200 Subject: ash: copy three tests from hush_test/hush-signals/* Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-signals/catch.right | 5 +++++ shell/ash_test/ash-signals/catch.tests | 20 ++++++++++++++++++++ shell/ash_test/ash-signals/signal_read2.right | 2 ++ shell/ash_test/ash-signals/signal_read2.tests | 7 +++++++ shell/ash_test/ash-signals/subshell.right | 21 +++++++++++++++++++++ shell/ash_test/ash-signals/subshell.tests | 19 +++++++++++++++++++ 6 files changed, 74 insertions(+) create mode 100644 shell/ash_test/ash-signals/catch.right create mode 100755 shell/ash_test/ash-signals/catch.tests create mode 100644 shell/ash_test/ash-signals/signal_read2.right create mode 100755 shell/ash_test/ash-signals/signal_read2.tests create mode 100644 shell/ash_test/ash-signals/subshell.right create mode 100755 shell/ash_test/ash-signals/subshell.tests (limited to 'shell') diff --git a/shell/ash_test/ash-signals/catch.right b/shell/ash_test/ash-signals/catch.right new file mode 100644 index 000000000..68530c6e7 --- /dev/null +++ b/shell/ash_test/ash-signals/catch.right @@ -0,0 +1,5 @@ +sending USR2 +caught +sending USR2 +sending USR2 +User defined signal 2 diff --git a/shell/ash_test/ash-signals/catch.tests b/shell/ash_test/ash-signals/catch.tests new file mode 100755 index 000000000..d2a21d17e --- /dev/null +++ b/shell/ash_test/ash-signals/catch.tests @@ -0,0 +1,20 @@ +# avoid ugly warnings about signals not being caught +trap ":" USR1 USR2 + +"$THIS_SH" -c ' +trap "echo caught" USR2 +echo "sending USR2" +kill -USR2 $$ + +trap "" USR2 +echo "sending USR2" +kill -USR2 $$ + +trap "-" USR2 +echo "sending USR2" +kill -USR2 $$ + +echo "not reached" +' + +trap "-" USR1 USR2 diff --git a/shell/ash_test/ash-signals/signal_read2.right b/shell/ash_test/ash-signals/signal_read2.right new file mode 100644 index 000000000..87d8da304 --- /dev/null +++ b/shell/ash_test/ash-signals/signal_read2.right @@ -0,0 +1,2 @@ +Hangup +Done:129 diff --git a/shell/ash_test/ash-signals/signal_read2.tests b/shell/ash_test/ash-signals/signal_read2.tests new file mode 100755 index 000000000..eab5b9b5b --- /dev/null +++ b/shell/ash_test/ash-signals/signal_read2.tests @@ -0,0 +1,7 @@ +$THIS_SH -c ' +(sleep 1; kill -HUP $$) & +while true; do + read ignored +done +' +echo "Done:$?" diff --git a/shell/ash_test/ash-signals/subshell.right b/shell/ash_test/ash-signals/subshell.right new file mode 100644 index 000000000..248fcc41a --- /dev/null +++ b/shell/ash_test/ash-signals/subshell.right @@ -0,0 +1,21 @@ +trap -- '' HUP +trap -- '' QUIT +trap -- '' SYS +Ok +trap -- '' HUP +trap -- '' QUIT +trap -- '' SYS +Ok +trap -- '' HUP +trap -- '' QUIT +trap -- '' SYS +Ok +trap -- '' HUP +trap -- '' QUIT +trap -- '' SYS +Ok +trap -- '' HUP +trap -- '' QUIT +trap -- '' SYS +Terminated +Done diff --git a/shell/ash_test/ash-signals/subshell.tests b/shell/ash_test/ash-signals/subshell.tests new file mode 100755 index 000000000..d877f2b82 --- /dev/null +++ b/shell/ash_test/ash-signals/subshell.tests @@ -0,0 +1,19 @@ +# Non-empty traps should be reset in subshell + +# HUP is special in interactive shells +trap '' HUP +# QUIT is always special +trap '' QUIT +# SYS is not special +trap '' SYS +# WINCH is harmless +trap 'bad: caught WINCH' WINCH +# With TERM we'll check whether it is reset +trap 'bad: caught TERM' TERM + +(trap; "$THIS_SH" -c 'kill -HUP $PPID'; echo Ok) +(trap; "$THIS_SH" -c 'kill -QUIT $PPID'; echo Ok) +(trap; "$THIS_SH" -c 'kill -SYS $PPID'; echo Ok) +(trap; "$THIS_SH" -c 'kill -WINCH $PPID'; echo Ok) +(trap; "$THIS_SH" -c 'kill -TERM $PPID'; echo Bad: TERM is not reset) +echo Done -- cgit v1.2.3-55-g6feb From 5c123ac2082530ef4426737183ff76fe4595e1ff Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 24 Jul 2017 20:03:24 +0200 Subject: ash: fix comment, no code changes Signed-off-by: Denys Vlasenko --- shell/ash.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index c52637c92..0ae086e98 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -1788,9 +1788,6 @@ single_quote(const char *s) /* * Produce a possibly single quoted string suitable as input to the shell. - * If 'conditional' is nonzero, quoting is only done if the string contains - * non-shellsafe characters, or is identical to a shell keyword (reserved - * word); if it is zero, quoting is always done. * If quoting was done, the return string is allocated on the stack, * otherwise a pointer to the original string is returned. */ -- cgit v1.2.3-55-g6feb From 64925384c9cf5e0d986e183577da286bb3207ce7 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 25 Jul 2017 14:55:05 +0200 Subject: ash: add a few tests from hush-vars/* Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-vars/param_expand_alt.right | 9 +++ shell/ash_test/ash-vars/param_expand_alt.tests | 28 ++++++++ shell/ash_test/ash-vars/param_expand_assign.right | 27 +++++++ shell/ash_test/ash-vars/param_expand_assign.tests | 39 ++++++++++ .../ash-vars/param_expand_bash_substring.right | 64 +++++++++++++++++ .../ash-vars/param_expand_bash_substring.tests | 84 ++++++++++++++++++++++ shell/hush_test/hush-vars/param_expand_alt.right | 1 + shell/hush_test/hush-vars/param_expand_alt.tests | 14 ++-- .../hush_test/hush-vars/param_expand_assign.tests | 27 +++---- .../hush-vars/param_expand_bash_substring.tests | 13 ++-- 10 files changed, 283 insertions(+), 23 deletions(-) create mode 100644 shell/ash_test/ash-vars/param_expand_alt.right create mode 100755 shell/ash_test/ash-vars/param_expand_alt.tests create mode 100644 shell/ash_test/ash-vars/param_expand_assign.right create mode 100755 shell/ash_test/ash-vars/param_expand_assign.tests create mode 100644 shell/ash_test/ash-vars/param_expand_bash_substring.right create mode 100755 shell/ash_test/ash-vars/param_expand_bash_substring.tests (limited to 'shell') diff --git a/shell/ash_test/ash-vars/param_expand_alt.right b/shell/ash_test/ash-vars/param_expand_alt.right new file mode 100644 index 000000000..c733c147a --- /dev/null +++ b/shell/ash_test/ash-vars/param_expand_alt.right @@ -0,0 +1,9 @@ +SHELL: line 1: syntax error: bad substitution +SHELL: line 1: syntax error: bad substitution +_0_ __ +_z_ _z_ +_ _ _ _ _ +_aaaa _ _ _word _word +_ _ _ _ _ +_ _ _ _word _ +_fff _ _ _word _word diff --git a/shell/ash_test/ash-vars/param_expand_alt.tests b/shell/ash_test/ash-vars/param_expand_alt.tests new file mode 100755 index 000000000..c9c4249af --- /dev/null +++ b/shell/ash_test/ash-vars/param_expand_alt.tests @@ -0,0 +1,28 @@ +# First try some invalid patterns. Do in subshell due to parsing error. +# (set argv0 to "SHELL" to avoid "/path/to/shell: blah" in error messages) +"$THIS_SH" -c 'echo ${+} ; echo moo' SHELL +"$THIS_SH" -c 'echo ${:+} ; echo moo' SHELL + +# now some funky ones. +# ${V+word} "if V unset, then substitute nothing, else substitute word" +# ${V:+word} "if V unset or '', then substitute nothing, else substitute word" +# bash doesn't accept ${#+}. ash prints 0 (not $#). +echo _${#+}_ _${#:+}_ +# Forms with non-empty word work as expected in both ash and bash. +echo _${#+z}_ _${#:+z}_ + +# now some valid ones +set -- +echo _$1 _${1+} _${1:+} _${1+word} _${1:+word} + +set -- aaaa +echo _$1 _${1+} _${1:+} _${1+word} _${1:+word} + +unset f +echo _$f _${f+} _${f:+} _${f+word} _${f:+word} + +f= +echo _$f _${f+} _${f:+} _${f+word} _${f:+word} + +f=fff +echo _$f _${f+} _${f:+} _${f+word} _${f:+word} diff --git a/shell/ash_test/ash-vars/param_expand_assign.right b/shell/ash_test/ash-vars/param_expand_assign.right new file mode 100644 index 000000000..9b07d8cd4 --- /dev/null +++ b/shell/ash_test/ash-vars/param_expand_assign.right @@ -0,0 +1,27 @@ +SHELL: line 1: syntax error: bad substitution +SHELL: line 1: syntax error: bad substitution +0 +0 +SHELL: line 1: 1: bad variable name +SHELL: line 1: 1: bad variable name +SHELL: line 1: 1: bad variable name +SHELL: line 1: 1: bad variable name +_aa +_aa +_aa +_aa +_ +_ +_ +_word +_word +_ +_ +_ +_ +_word +_fff +_fff +_fff +_fff +_fff diff --git a/shell/ash_test/ash-vars/param_expand_assign.tests b/shell/ash_test/ash-vars/param_expand_assign.tests new file mode 100755 index 000000000..79de95613 --- /dev/null +++ b/shell/ash_test/ash-vars/param_expand_assign.tests @@ -0,0 +1,39 @@ +# First try some invalid patterns. Do in subshell due to parsing error. +# (set argv0 to "SHELL" to avoid "/path/to/shell: blah" in error messages) +"$THIS_SH" -c 'echo ${=}' SHELL +"$THIS_SH" -c 'echo ${:=}' SHELL + +# now some funky ones +"$THIS_SH" -c 'echo ${#=}' SHELL +"$THIS_SH" -c 'echo ${#:=}' SHELL + +# should error out +"$THIS_SH" -c 'set --; echo _${1=}' SHELL +"$THIS_SH" -c 'set --; echo _${1:=}' SHELL +"$THIS_SH" -c 'set --; echo _${1=word}' SHELL +"$THIS_SH" -c 'set --; echo _${1:=word}' SHELL + +# should not error +"$THIS_SH" -c 'set aa; echo _${1=}' SHELL +"$THIS_SH" -c 'set aa; echo _${1:=}' SHELL +"$THIS_SH" -c 'set aa; echo _${1=word}' SHELL +"$THIS_SH" -c 'set aa; echo _${1:=word}' SHELL + +# should work fine +unset f; echo _$f +unset f; echo _${f=} +unset f; echo _${f:=} +unset f; echo _${f=word} +unset f; echo _${f:=word} + +f=; echo _$f +f=; echo _${f=} +f=; echo _${f:=} +f=; echo _${f=word} +f=; echo _${f:=word} + +f=fff; echo _$f +f=fff; echo _${f=} +f=fff; echo _${f:=} +f=fff; echo _${f=word} +f=fff; echo _${f:=word} diff --git a/shell/ash_test/ash-vars/param_expand_bash_substring.right b/shell/ash_test/ash-vars/param_expand_bash_substring.right new file mode 100644 index 000000000..9ad6dbcad --- /dev/null +++ b/shell/ash_test/ash-vars/param_expand_bash_substring.right @@ -0,0 +1,64 @@ +SHELL: line 1: syntax error: bad substitution +SHELL: line 1: syntax error: bad substitution +SHELL: line 1: syntax error: bad substitution +SHELL: line 1: syntax error: bad substitution +SHELL: line 1: syntax error: missing '}' +1 =|| +1:1 =|| +1:1:2=|| +1::2 =|| +1:1: =|| +1:: =|| +1 =|0123| +1:1 =|123| +1:1:2=|12| +1::2 =|01| +1:1: =|| +1:: =|| +f =|| +f:1 =|| +f:1:2=|| +f::2 =|| +f:1: =|| +f:: =|| +f =|| +f:1 =|| +f:1:2=|| +f::2 =|| +f:1: =|| +f:: =|| +f =|a| +f:1 =|| +f:1:2=|| +f::2 =|a| +f:1: =|| +f:: =|| +f =|0123456789| +f:1 =|123456789| +f:1:2=|12| +f::2 =|01| +f:1: =|| +f:: =|| +Substrings from special vars +? =|0| +?:1 =|| +?:1:2=|| +?::2 =|0| +?:1: =|| +?:: =|| +# =|11| +#:1 =|1| +#:1:2=|1| +#::2 =|11| +#:1: =|| +#:: =|| +Substrings with expressions +f =|01234567| +f:1+1:2+2 =|2345| +f:-1:2+2 =|01234567| +f:1:f =|1234567| +f:1:$f =|1234567| +f:1:${f} =|1234567| +f:1:${f:3:1} =|123| +f:1:1`echo 1`=|1| +Done diff --git a/shell/ash_test/ash-vars/param_expand_bash_substring.tests b/shell/ash_test/ash-vars/param_expand_bash_substring.tests new file mode 100755 index 000000000..cce9f123e --- /dev/null +++ b/shell/ash_test/ash-vars/param_expand_bash_substring.tests @@ -0,0 +1,84 @@ +# first try some invalid patterns +# do all of these in subshells since it's supposed to error out +# (set argv0 to "SHELL" to avoid "/path/to/shell: blah" in error messages) +export var=0123456789 +"$THIS_SH" -c 'echo ${:}' SHELL +"$THIS_SH" -c 'echo ${::}' SHELL +"$THIS_SH" -c 'echo ${:1}' SHELL +"$THIS_SH" -c 'echo ${::1}' SHELL + +#this also is not valid in bash, hush accepts it: +"$THIS_SH" -c 'echo ${var:}' SHELL + +# then some funky ones +# UNFIXED BUG: this should work: "$THIS_SH" -c 'echo ${?:0}' + +# now some valid ones +set --; echo "1 =|${1}|" +set --; echo "1:1 =|${1:1}|" +set --; echo "1:1:2=|${1:1:2}|" +set --; echo "1::2 =|${1::2}|" +set --; echo "1:1: =|${1:1:}|" +set --; echo "1:: =|${1::}|" + +set -- 0123; echo "1 =|${1}|" +set -- 0123; echo "1:1 =|${1:1}|" +set -- 0123; echo "1:1:2=|${1:1:2}|" +set -- 0123; echo "1::2 =|${1::2}|" +set -- 0123; echo "1:1: =|${1:1:}|" +set -- 0123; echo "1:: =|${1::}|" + +unset f; echo "f =|$f|" +unset f; echo "f:1 =|${f:1}|" +unset f; echo "f:1:2=|${f:1:2}|" +unset f; echo "f::2 =|${f::2}|" +unset f; echo "f:1: =|${f:1:}|" +unset f; echo "f:: =|${f::}|" + +f=; echo "f =|$f|" +f=; echo "f:1 =|${f:1}|" +f=; echo "f:1:2=|${f:1:2}|" +f=; echo "f::2 =|${f::2}|" +f=; echo "f:1: =|${f:1:}|" +f=; echo "f:: =|${f::}|" + +f=a; echo "f =|$f|" +f=a; echo "f:1 =|${f:1}|" +f=a; echo "f:1:2=|${f:1:2}|" +f=a; echo "f::2 =|${f::2}|" +f=a; echo "f:1: =|${f:1:}|" +f=a; echo "f:: =|${f::}|" + +f=0123456789; echo "f =|$f|" +f=0123456789; echo "f:1 =|${f:1}|" +f=0123456789; echo "f:1:2=|${f:1:2}|" +f=0123456789; echo "f::2 =|${f::2}|" +f=0123456789; echo "f:1: =|${f:1:}|" +f=0123456789; echo "f:: =|${f::}|" + +echo "Substrings from special vars" +echo '? '"=|$?|" +echo '?:1 '"=|${?:1}|" +echo '?:1:2'"=|${?:1:2}|" +echo '?::2 '"=|${?::2}|" +echo '?:1: '"=|${?:1:}|" +echo '?:: '"=|${?::}|" +set -- 1 2 3 4 5 6 7 8 9 10 11 +echo '# '"=|$#|" +echo '#:1 '"=|${#:1}|" +echo '#:1:2'"=|${#:1:2}|" +echo '#::2 '"=|${#::2}|" +echo '#:1: '"=|${#:1:}|" +echo '#:: '"=|${#::}|" + +echo "Substrings with expressions" +f=01234567; echo 'f '"=|$f|" +f=01234567; echo 'f:1+1:2+2 '"=|${f:1+1:2+2}|" +f=01234567; echo 'f:-1:2+2 '"=|${f:-1:2+2}|" +f=01234567; echo 'f:1:f '"=|${f:1:f}|" +f=01234567; echo 'f:1:$f '"=|${f:1:$f}|" +f=01234567; echo 'f:1:${f} '"=|${f:1:${f}}|" +f=01234567; echo 'f:1:${f:3:1} '"=|${f:1:${f:3:1}}|" +f=01234567; echo 'f:1:1`echo 1`'"=|${f:1:`echo 1`}|" + +echo Done diff --git a/shell/hush_test/hush-vars/param_expand_alt.right b/shell/hush_test/hush-vars/param_expand_alt.right index 67f18d69c..4f9eb2907 100644 --- a/shell/hush_test/hush-vars/param_expand_alt.right +++ b/shell/hush_test/hush-vars/param_expand_alt.right @@ -1,6 +1,7 @@ hush: syntax error: unterminated ${name} hush: syntax error: unterminated ${name} __ __ +_z_ _z_ _ _ _ _ _ _aaaa _ _ _word _word _ _ _ _ _ diff --git a/shell/hush_test/hush-vars/param_expand_alt.tests b/shell/hush_test/hush-vars/param_expand_alt.tests index 3b646b142..c9c4249af 100755 --- a/shell/hush_test/hush-vars/param_expand_alt.tests +++ b/shell/hush_test/hush-vars/param_expand_alt.tests @@ -1,9 +1,15 @@ -# first try some invalid patterns (do in subshell due to parsing error) -"$THIS_SH" -c 'echo ${+} ; echo moo' -"$THIS_SH" -c 'echo ${:+} ; echo moo' +# First try some invalid patterns. Do in subshell due to parsing error. +# (set argv0 to "SHELL" to avoid "/path/to/shell: blah" in error messages) +"$THIS_SH" -c 'echo ${+} ; echo moo' SHELL +"$THIS_SH" -c 'echo ${:+} ; echo moo' SHELL -# now some funky ones. (bash doesn't accept ${#+}) +# now some funky ones. +# ${V+word} "if V unset, then substitute nothing, else substitute word" +# ${V:+word} "if V unset or '', then substitute nothing, else substitute word" +# bash doesn't accept ${#+}. ash prints 0 (not $#). echo _${#+}_ _${#:+}_ +# Forms with non-empty word work as expected in both ash and bash. +echo _${#+z}_ _${#:+z}_ # now some valid ones set -- diff --git a/shell/hush_test/hush-vars/param_expand_assign.tests b/shell/hush_test/hush-vars/param_expand_assign.tests index 149cb20df..79de95613 100755 --- a/shell/hush_test/hush-vars/param_expand_assign.tests +++ b/shell/hush_test/hush-vars/param_expand_assign.tests @@ -1,22 +1,23 @@ -# first try some invalid patterns (do in subshell due to parsing error) -"$THIS_SH" -c 'echo ${=}' -"$THIS_SH" -c 'echo ${:=}' +# First try some invalid patterns. Do in subshell due to parsing error. +# (set argv0 to "SHELL" to avoid "/path/to/shell: blah" in error messages) +"$THIS_SH" -c 'echo ${=}' SHELL +"$THIS_SH" -c 'echo ${:=}' SHELL # now some funky ones -"$THIS_SH" -c 'echo ${#=}' -"$THIS_SH" -c 'echo ${#:=}' +"$THIS_SH" -c 'echo ${#=}' SHELL +"$THIS_SH" -c 'echo ${#:=}' SHELL # should error out -"$THIS_SH" -c 'set --; echo _${1=}' -"$THIS_SH" -c 'set --; echo _${1:=}' -"$THIS_SH" -c 'set --; echo _${1=word}' -"$THIS_SH" -c 'set --; echo _${1:=word}' +"$THIS_SH" -c 'set --; echo _${1=}' SHELL +"$THIS_SH" -c 'set --; echo _${1:=}' SHELL +"$THIS_SH" -c 'set --; echo _${1=word}' SHELL +"$THIS_SH" -c 'set --; echo _${1:=word}' SHELL # should not error -"$THIS_SH" -c 'set aa; echo _${1=}' -"$THIS_SH" -c 'set aa; echo _${1:=}' -"$THIS_SH" -c 'set aa; echo _${1=word}' -"$THIS_SH" -c 'set aa; echo _${1:=word}' +"$THIS_SH" -c 'set aa; echo _${1=}' SHELL +"$THIS_SH" -c 'set aa; echo _${1:=}' SHELL +"$THIS_SH" -c 'set aa; echo _${1=word}' SHELL +"$THIS_SH" -c 'set aa; echo _${1:=word}' SHELL # should work fine unset f; echo _$f diff --git a/shell/hush_test/hush-vars/param_expand_bash_substring.tests b/shell/hush_test/hush-vars/param_expand_bash_substring.tests index 5c9552dba..cce9f123e 100755 --- a/shell/hush_test/hush-vars/param_expand_bash_substring.tests +++ b/shell/hush_test/hush-vars/param_expand_bash_substring.tests @@ -1,13 +1,14 @@ # first try some invalid patterns # do all of these in subshells since it's supposed to error out +# (set argv0 to "SHELL" to avoid "/path/to/shell: blah" in error messages) export var=0123456789 -"$THIS_SH" -c 'echo ${:}' -"$THIS_SH" -c 'echo ${::}' -"$THIS_SH" -c 'echo ${:1}' -"$THIS_SH" -c 'echo ${::1}' +"$THIS_SH" -c 'echo ${:}' SHELL +"$THIS_SH" -c 'echo ${::}' SHELL +"$THIS_SH" -c 'echo ${:1}' SHELL +"$THIS_SH" -c 'echo ${::1}' SHELL -#this also is not valid in bash, but we accept it: -"$THIS_SH" -c 'echo ${var:}' +#this also is not valid in bash, hush accepts it: +"$THIS_SH" -c 'echo ${var:}' SHELL # then some funky ones # UNFIXED BUG: this should work: "$THIS_SH" -c 'echo ${?:0}' -- cgit v1.2.3-55-g6feb From 645c697372b714f1293a37a185aa62965f600479 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 25 Jul 2017 15:18:57 +0200 Subject: hush: treat ${#?} as "length of $?" Signed-off-by: Denys Vlasenko --- .../ash-vars/param_expand_indicate_error.right | 43 +++++++++++++++ .../ash-vars/param_expand_indicate_error.tests | 61 ++++++++++++++++++++++ shell/hush.c | 12 +++-- .../hush-vars/param_expand_indicate_error.right | 2 +- .../hush-vars/param_expand_indicate_error.tests | 2 +- 5 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 shell/ash_test/ash-vars/param_expand_indicate_error.right create mode 100755 shell/ash_test/ash-vars/param_expand_indicate_error.tests (limited to 'shell') diff --git a/shell/ash_test/ash-vars/param_expand_indicate_error.right b/shell/ash_test/ash-vars/param_expand_indicate_error.right new file mode 100644 index 000000000..33afacee0 --- /dev/null +++ b/shell/ash_test/ash-vars/param_expand_indicate_error.right @@ -0,0 +1,43 @@ +SHELL: line 1: syntax error: bad substitution +1 +0 +==== +_ +SHELL: line 1: 1: parameter not set +SHELL: line 1: 1: parameter not set or null +SHELL: line 1: 1: message1 +SHELL: line 1: 1: message1 +SHELL: line 1: 1: unset! +SHELL: line 1: 1: null or unset! +==== +_aaaa +_aaaa +_aaaa +_aaaa +_aaaa +_aaaa +_aaaa +==== +_ +SHELL: line 1: f: parameter not set +SHELL: line 1: f: parameter not set or null +SHELL: line 1: f: message3 +SHELL: line 1: f: message3 +SHELL: line 1: f: unset! +SHELL: line 1: f: null or unset! +==== +_ +_ +SHELL: line 1: f: parameter not set or null +_ +SHELL: line 1: f: message4 +_ +SHELL: line 1: f: null or unset! +==== +_fff +_fff +_fff +_fff +_fff +_fff +_fff diff --git a/shell/ash_test/ash-vars/param_expand_indicate_error.tests b/shell/ash_test/ash-vars/param_expand_indicate_error.tests new file mode 100755 index 000000000..0f3061949 --- /dev/null +++ b/shell/ash_test/ash-vars/param_expand_indicate_error.tests @@ -0,0 +1,61 @@ +# do all of these in subshells since it's supposed to error out +# (set argv0 to "SHELL" to avoid "/path/to/shell: blah" in error messages) + +# first try some invalid patterns +#"$THIS_SH" -c 'echo ${?}' SHELL -- this is valid as it's the same as $? +"$THIS_SH" -c 'echo ${:?}' SHELL + +# then some funky ones +# note: bash prints 1 - treats it as "length of $#" +"$THIS_SH" -c 'echo ${#?}' SHELL +# bash prints 0 +"$THIS_SH" -c 'echo ${#:?}' SHELL + +# now some valid ones +export msg_unset="unset!" +export msg_null_or_unset="null or unset!" + +echo ==== +"$THIS_SH" -c 'set --; echo _$1' SHELL +"$THIS_SH" -c 'set --; echo _${1?}' SHELL +"$THIS_SH" -c 'set --; echo _${1:?}' SHELL +"$THIS_SH" -c 'set --; echo _${1?message1}' SHELL +"$THIS_SH" -c 'set --; echo _${1:?message1}' SHELL +"$THIS_SH" -c 'set --; echo _${1?$msg_unset}' SHELL +"$THIS_SH" -c 'set --; echo _${1:?$msg_null_or_unset}' SHELL + +echo ==== +"$THIS_SH" -c 'set -- aaaa; echo _$1' SHELL +"$THIS_SH" -c 'set -- aaaa; echo _${1?}' SHELL +"$THIS_SH" -c 'set -- aaaa; echo _${1:?}' SHELL +"$THIS_SH" -c 'set -- aaaa; echo _${1?word}' SHELL +"$THIS_SH" -c 'set -- aaaa; echo _${1:?word}' SHELL +"$THIS_SH" -c 'set -- aaaa; echo _${1?$msg_unset}' SHELL +"$THIS_SH" -c 'set -- aaaa; echo _${1:?$msg_null_or_unset}' SHELL + +echo ==== +"$THIS_SH" -c 'unset f; echo _$f' SHELL +"$THIS_SH" -c 'unset f; echo _${f?}' SHELL +"$THIS_SH" -c 'unset f; echo _${f:?}' SHELL +"$THIS_SH" -c 'unset f; echo _${f?message3}' SHELL +"$THIS_SH" -c 'unset f; echo _${f:?message3}' SHELL +"$THIS_SH" -c 'unset f; echo _${f?$msg_unset}' SHELL +"$THIS_SH" -c 'unset f; echo _${f:?$msg_null_or_unset}' SHELL + +echo ==== +"$THIS_SH" -c 'f=; echo _$f' SHELL +"$THIS_SH" -c 'f=; echo _${f?}' SHELL +"$THIS_SH" -c 'f=; echo _${f:?}' SHELL +"$THIS_SH" -c 'f=; echo _${f?word}' SHELL +"$THIS_SH" -c 'f=; echo _${f:?message4}' SHELL +"$THIS_SH" -c 'f=; echo _${f?$msg_unset}' SHELL +"$THIS_SH" -c 'f=; echo _${f:?$msg_null_or_unset}' SHELL + +echo ==== +"$THIS_SH" -c 'f=fff; echo _$f' SHELL +"$THIS_SH" -c 'f=fff; echo _${f?}' SHELL +"$THIS_SH" -c 'f=fff; echo _${f:?}' SHELL +"$THIS_SH" -c 'f=fff; echo _${f?word}' SHELL +"$THIS_SH" -c 'f=fff; echo _${f:?word}' SHELL +"$THIS_SH" -c 'f=fff; echo _${f?$msg_unset}' SHELL +"$THIS_SH" -c 'f=fff; echo _${f:?$msg_null_or_unset}' SHELL diff --git a/shell/hush.c b/shell/hush.c index 20b092398..f9dad074f 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -5559,8 +5559,10 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha first_char = arg[0] = arg0 & 0x7f; exp_op = 0; - if (first_char == '#' /* ${#... */ - && arg[1] && !exp_saveptr /* not ${#} and not ${#...} */ + if (first_char == '#' && arg[1] /* ${#... but not ${#} */ + && (!exp_saveptr /* and (not ${#...} */ + || (arg[1] == '?' && arg[2] == '\0') /* or ${#?} - "len of $?") */ + ) ) { /* It must be length operator: ${#var} */ var++; @@ -5797,7 +5799,11 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha /* mimic bash message */ die_if_script("%s: %s", var, - exp_word[0] ? exp_word : "parameter null or not set" + exp_word[0] + ? exp_word + : "parameter null or not set" + /* ash has more specific messages, a-la: */ + /*: (exp_save == ':' ? "parameter null or not set" : "parameter not set")*/ ); //TODO: how interactive bash aborts expansion mid-command? } else { diff --git a/shell/hush_test/hush-vars/param_expand_indicate_error.right b/shell/hush_test/hush-vars/param_expand_indicate_error.right index 06fcc5104..acf293893 100644 --- a/shell/hush_test/hush-vars/param_expand_indicate_error.right +++ b/shell/hush_test/hush-vars/param_expand_indicate_error.right @@ -1,5 +1,5 @@ hush: syntax error: unterminated ${name} -0 +1 0 ==== _ diff --git a/shell/hush_test/hush-vars/param_expand_indicate_error.tests b/shell/hush_test/hush-vars/param_expand_indicate_error.tests index be14b1e37..5f946e39a 100755 --- a/shell/hush_test/hush-vars/param_expand_indicate_error.tests +++ b/shell/hush_test/hush-vars/param_expand_indicate_error.tests @@ -5,7 +5,7 @@ "$THIS_SH" -c 'echo ${:?}' # then some funky ones -# note: bash prints 1 - treats it as "length of $#"? We print 0 +# note: bash prints 1 - treats it as "length of $#" "$THIS_SH" -c 'echo ${#?}' # bash prints 0 "$THIS_SH" -c 'echo ${#:?}' -- cgit v1.2.3-55-g6feb From be669fa1fdff6f751c8cdd3fc18a9fa7a7f46cd3 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 25 Jul 2017 15:25:07 +0200 Subject: ash: import param_expand_default.tests from hush Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-vars/param_expand_default.right | 7 +++++++ shell/ash_test/ash-vars/param_expand_default.tests | 23 ++++++++++++++++++++++ .../hush_test/hush-vars/param_expand_default.tests | 6 ++++-- 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 shell/ash_test/ash-vars/param_expand_default.right create mode 100755 shell/ash_test/ash-vars/param_expand_default.tests (limited to 'shell') diff --git a/shell/ash_test/ash-vars/param_expand_default.right b/shell/ash_test/ash-vars/param_expand_default.right new file mode 100644 index 000000000..3eecd1375 --- /dev/null +++ b/shell/ash_test/ash-vars/param_expand_default.right @@ -0,0 +1,7 @@ +SHELL: line 1: syntax error: bad substitution +_0 _0 +_ _ _ _word _word +_aaaa _aaaa _aaaa _aaaa _aaaa +_ _ _ _word _word +_ _ _ _ _word +_fff _fff _fff _fff _fff diff --git a/shell/ash_test/ash-vars/param_expand_default.tests b/shell/ash_test/ash-vars/param_expand_default.tests new file mode 100755 index 000000000..5e42d30e3 --- /dev/null +++ b/shell/ash_test/ash-vars/param_expand_default.tests @@ -0,0 +1,23 @@ +# first try some invalid patterns (do in subshell due to parsing error) +# (set argv0 to "SHELL" to avoid "/path/to/shell: blah" in error messages) +# valid in bash and ash (same as $-): "$THIS_SH" -c 'echo ${-}' SHELL +"$THIS_SH" -c 'echo ${:-}' SHELL + +# now some funky ones +echo _${#-} _${#:-} + +# now some valid ones +set -- +echo _$1 _${1-} _${1:-} _${1-word} _${1:-word} + +set -- aaaa +echo _$1 _${1-} _${1:-} _${1-word} _${1:-word} + +unset f +echo _$f _${f-} _${f:-} _${f-word} _${f:-word} + +f= +echo _$f _${f-} _${f:-} _${f-word} _${f:-word} + +f=fff +echo _$f _${f-} _${f:-} _${f-word} _${f:-word} diff --git a/shell/hush_test/hush-vars/param_expand_default.tests b/shell/hush_test/hush-vars/param_expand_default.tests index 1ea051748..16e5f8efe 100755 --- a/shell/hush_test/hush-vars/param_expand_default.tests +++ b/shell/hush_test/hush-vars/param_expand_default.tests @@ -1,6 +1,8 @@ # first try some invalid patterns (do in subshell due to parsing error) -"$THIS_SH" -c 'echo ${-}' -"$THIS_SH" -c 'echo ${:-}' +# (set argv0 to "SHELL" to avoid "/path/to/shell: blah" in error messages) +# valid in bash and ash (same as $-), not supported in hush (yet?): +"$THIS_SH" -c 'echo ${-}' SHELL +"$THIS_SH" -c 'echo ${:-}' SHELL # now some funky ones echo _${#-} _${#:-} -- cgit v1.2.3-55-g6feb From b28d4c3462e6b0e66322503f5ef0b941e0bb9cb8 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 25 Jul 2017 16:29:36 +0200 Subject: ash: [VAR] Move unsetvar functionality into setvareq Upstream commit: Date: Tue, 25 May 2010 20:55:05 +0800 [VAR] Move unsetvar functionality into setvareq This patch moves the unsetvar code into setvareq so that we can no have a pathological case of an unset variable hanging around unless it has a bit pinning it like VEXPORT. Signed-off-by: Herbert Xu function old new delta setvareq 227 303 +76 expmeta 517 521 +4 localcmd 364 366 +2 unsetcmd 96 76 -20 unsetvar 129 7 -122 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 3/2 up/down: 82/-142) Total: -60 bytes Signed-off-by: Denys Vlasenko --- shell/ash.c | 57 +++++++++------------------------ shell/ash_test/ash-vars/readonly0.right | 2 +- shell/ash_test/ash-vars/unset.right | 17 ++++++++++ shell/ash_test/ash-vars/unset.tests | 40 +++++++++++++++++++++++ shell/hush_test/hush-vars/unset.right | 4 +-- shell/hush_test/hush-vars/unset.tests | 7 ++-- 6 files changed, 81 insertions(+), 46 deletions(-) create mode 100644 shell/ash_test/ash-vars/unset.right create mode 100755 shell/ash_test/ash-vars/unset.tests (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 0ae086e98..72ceba782 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -2269,11 +2269,22 @@ setvareq(char *s, int flags) if (!(vp->flags & (VTEXTFIXED|VSTACK))) free((char*)vp->var_text); + if (((flags & (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) | (vp->flags & VSTRFIXED)) == VUNSET) { + *vpp = vp->next; + free(vp); + out_free: + if ((flags & (VTEXTFIXED|VSTACK|VNOSAVE)) == VNOSAVE) + free(s); + return; + } + flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET); } else { /* variable s is not found */ if (flags & VNOSET) return; + if ((flags & (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) == VUNSET) + goto out_free; vp = ckzalloc(sizeof(*vp)); vp->next = *vpp; /*vp->func = NULL; - ckzalloc did it */ @@ -2331,43 +2342,10 @@ setvar0(const char *name, const char *val) /* * Unset the specified variable. */ -static int +static void unsetvar(const char *s) { - struct var **vpp; - struct var *vp; - int retval; - - vpp = findvar(hashvar(s), s); - vp = *vpp; - retval = 2; - if (vp) { - int flags = vp->flags; - - retval = 1; - if (flags & VREADONLY) - goto out; -#if ENABLE_ASH_RANDOM_SUPPORT - vp->flags &= ~VDYNAMIC; -#endif - if (flags & VUNSET) - goto ok; - if ((flags & VSTRFIXED) == 0) { - INT_OFF; - if ((flags & (VTEXTFIXED|VSTACK)) == 0) - free((char*)vp->var_text); - *vpp = vp->next; - free(vp); - INT_ON; - } else { - setvar0(s, NULL); - vp->flags &= ~VEXPORT; - } - ok: - retval = 0; - } - out: - return retval; + setvar0(s, NULL); } /* @@ -13218,7 +13196,6 @@ unsetcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) char **ap; int i; int flag = 0; - int ret = 0; while ((i = nextopt("vf")) != 0) { flag = i; @@ -13226,15 +13203,13 @@ unsetcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) for (ap = argptr; *ap; ap++) { if (flag != 'f') { - i = unsetvar(*ap); - ret |= i; - if (!(i & 2)) - continue; + unsetvar(*ap); + continue; } if (flag != 'v') unsetfunc(*ap); } - return ret & 1; + return 0; } static const unsigned char timescmd_str[] ALIGN1 = { diff --git a/shell/ash_test/ash-vars/readonly0.right b/shell/ash_test/ash-vars/readonly0.right index f3a6bde9e..ecc4054f8 100644 --- a/shell/ash_test/ash-vars/readonly0.right +++ b/shell/ash_test/ash-vars/readonly0.right @@ -10,4 +10,4 @@ Fail:2 ./readonly0.tests: export: line 27: a: is read only Fail:2 -Fail:1 +./readonly0.tests: unset: line 44: a: is read only diff --git a/shell/ash_test/ash-vars/unset.right b/shell/ash_test/ash-vars/unset.right new file mode 100644 index 000000000..77d5abe9e --- /dev/null +++ b/shell/ash_test/ash-vars/unset.right @@ -0,0 +1,17 @@ +./unset.tests: unset: line 3: -: bad variable name +2 +./unset.tests: unset: line 5: illegal option -m +2 +0 +___ +0 f g +0 g +0 +___ +0 f g +0 +0 f g +0 +___ +./unset.tests: unset: line 36: VAR_RO: is read only +2 f g diff --git a/shell/ash_test/ash-vars/unset.tests b/shell/ash_test/ash-vars/unset.tests new file mode 100755 index 000000000..11b392744 --- /dev/null +++ b/shell/ash_test/ash-vars/unset.tests @@ -0,0 +1,40 @@ +# check invalid options are rejected +# bash: in posix mode, aborts if non-interactive; using subshell to avoid that +(unset -) +echo $? +(unset -m a b c) +echo $? + +# check funky usage +unset +echo $? + +# check normal usage +echo ___ +f=f g=g +echo $? $f $g +unset f +echo $? $f $g +unset g +echo $? $f $g + +echo ___ +f=f g=g +echo $? $f $g +unset f g +echo $? $f $g +f=f g=g +echo $? $f $g +unset -v f g +echo $? $f $g + +# check read only vars +echo ___ +f=f g=g +VAR_RO=1 +readonly VAR_RO +(unset VAR_RO) +echo $? $f $g +# not testing "do variables survive error halfway through unset" since unset aborts +# unset f VAR_RO g +#echo $? $f $g diff --git a/shell/hush_test/hush-vars/unset.right b/shell/hush_test/hush-vars/unset.right index 1fbe76a73..097274201 100644 --- a/shell/hush_test/hush-vars/unset.right +++ b/shell/hush_test/hush-vars/unset.right @@ -12,7 +12,7 @@ ___ 0 f g 0 ___ -hush: HUSH_VERSION: readonly variable +hush: VAR_RO: readonly variable 1 f g -hush: HUSH_VERSION: readonly variable +hush: VAR_RO: readonly variable 1 diff --git a/shell/hush_test/hush-vars/unset.tests b/shell/hush_test/hush-vars/unset.tests index f59ce5923..81243fbf9 100755 --- a/shell/hush_test/hush-vars/unset.tests +++ b/shell/hush_test/hush-vars/unset.tests @@ -1,4 +1,5 @@ # check invalid options are rejected +# bash: in posix mode, aborts if non-interactive unset - echo $? unset -m a b c @@ -30,7 +31,9 @@ echo $? $f $g # check read only vars echo ___ f=f g=g -unset HUSH_VERSION +VAR_RO=1 +readonly VAR_RO +unset VAR_RO echo $? $f $g -unset f HUSH_VERSION g +unset f VAR_RO g echo $? $f $g -- cgit v1.2.3-55-g6feb From 2990aa45d188b1d9814c89dd44658f068eb37e83 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 25 Jul 2017 17:37:57 +0200 Subject: ash: sync up with dash with respect to redirection escaping We fixed the problem differently than they. Let's not deviate. Upstream commit: Date: Thu, 27 May 2010 20:07:29 +1000 [EXPAND] Fix corruption of redirections with byte 0x81 In other ash variants, a partial implementation of ksh-like cmd >file* adds and removes CTLESC bytes ('\x81') in redirection filenames, preserving 8-bit transparency. Long ago, dash removed the code to add the CTLESC bytes, but not the code to remove them, causing corruption of filenames containing CTLESC. This commit removes the code to remove the CTLESC bytes. The CTLESC byte occurs frequently in UTF-8 encoded non-Latin text. This bug has been reported various times to Ubuntu and Debian (e.g. Launchpad Ubuntu #422298). This patch is the same as the one submitted by Alexander Korolkov in Ubuntu #422298. Signed-off-by: Jilles Tjoelker Signed-off-by: Herbert Xu function old new delta changepath 194 192 -2 expandarg 1000 984 -16 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 0/2 up/down: 0/-18) Total: -18 bytes Signed-off-by: Denys Vlasenko --- shell/ash.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 72ceba782..c353834a4 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -5711,7 +5711,7 @@ ash_arith(const char *s) #define RMESCAPE_SLASH 0x20 /* Stop globbing after slash */ /* Add CTLESC when necessary. */ -#define QUOTES_ESC (EXP_FULL | EXP_CASE | EXP_QPAT | EXP_REDIR) +#define QUOTES_ESC (EXP_FULL | EXP_CASE | EXP_QPAT) /* Do not skip NUL characters. */ #define QUOTES_KEEPNUL EXP_TILDE @@ -7608,10 +7608,6 @@ expandarg(union node *arg, struct arglist *arglist, int flag) exparg.lastp = &exparg.list; expandmeta(exparg.list /*, flag*/); } else { - if (flag & EXP_REDIR) { /*XXX - for now, just remove escapes */ - rmescapes(p, 0); - TRACE(("expandarg: rmescapes:'%s'\n", p)); - } sp = stzalloc(sizeof(*sp)); sp->text = p; *exparg.lastp = sp; -- cgit v1.2.3-55-g6feb From f1a5cb0548f647e628032ea8645c0d0d2d07b02f Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 25 Jul 2017 17:47:48 +0200 Subject: ash: [REDIR] Replace GPL noclobberopen code with the FreeBSD version Upstream commit: Date: Thu, 10 Mar 2011 16:52:13 +0800 [REDIR] Replace GPL noclobberopen code with the FreeBSD version Replace noclobberopen() from bash with the FreeBSD code for noclobber opens. This also reduces code size by eliminating an unnecessary check. Signed-off-by: Herbert Xu function old new delta changepath 192 194 +2 localcmd 366 364 -2 expmeta 521 517 -4 redirect 1210 1135 -75 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 1/3 up/down: 2/-81) Total: -79 bytes Signed-off-by: Denys Vlasenko --- shell/ash.c | 81 +++++++++++++------------------------------------------------ 1 file changed, 17 insertions(+), 64 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index c353834a4..b4b0d5253 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -5174,68 +5174,6 @@ stoppedjobs(void) #define EMPTY -2 /* marks an unused slot in redirtab */ #define CLOSED -3 /* marks a slot of previously-closed fd */ -/* - * Open a file in noclobber mode. - * The code was copied from bash. - */ -static int -noclobberopen(const char *fname) -{ - int r, fd; - struct stat finfo, finfo2; - - /* - * If the file exists and is a regular file, return an error - * immediately. - */ - r = stat(fname, &finfo); - if (r == 0 && S_ISREG(finfo.st_mode)) { - errno = EEXIST; - return -1; - } - - /* - * If the file was not present (r != 0), make sure we open it - * exclusively so that if it is created before we open it, our open - * will fail. Make sure that we do not truncate an existing file. - * Note that we don't turn on O_EXCL unless the stat failed -- if the - * file was not a regular file, we leave O_EXCL off. - */ - if (r != 0) - return open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666); - fd = open(fname, O_WRONLY|O_CREAT, 0666); - - /* If the open failed, return the file descriptor right away. */ - if (fd < 0) - return fd; - - /* - * OK, the open succeeded, but the file may have been changed from a - * non-regular file to a regular file between the stat and the open. - * We are assuming that the O_EXCL open handles the case where FILENAME - * did not exist and is symlinked to an existing file between the stat - * and open. - */ - - /* - * If we can open it and fstat the file descriptor, and neither check - * revealed that it was a regular file, and the file has not been - * replaced, return the file descriptor. - */ - if (fstat(fd, &finfo2) == 0 - && !S_ISREG(finfo2.st_mode) - && finfo.st_dev == finfo2.st_dev - && finfo.st_ino == finfo2.st_ino - ) { - return fd; - } - - /* The file has been replaced. badness. */ - close(fd); - errno = EEXIST; - return -1; -} - /* * Handle here documents. Normally we fork off a process to write the * data to a pipe. If the document is short, we can stuff the data in @@ -5280,6 +5218,7 @@ openhere(union node *redir) static int openredirect(union node *redir) { + struct stat sb; char *fname; int f; @@ -5319,9 +5258,23 @@ openredirect(union node *redir) #endif /* Take care of noclobber mode. */ if (Cflag) { - f = noclobberopen(fname); - if (f < 0) + if (stat(fname, &sb) < 0) { + f = open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666); + if (f < 0) + goto ecreate; + } else if (!S_ISREG(sb.st_mode)) { + f = open(fname, O_WRONLY, 0666); + if (f < 0) + goto ecreate; + if (fstat(f, &sb) < 0 && S_ISREG(sb.st_mode)) { + close(f); + errno = EEXIST; + goto ecreate; + } + } else { + errno = EEXIST; goto ecreate; + } break; } /* FALLTHROUGH */ -- cgit v1.2.3-55-g6feb From 86981e3ad2d03e77d1f668ac1603a041be448dae Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 25 Jul 2017 20:06:17 +0200 Subject: ash: allow "trap NUM [SIG]..." syntax While at it, make get_signum() return -1 for numeric strings >= NSIG. function old new delta trapcmd 292 306 +14 get_signum 295 300 +5 builtin_trap 413 412 -1 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 2/1 up/down: 19/-1) Total: 18 bytes Signed-off-by: Denys Vlasenko --- libbb/u_signal_names.c | 2 +- procps/kill.c | 2 +- shell/ash.c | 9 +++++++-- shell/hush.c | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) (limited to 'shell') diff --git a/libbb/u_signal_names.c b/libbb/u_signal_names.c index bf984a44e..b82a706d8 100644 --- a/libbb/u_signal_names.c +++ b/libbb/u_signal_names.c @@ -143,7 +143,7 @@ int FAST_FUNC get_signum(const char *name) unsigned i; i = bb_strtou(name, NULL, 10); - if (!errno) + if (!errno && i < NSIG) /* for shells, we allow 0 too */ return i; if (strncasecmp(name, "SIG", 3) == 0) name += 3; diff --git a/procps/kill.c b/procps/kill.c index 5cff24475..09beefb2d 100644 --- a/procps/kill.c +++ b/procps/kill.c @@ -188,7 +188,7 @@ int kill_main(int argc UNUSED_PARAM, char **argv) arg = *++argv; } /* else it must be -SIG */ signo = get_signum(arg); - if (signo < 0) { /* || signo > MAX_SIGNUM ? */ + if (signo < 0) { bb_error_msg("bad signal name '%s'", arg); return EXIT_FAILURE; } diff --git a/shell/ash.c b/shell/ash.c index b4b0d5253..42e14cbc8 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -12981,13 +12981,18 @@ trapcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) return 0; } + /* Why the second check? + * "trap NUM [sig2]..." is the same as "trap - NUM [sig2]..." + * In this case, NUM is signal no, not an action. + */ action = NULL; - if (ap[1]) + if (ap[1] && !is_number(ap[0])) action = *ap++; + exitcode = 0; while (*ap) { signo = get_signum(*ap); - if (signo < 0 || signo >= NSIG) { + if (signo < 0) { /* Mimic bash message exactly */ ash_msg("%s: invalid signal specification", *ap); exitcode = 1; diff --git a/shell/hush.c b/shell/hush.c index f9dad074f..11b33f40a 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -9745,7 +9745,7 @@ static int FAST_FUNC builtin_trap(char **argv) sighandler_t handler; sig = get_signum(*argv++); - if (sig < 0 || sig >= NSIG) { + if (sig < 0) { ret = EXIT_FAILURE; /* Mimic bash message exactly */ bb_error_msg("trap: %s: invalid signal specification", argv[-1]); -- cgit v1.2.3-55-g6feb From 1e3e2ccd5dd280371c9ca29c0e0304a0d40592af Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 25 Jul 2017 20:31:14 +0200 Subject: ash: [SHELL] Optimize dash -c "command" to avoid a fork Upstream commit: Date: Thu, 7 Jul 2011 13:58:48 +0800 [SHELL] Optimize dash -c "command" to avoid a fork On Sun, Apr 10, 2011 at 07:36:49AM +0000, Jonathan Nieder wrote: > From: Jilles Tjoelker > Date: Sat, 13 Jun 2009 16:17:45 -0500 > > This change only affects strings passed to -c, when the -s option is > not used. > > Use the EV_EXIT flag to inform the eval machinery that the string > being passed is the entirety of input. This way, a fork may be > omitted in many special cases. > > If there are empty lines after the last command, the evalcmd will not > see the end early enough and forks will not be omitted. The same thing > seems to happen in bash. > > Example: > sh -c 'ps lT' > No longer shows a shell process waiting for ps to finish. > > [jn: ported from FreeBSD SVN r194128. Bugs are mine.] > > Signed-off-by: Jonathan Nieder Instead of detecting EOF using the input layer, I'm going to use the parser instead. In either case, we always have to read ahead in order to complete the parsing of the previous node. Therefore we always know whether there is more to come, except in the case where we see a newline/semicolon or similar. For the purposes of sh -c, this should be sufficient. Signed-off-by: Herbert Xu function old new delta evalstring 190 224 +34 ash_main 1014 1022 +8 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 2/0 up/down: 42/0) Total: 42 bytes Signed-off-by: Denys Vlasenko --- shell/ash.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 42e14cbc8..c96ec939e 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -6128,7 +6128,9 @@ struct backcmd { /* result of evalbackcmd */ }; /* These forward decls are needed to use "eval" code for backticks handling: */ -#define EV_EXIT 01 /* exit after evaluating tree */ +/* flags in argument to evaltree */ +#define EV_EXIT 01 /* exit after evaluating tree */ +#define EV_TESTED 02 /* exit status is checked; ignore -e flag */ static int evaltree(union node *, int); static void FAST_FUNC @@ -8345,10 +8347,6 @@ commandcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) static void *funcblock; /* block to allocate function from */ static char *funcstring_end; /* end of block to allocate strings from */ -/* flags in argument to evaltree */ -#define EV_EXIT 01 /* exit after evaluating tree */ -#define EV_TESTED 02 /* exit status is checked; ignore -e flag */ - static const uint8_t nodesize[N_NUMBER] ALIGN1 = { [NCMD ] = SHELL_ALIGN(sizeof(struct ncmd)), [NPIPE ] = SHELL_ALIGN(sizeof(struct npipe)), @@ -12491,6 +12489,12 @@ expandstr(const char *ps) return stackblock(); } +static inline int +parser_eof(void) +{ + return tokpushback && lasttoken == TEOF; +} + /* * Execute a command or commands contained in a string. */ @@ -12526,7 +12530,7 @@ evalstring(char *s, int flags) while ((n = parsecmd(0)) != NODE_EOF) { int i; - i = evaltree(n, flags); + i = evaltree(n, flags & ~(parser_eof() ? 0 : EV_EXIT)); if (n) status = i; popstackmark(&smark); @@ -13671,7 +13675,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv) // if (!sflag) g_parsefile->pf_fd = -1; // ^^ not necessary since now we special-case fd 0 // in is_hidden_fd() to not be considered "hidden fd" - evalstring(minusc, 0); + evalstring(minusc, sflag ? 0 : EV_EXIT); } if (sflag || minusc == NULL) { -- cgit v1.2.3-55-g6feb From 2093ad296f8a4528ad0e106b52074871a2bf070e Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 26 Jul 2017 00:07:27 +0200 Subject: hush: fix ${##}, ${#?}, ${#!} handling function old new delta parse_dollar 786 820 +34 expand_one_var 1579 1592 +13 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 2/0 up/down: 47/0) Total: 47 bytes Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-vars/param_expand_alt.tests | 2 +- shell/ash_test/ash-vars/param_expand_len1.right | 11 ++++++++ shell/ash_test/ash-vars/param_expand_len1.tests | 31 +++++++++++++++++++++++ shell/hush.c | 27 +++++++++++++++----- shell/hush_test/hush-vars/param_expand_alt.tests | 2 +- shell/hush_test/hush-vars/param_expand_len1.right | 11 ++++++++ shell/hush_test/hush-vars/param_expand_len1.tests | 31 +++++++++++++++++++++++ 7 files changed, 106 insertions(+), 9 deletions(-) create mode 100644 shell/ash_test/ash-vars/param_expand_len1.right create mode 100755 shell/ash_test/ash-vars/param_expand_len1.tests create mode 100644 shell/hush_test/hush-vars/param_expand_len1.right create mode 100755 shell/hush_test/hush-vars/param_expand_len1.tests (limited to 'shell') diff --git a/shell/ash_test/ash-vars/param_expand_alt.tests b/shell/ash_test/ash-vars/param_expand_alt.tests index c9c4249af..d80452434 100755 --- a/shell/ash_test/ash-vars/param_expand_alt.tests +++ b/shell/ash_test/ash-vars/param_expand_alt.tests @@ -6,7 +6,7 @@ # now some funky ones. # ${V+word} "if V unset, then substitute nothing, else substitute word" # ${V:+word} "if V unset or '', then substitute nothing, else substitute word" -# bash doesn't accept ${#+}. ash prints 0 (not $#). +# bash doesn't accept ${#+}. ash prints 0 (not $#): "len of $+" echo _${#+}_ _${#:+}_ # Forms with non-empty word work as expected in both ash and bash. echo _${#+z}_ _${#:+z}_ diff --git a/shell/ash_test/ash-vars/param_expand_len1.right b/shell/ash_test/ash-vars/param_expand_len1.right new file mode 100644 index 000000000..dff3c7bb1 --- /dev/null +++ b/shell/ash_test/ash-vars/param_expand_len1.right @@ -0,0 +1,11 @@ +One:1 +Two:2 +Three:3 + +One:1 +Two:2 +Three:3 + +Ok ${#$}: 0 + +Ok ${#!}: 0 diff --git a/shell/ash_test/ash-vars/param_expand_len1.tests b/shell/ash_test/ash-vars/param_expand_len1.tests new file mode 100755 index 000000000..e1beab320 --- /dev/null +++ b/shell/ash_test/ash-vars/param_expand_len1.tests @@ -0,0 +1,31 @@ +# ${#c} for any single char c means "length of $c", including all special vars + +false +echo One:${#?} +(exit 10) +echo Two:${#?} +(exit 100) +echo Three:${#?} + +echo +echo One:${##} +set -- 1 2 3 4 5 6 7 8 9 0 +echo Two:${##} +set -- 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 \ + 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 \ + 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 \ + 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 +echo Three:${##} + +echo +v=$$ +test "${#v}" = "${#$}" +echo 'Ok ${#$}:' $? + +echo +sleep 0 & +v=$! +test "${#v}" = "${#!}" +echo 'Ok ${#!}:' $? + +# TODO: ${#-} ${#_} diff --git a/shell/hush.c b/shell/hush.c index 11b33f40a..d0225edb9 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -4466,6 +4466,8 @@ static int parse_dollar(o_string *as_string, case '@': /* args */ goto make_one_char_var; case '{': { + char len_single_ch; + o_addchr(dest, SPECIAL_VAR_SYMBOL); ch = i_getch(input); /* eat '{' */ @@ -4485,6 +4487,7 @@ static int parse_dollar(o_string *as_string, return 0; } nommu_addchr(as_string, ch); + len_single_ch = ch; ch |= quote_mask; /* It's possible to just call add_till_closing_bracket() at this point. @@ -4509,9 +4512,18 @@ static int parse_dollar(o_string *as_string, /* handle parameter expansions * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02 */ - if (!strchr(VAR_SUBST_OPS, ch)) /* ${var... */ - goto bad_dollar_syntax; - + if (!strchr(VAR_SUBST_OPS, ch)) { /* ${var... */ + if (len_single_ch != '#' + /*|| !strchr(SPECIAL_VARS_STR, ch) - disallow errors like ${#+} ? */ + || i_peek(input) != '}' + ) { + goto bad_dollar_syntax; + } + /* else: it's "length of C" ${#C} op, + * where C is a single char + * special var name, e.g. ${#!}. + */ + } /* Eat everything until closing '}' (or ':') */ end_ch = '}'; if (BASH_SUBSTR @@ -4568,6 +4580,7 @@ static int parse_dollar(o_string *as_string, } break; } + len_single_ch = 0; /* it can't be ${#C} op */ } o_addchr(dest, SPECIAL_VAR_SYMBOL); break; @@ -5559,10 +5572,10 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha first_char = arg[0] = arg0 & 0x7f; exp_op = 0; - if (first_char == '#' && arg[1] /* ${#... but not ${#} */ - && (!exp_saveptr /* and (not ${#...} */ - || (arg[1] == '?' && arg[2] == '\0') /* or ${#?} - "len of $?") */ - ) + if (first_char == '#' && arg[1] /* ${#...} but not ${#} */ + && (!exp_saveptr /* and ( not(${#...}) */ + || (arg[2] == '\0' && strchr(SPECIAL_VARS_STR, arg[1])) /* or ${#C} "len of $C" ) */ + ) /* NB: skipping ^^^specvar check mishandles ${#::2} */ ) { /* It must be length operator: ${#var} */ var++; diff --git a/shell/hush_test/hush-vars/param_expand_alt.tests b/shell/hush_test/hush-vars/param_expand_alt.tests index c9c4249af..d80452434 100755 --- a/shell/hush_test/hush-vars/param_expand_alt.tests +++ b/shell/hush_test/hush-vars/param_expand_alt.tests @@ -6,7 +6,7 @@ # now some funky ones. # ${V+word} "if V unset, then substitute nothing, else substitute word" # ${V:+word} "if V unset or '', then substitute nothing, else substitute word" -# bash doesn't accept ${#+}. ash prints 0 (not $#). +# bash doesn't accept ${#+}. ash prints 0 (not $#): "len of $+" echo _${#+}_ _${#:+}_ # Forms with non-empty word work as expected in both ash and bash. echo _${#+z}_ _${#:+z}_ diff --git a/shell/hush_test/hush-vars/param_expand_len1.right b/shell/hush_test/hush-vars/param_expand_len1.right new file mode 100644 index 000000000..dff3c7bb1 --- /dev/null +++ b/shell/hush_test/hush-vars/param_expand_len1.right @@ -0,0 +1,11 @@ +One:1 +Two:2 +Three:3 + +One:1 +Two:2 +Three:3 + +Ok ${#$}: 0 + +Ok ${#!}: 0 diff --git a/shell/hush_test/hush-vars/param_expand_len1.tests b/shell/hush_test/hush-vars/param_expand_len1.tests new file mode 100755 index 000000000..e1beab320 --- /dev/null +++ b/shell/hush_test/hush-vars/param_expand_len1.tests @@ -0,0 +1,31 @@ +# ${#c} for any single char c means "length of $c", including all special vars + +false +echo One:${#?} +(exit 10) +echo Two:${#?} +(exit 100) +echo Three:${#?} + +echo +echo One:${##} +set -- 1 2 3 4 5 6 7 8 9 0 +echo Two:${##} +set -- 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 \ + 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 \ + 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 \ + 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 +echo Three:${##} + +echo +v=$$ +test "${#v}" = "${#$}" +echo 'Ok ${#$}:' $? + +echo +sleep 0 & +v=$! +test "${#v}" = "${#!}" +echo 'Ok ${#!}:' $? + +# TODO: ${#-} ${#_} -- cgit v1.2.3-55-g6feb From b0648b0e7874e8551df64708532346a049ab7f2c Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 26 Jul 2017 00:30:02 +0200 Subject: shell: remove ${#+} tests, it is not a valid construct Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-vars/param_expand_alt.right | 2 +- shell/ash_test/ash-vars/param_expand_alt.tests | 9 +++++++-- shell/hush_test/hush-vars/param_expand_alt.right | 2 +- shell/hush_test/hush-vars/param_expand_alt.tests | 9 +++++++-- 4 files changed, 16 insertions(+), 6 deletions(-) (limited to 'shell') diff --git a/shell/ash_test/ash-vars/param_expand_alt.right b/shell/ash_test/ash-vars/param_expand_alt.right index c733c147a..1303f8064 100644 --- a/shell/ash_test/ash-vars/param_expand_alt.right +++ b/shell/ash_test/ash-vars/param_expand_alt.right @@ -1,6 +1,6 @@ SHELL: line 1: syntax error: bad substitution SHELL: line 1: syntax error: bad substitution -_0_ __ +__ _z_ _z_ _ _ _ _ _ _aaaa _ _ _word _word diff --git a/shell/ash_test/ash-vars/param_expand_alt.tests b/shell/ash_test/ash-vars/param_expand_alt.tests index d80452434..23e9a26be 100755 --- a/shell/ash_test/ash-vars/param_expand_alt.tests +++ b/shell/ash_test/ash-vars/param_expand_alt.tests @@ -6,8 +6,13 @@ # now some funky ones. # ${V+word} "if V unset, then substitute nothing, else substitute word" # ${V:+word} "if V unset or '', then substitute nothing, else substitute word" -# bash doesn't accept ${#+}. ash prints 0 (not $#): "len of $+" -echo _${#+}_ _${#:+}_ +# +# ${#:+} is a :+ op on $#, but ${#+} (and any other ${#c}) is "length of $c", +# not + op on $#. +# bash and dash do not accept ${#+}. it's possible for some shell to skip +# the check that c is valid and interpret ${#+} as "len of $+". Not testing it. +# echo _${#+}_ +echo _${#:+}_ # Forms with non-empty word work as expected in both ash and bash. echo _${#+z}_ _${#:+z}_ diff --git a/shell/hush_test/hush-vars/param_expand_alt.right b/shell/hush_test/hush-vars/param_expand_alt.right index 4f9eb2907..c46786e1f 100644 --- a/shell/hush_test/hush-vars/param_expand_alt.right +++ b/shell/hush_test/hush-vars/param_expand_alt.right @@ -1,6 +1,6 @@ hush: syntax error: unterminated ${name} hush: syntax error: unterminated ${name} -__ __ +__ _z_ _z_ _ _ _ _ _ _aaaa _ _ _word _word diff --git a/shell/hush_test/hush-vars/param_expand_alt.tests b/shell/hush_test/hush-vars/param_expand_alt.tests index d80452434..23e9a26be 100755 --- a/shell/hush_test/hush-vars/param_expand_alt.tests +++ b/shell/hush_test/hush-vars/param_expand_alt.tests @@ -6,8 +6,13 @@ # now some funky ones. # ${V+word} "if V unset, then substitute nothing, else substitute word" # ${V:+word} "if V unset or '', then substitute nothing, else substitute word" -# bash doesn't accept ${#+}. ash prints 0 (not $#): "len of $+" -echo _${#+}_ _${#:+}_ +# +# ${#:+} is a :+ op on $#, but ${#+} (and any other ${#c}) is "length of $c", +# not + op on $#. +# bash and dash do not accept ${#+}. it's possible for some shell to skip +# the check that c is valid and interpret ${#+} as "len of $+". Not testing it. +# echo _${#+}_ +echo _${#:+}_ # Forms with non-empty word work as expected in both ash and bash. echo _${#+z}_ _${#:+z}_ -- cgit v1.2.3-55-g6feb From b31b61bb9bff8a920b7e25b83e3aa08f7c907331 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 26 Jul 2017 13:42:53 +0200 Subject: ash: fix redir_leak.tests if STANDALONE=y If STANDALONE and we run a NOEXEC applet, saved copies of redirected fds were visible for the child. They have CLOEXEC bit, yes, but we do not exec in this case. Signed-off-by: Denys Vlasenko --- shell/ash.c | 1 + 1 file changed, 1 insertion(+) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index c96ec939e..524580e8a 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -7698,6 +7698,7 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char ** clearenv(); while (*envp) putenv(*envp++); + popredir(/*drop:*/ 1, /*restore:*/ 0); run_applet_no_and_exit(applet_no, cmd, argv); } /* re-exec ourselves with the new arguments */ -- cgit v1.2.3-55-g6feb From cf3a796dd1dfe53f7d67507f7f4d5c05cc7ebc8d Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 26 Jul 2017 14:38:19 +0200 Subject: ash: alloc slightly smaller buffer in cvtnum(); faster unsetvar() Signed-off-by: Denys Vlasenko --- shell/ash.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 524580e8a..915e86167 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -214,6 +214,9 @@ #include "shell_common.h" #if ENABLE_FEATURE_SH_MATH # include "math.h" +#else +typedef long arith_t; +# define ARITH_FMT "%ld" #endif #if ENABLE_ASH_RANDOM_SUPPORT # include "random.h" @@ -621,8 +624,8 @@ fmtstr(char *outbuf, size_t length, const char *fmt, ...) va_list ap; int ret; - va_start(ap, fmt); INT_OFF; + va_start(ap, fmt); ret = vsnprintf(outbuf, length, fmt, ap); va_end(ap); INT_ON; @@ -2345,7 +2348,7 @@ setvar0(const char *name, const char *val) static void unsetvar(const char *s) { - setvar0(s, NULL); + setvar(s, NULL, 0); } /* @@ -5697,19 +5700,20 @@ static struct arglist exparg; /* * Our own itoa(). + * cvtnum() is used even if math support is off (to prepare $? values and such). */ -#if !ENABLE_FEATURE_SH_MATH -/* cvtnum() is used even if math support is off (to prepare $? values and such) */ -typedef long arith_t; -# define ARITH_FMT "%ld" -#endif static int cvtnum(arith_t num) { int len; - expdest = makestrspace(sizeof(arith_t)*3 + 2, expdest); - len = fmtstr(expdest, sizeof(arith_t)*3 + 2, ARITH_FMT, num); + /* 32-bit and wider ints require buffer size of bytes*3 (or less) */ + len = sizeof(arith_t) * 3; + /* If narrower: worst case, 1-byte ints: need 5 bytes: "-127" */ + if (sizeof(arith_t) < 4) len += 2; + + expdest = makestrspace(len, expdest); + len = fmtstr(expdest, len, ARITH_FMT, num); STADJUST(len, expdest); return len; } -- cgit v1.2.3-55-g6feb From b8ab27bf53797ec98c7c7d1c80a5a0a062f273a5 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 26 Jul 2017 19:22:34 +0200 Subject: ash: [VAR] Add localvars nesting Upstream commit: Date: Mon, 24 May 2010 15:31:27 +0800 [VAR] Add localvars nesting This patch adds localvars nesting infrastructure so we can reuse the localvars mechanism for command evaluation. Signed-off-by: Herbert Xu Signed-off-by: Denys Vlasenko --- shell/ash.c | 57 ++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 13 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 915e86167..8bef78546 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -9167,7 +9167,12 @@ optschanged(void) #endif } -static struct localvar *localvars; +struct localvar_list { + struct localvar_list *next; + struct localvar *lv; +}; + +static struct localvar_list *localvar_stack; /* * Called after a function returns. @@ -9176,11 +9181,19 @@ static struct localvar *localvars; static void poplocalvars(void) { - struct localvar *lvp; + struct localvar_list *ll; + struct localvar *lvp, *next; struct var *vp; - while ((lvp = localvars) != NULL) { - localvars = lvp->next; + INT_OFF; + ll = localvar_stack; + localvar_stack = ll->next; + + next = ll->lv; + free(ll); + + while ((lvp = next) != NULL) { + next = lvp->next; vp = lvp->vp; TRACE(("poplocalvar %s\n", vp ? vp->var_text : "-")); if (vp == NULL) { /* $- saved */ @@ -9199,19 +9212,34 @@ poplocalvars(void) } free(lvp); } + INT_ON; +} + +/* + * Create a new localvar environment. + */ +static void +pushlocalvars(void) +{ + struct localvar_list *ll; + + INT_OFF; + ll = ckzalloc(sizeof(*ll)); + /*ll->lv = NULL; - zalloc did it */ + ll->next = localvar_stack; + localvar_stack = ll; + INT_ON; } static int evalfun(struct funcnode *func, int argc, char **argv, int flags) { volatile struct shparam saveparam; - struct localvar *volatile savelocalvars; struct jmploc *volatile savehandler; struct jmploc jmploc; int e; saveparam = shellparam; - savelocalvars = localvars; savehandler = exception_handler; e = setjmp(jmploc.loc); if (e) { @@ -9219,7 +9247,6 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) } INT_OFF; exception_handler = &jmploc; - localvars = NULL; shellparam.malloced = 0; func->count++; funcnest++; @@ -9230,13 +9257,13 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) shellparam.optind = 1; shellparam.optoff = -1; #endif + pushlocalvars(); evaltree(func->n.narg.next, flags & EV_TESTED); + poplocalvars(); funcdone: INT_OFF; funcnest--; freefunc(func); - poplocalvars(); - localvars = savelocalvars; freeparam(&shellparam); shellparam = saveparam; exception_handler = savehandler; @@ -9265,7 +9292,7 @@ mklocal(char *name) * x=0; f() { local x=1; echo $x; local x; echo $x; }; f; echo $x * x=0; f() { local x=1; echo $x; local x=2; echo $x; }; f; echo $x */ - lvp = localvars; + lvp = localvar_stack->lv; while (lvp) { if (lvp->vp && varcmp(lvp->vp->var_text, name) == 0) { if (eq) @@ -9310,8 +9337,8 @@ mklocal(char *name) } } lvp->vp = vp; - lvp->next = localvars; - localvars = lvp; + lvp->next = localvar_stack->lv; + localvar_stack->lv = lvp; ret: INT_ON; } @@ -9324,7 +9351,7 @@ localcmd(int argc UNUSED_PARAM, char **argv) { char *name; - if (!funcnest) + if (!localvar_stack) ash_msg_and_raise_error("not in a function"); argv = argptr; @@ -13570,6 +13597,10 @@ reset(void) /* from redir.c: */ while (redirlist) popredir(/*drop:*/ 0, /*restore:*/ 0); + + /* from var.c: */ + while (localvar_stack) + poplocalvars(); } #if PROFILE -- cgit v1.2.3-55-g6feb From d5b500c81c1ec73d2feeea14c2e872726044d9e8 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 26 Jul 2017 19:25:40 +0200 Subject: ash: [VAR] Fix poplocalvar leak Upstream commit: Date: Tue, 25 May 2010 18:14:32 +0800 [VAR] Fix poplocalvar leak When a variable is marked as local, we set VSTRFIXED on its vp recored. However, poplocalvar never clears this flag for variables that were unset to begin with. Thus if you ever made an unset variable local, it would get the VSTRFIXED bit and stick around forever. Signed-off-by: Herbert Xu Signed-off-by: Denys Vlasenko --- shell/ash.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 8bef78546..75a72ea0c 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -9200,7 +9200,8 @@ poplocalvars(void) memcpy(optlist, lvp->text, sizeof(optlist)); free((char*)lvp->text); optschanged(); - } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { + } else if (lvp->flags == VUNSET) { + vp->flags &= ~(VSTRFIXED|VREADONLY); unsetvar(vp->var_text); } else { if (vp->var_func) -- cgit v1.2.3-55-g6feb From 981a0568b3f3003cd1a2640ade61d8f5ebdfb865 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 26 Jul 2017 19:53:11 +0200 Subject: ash: [VAR] Replace cmdenviron with localvars Upstream commit: Date: Wed, 26 May 2010 18:54:19 +0800 [VAR] Replace cmdenviron with localvars This patch replaces the cmdenviron mechanism for temporary command variables with the localvars mechanism used by functions. This reduces code size, and more importantly, makes the variable assignment take effect immediately as required by POSIX. Signed-off-by: Herbert Xu Signed-off-by: Denys Vlasenko --- shell/ash.c | 61 ++++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 34 insertions(+), 27 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 75a72ea0c..e900a425f 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -1250,7 +1250,6 @@ static struct parsefile basepf; /* top level input file */ static struct parsefile *g_parsefile = &basepf; /* current input file */ static int startlinno; /* line # where last token started */ static char *commandname; /* currently executing command */ -static struct strlist *cmdenviron; /* environment for builtin command */ /* ============ Message printing */ @@ -2225,15 +2224,9 @@ reinit_unicode_for_ash(void) /* * Search the environment of a builtin command. */ -static const char * +static inline const char * bltinlookup(const char *name) { - struct strlist *sp; - - for (sp = cmdenviron; sp; sp = sp->next) { - if (varcmp(sp->text, name) == 0) - return var_end(sp->text); - } return lookupvar(name); } @@ -9179,7 +9172,7 @@ static struct localvar_list *localvar_stack; * Interrupts must be off. */ static void -poplocalvars(void) +poplocalvars(int keep) { struct localvar_list *ll; struct localvar *lvp, *next; @@ -9196,7 +9189,23 @@ poplocalvars(void) next = lvp->next; vp = lvp->vp; TRACE(("poplocalvar %s\n", vp ? vp->var_text : "-")); - if (vp == NULL) { /* $- saved */ + if (keep) { + int bits = VSTRFIXED; + + if (lvp->flags != VUNSET) { + if (vp->var_text == lvp->text) + bits |= VTEXTFIXED; + else if (!(lvp->flags & (VTEXTFIXED|VSTACK))) + free((char*)lvp->text); + } + + vp->flags &= ~bits; + vp->flags |= (lvp->flags & bits); + + if ((vp->flags & + (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) == VUNSET) + unsetvar(vp->var_text); + } else if (vp == NULL) { /* $- saved */ memcpy(optlist, lvp->text, sizeof(optlist)); free((char*)lvp->text); optschanged(); @@ -9260,7 +9269,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) #endif pushlocalvars(); evaltree(func->n.narg.next, flags & EV_TESTED); - poplocalvars(); + poplocalvars(0); funcdone: INT_OFF; funcnest--; @@ -9631,6 +9640,7 @@ evalcommand(union node *cmd, int flags) /* First expand the arguments. */ TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); setstackmark(&smark); + pushlocalvars(); back_exitstatus = 0; cmdentry.cmdtype = CMDBUILTIN; @@ -9684,6 +9694,8 @@ evalcommand(union node *cmd, int flags) spp = varlist.lastp; expandarg(argp, &varlist, EXP_VARTILDE); + mklocal((*spp)->text); + /* * Modify the command lookup path, if a PATH= assignment * is present @@ -9815,6 +9827,7 @@ evalcommand(union node *cmd, int flags) /* parent */ status = waitforjob(jp); INT_ON; + poplocalvars(0); TRACE(("forked child exited with %d\n", status)); break; } @@ -9827,17 +9840,10 @@ evalcommand(union node *cmd, int flags) /* NOTREACHED */ } /* default */ case CMDBUILTIN: - cmdenviron = varlist.list; - if (cmdenviron) { - struct strlist *list = cmdenviron; - int i = VNOSET; - if (spclbltin > 0 || argc == 0) { - i = 0; - if (cmd_is_exec && argc > 1) - i = VEXPORT; - } - listsetvar(list, i); - } + poplocalvars(spclbltin > 0 || argc == 0); + if (cmd_is_exec && argc > 1) + listsetvar(varlist.list, VEXPORT); + /* Tight loop with builtins only: * "while kill -0 $child; do true; done" * will never exit even if $child died, unless we do this @@ -9855,7 +9861,7 @@ evalcommand(union node *cmd, int flags) goto readstatus; case CMDFUNCTION: - listsetvar(varlist.list, 0); + poplocalvars(1); /* See above for the rationale */ dowait(DOWAIT_NONBLOCK, NULL); if (evalfun(cmdentry.u.func, argc, argv, flags)) @@ -12714,11 +12720,12 @@ dotcmd(int argc_ UNUSED_PARAM, char **argv_ UNUSED_PARAM) char *fullname; char **argv; char *args_need_save; - struct strlist *sp; volatile struct shparam saveparam; - for (sp = cmdenviron; sp; sp = sp->next) - setvareq(ckstrdup(sp->text), VSTRFIXED | VTEXTFIXED); +//??? +// struct strlist *sp; +// for (sp = cmdenviron; sp; sp = sp->next) +// setvareq(ckstrdup(sp->text), VSTRFIXED | VTEXTFIXED); nextopt(nullstr); /* handle possible "--" */ argv = argptr; @@ -13601,7 +13608,7 @@ reset(void) /* from var.c: */ while (localvar_stack) - poplocalvars(); + poplocalvars(0); } #if PROFILE -- cgit v1.2.3-55-g6feb From 484fc2056df8a82acabc70386eeb6d0da4982fec Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 26 Jul 2017 19:55:31 +0200 Subject: ash: [VAR] Fix poplocalvar on abnormal exit from function Upstream commit: Date: Thu, 27 May 2010 11:32:55 +0800 [VAR] Fix poplocalvar on abnormal exit from function The new localvar code broke the abnormal exit from functions and built-ins by not restoring the original localvar state. This patch fixes this by storing the previous localvar state so that we always unwind correctly in case of an abnormal exit. Signed-off-by: Herbert Xu Signed-off-by: Denys Vlasenko --- shell/ash.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index e900a425f..f7fc18f0d 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -9228,7 +9228,7 @@ poplocalvars(int keep) /* * Create a new localvar environment. */ -static void +static struct localvar_list * pushlocalvars(void) { struct localvar_list *ll; @@ -9239,6 +9239,15 @@ pushlocalvars(void) ll->next = localvar_stack; localvar_stack = ll; INT_ON; + + return ll->next; +} + +static void +unwindlocalvars(struct localvar_list *stop) +{ + while (localvar_stack != stop) + poplocalvars(0); } static int @@ -9619,6 +9628,7 @@ evalcommand(union node *cmd, int flags) static const struct builtincmd null_bltin = { "\0\0", bltincmd /* why three NULs? */ }; + struct localvar_list *localvar_stop; struct stackmark smark; union node *argp; struct arglist arglist; @@ -9640,7 +9650,7 @@ evalcommand(union node *cmd, int flags) /* First expand the arguments. */ TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); setstackmark(&smark); - pushlocalvars(); + localvar_stop = pushlocalvars(); back_exitstatus = 0; cmdentry.cmdtype = CMDBUILTIN; @@ -9827,7 +9837,6 @@ evalcommand(union node *cmd, int flags) /* parent */ status = waitforjob(jp); INT_ON; - poplocalvars(0); TRACE(("forked child exited with %d\n", status)); break; } @@ -9874,6 +9883,7 @@ evalcommand(union node *cmd, int flags) out: if (cmd->ncmd.redirect) popredir(/*drop:*/ cmd_is_exec, /*restore:*/ cmd_is_exec); + unwindlocalvars(localvar_stop); if (lastarg) { /* dsl: I think this is intended to be used to support * '_' in 'vi' command mode during line editing... @@ -13607,8 +13617,7 @@ reset(void) popredir(/*drop:*/ 0, /*restore:*/ 0); /* from var.c: */ - while (localvar_stack) - poplocalvars(0); + unwindlocalvars(NULL); } #if PROFILE -- cgit v1.2.3-55-g6feb From 85241c7b0b4558f405f7e633ad62520299b8396d Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 26 Jul 2017 20:00:08 +0200 Subject: ash: [VAR] Do not poplocalvars prematurely on regular utilities Upstream commit: Date: Thu, 27 May 2010 11:50:19 +0800 [VAR] Do not poplocalvars prematurely on regular utilities The recent cmdenviron removal broke regular utilities by calling poplocalvars too early. This patch fixes that by postponing the poplocalvars for regular utilities until they have completed. In order to ensure that local still works, it is now a special built-in. Signed-off-by: Herbert Xu Signed-off-by: Denys Vlasenko --- shell/ash.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index f7fc18f0d..3604af4fc 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -9539,7 +9539,7 @@ static const struct builtincmd builtintab[] = { #if ENABLE_FEATURE_SH_MATH { BUILTIN_NOSPEC "let" , letcmd }, #endif - { BUILTIN_ASSIGN "local" , localcmd }, + { BUILTIN_SPEC_REG_ASSG "local" , localcmd }, #if ENABLE_ASH_PRINTF { BUILTIN_REGULAR "printf" , printfcmd }, #endif @@ -9849,9 +9849,11 @@ evalcommand(union node *cmd, int flags) /* NOTREACHED */ } /* default */ case CMDBUILTIN: - poplocalvars(spclbltin > 0 || argc == 0); - if (cmd_is_exec && argc > 1) - listsetvar(varlist.list, VEXPORT); + if (spclbltin > 0 || argc == 0) { + poplocalvars(1); + if (cmd_is_exec && argc > 1) + listsetvar(varlist.list, VEXPORT); + } /* Tight loop with builtins only: * "while kill -0 $child; do true; done" -- cgit v1.2.3-55-g6feb From d04fc712e30b681117afaad490fec2a747b390c6 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 26 Jul 2017 20:06:48 +0200 Subject: ash: [VAR] Fix loss of variables when hash collides Upstream commit: Date: Tue, 6 Jul 2010 17:40:53 +0800 [VAR] Fix loss of variables when hash collides Brian Koropoff reported that the new var patches broke the following script: #!/bin/dash GDM_LANG="bar" OPTION="foo" unset GDM_LANG # OPTION has mysteriously become unset echo "$OPTION" He correctly diagnosed this as a result of removing all variables in the hash chain preceding the one that should be removed in setvareq. He also provided a patch to fix this. This patch is based on his but without keeping the original vpp. As a result, we now store new variables at the end of the hash chain instead of the beginning. To make this work, setvareq/setvar now returns the vp pointer modified. In case they're used to unset a variable the pointer returned is undefined. This is because mklocal needs it and used to get it by assuming that the new variable always appear at the beginning of the chain. Signed-off-by: Herbert Xu Signed-off-by: Denys Vlasenko --- shell/ash.c | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 3604af4fc..5bb59355c 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -2237,14 +2237,15 @@ bltinlookup(const char *name) * will go away. * Called with interrupts off. */ -static void +static struct var * setvareq(char *s, int flags) { struct var *vp, **vpp; vpp = hashvar(s); flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1)); - vp = *findvar(vpp, s); + vpp = findvar(vpp, s); + vp = *vpp; if (vp) { if ((vp->flags & (VREADONLY|VDYNAMIC)) == VREADONLY) { const char *n; @@ -2257,7 +2258,7 @@ setvareq(char *s, int flags) } if (flags & VNOSET) - return; + goto out; if (vp->var_func && !(flags & VNOFUNC)) vp->var_func(var_end(s)); @@ -2271,14 +2272,14 @@ setvareq(char *s, int flags) out_free: if ((flags & (VTEXTFIXED|VSTACK|VNOSAVE)) == VNOSAVE) free(s); - return; + goto out; } flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET); } else { /* variable s is not found */ if (flags & VNOSET) - return; + goto out; if ((flags & (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) == VUNSET) goto out_free; vp = ckzalloc(sizeof(*vp)); @@ -2290,13 +2291,16 @@ setvareq(char *s, int flags) s = ckstrdup(s); vp->var_text = s; vp->flags = flags; + + out: + return vp; } /* * Set the value of a variable. The flags argument is ored with the * flags of the variable. If val is NULL, the variable is unset. */ -static void +static struct var * setvar(const char *name, const char *val, int flags) { const char *q; @@ -2304,6 +2308,7 @@ setvar(const char *name, const char *val, int flags) char *nameeq; size_t namelen; size_t vallen; + struct var *vp; q = endofname(name); p = strchrnul(q, '='); @@ -2325,8 +2330,10 @@ setvar(const char *name, const char *val, int flags) p = mempcpy(p, val, vallen); } *p = '\0'; - setvareq(nameeq, flags | VNOSAVE); + vp = setvareq(nameeq, flags | VNOSAVE); INT_ON; + + return vp; } static void FAST_FUNC @@ -9336,10 +9343,9 @@ mklocal(char *name) if (vp == NULL) { /* variable did not exist yet */ if (eq) - setvareq(name, VSTRFIXED); + vp = setvareq(name, VSTRFIXED); else - setvar(name, NULL, VSTRFIXED); - vp = *vpp; /* the new variable */ + vp = setvar(name, NULL, VSTRFIXED); lvp->flags = VUNSET; } else { lvp->text = vp->var_text; -- cgit v1.2.3-55-g6feb From b8c0bc18f0cc31e3a2f41dd2c0b30426e4a77fc5 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 26 Jul 2017 23:03:21 +0200 Subject: ash: revert previous implementation of "A=1 A=2 B=$A cmd" code Reverts this: commit 0e6f661e23d358cca104c24f8438d0ec64df32f1 Date: Fri Feb 15 15:02:15 2008 +0000 ash: handle "A=1 A=2 B=$A; echo $B". closes bug 947. A different fix from upstream has been imported by previous six commits. Last seven commits, cumulative: function old new delta poplocalvars - 314 +314 mklocal - 288 +288 pushlocalvars - 48 +48 evalcommand 1372 1408 +36 unwindlocalvars - 22 +22 ash_main 1022 1029 +7 setvar 167 172 +5 localvar_stack - 4 +4 setvareq 303 302 -1 evalcase 271 269 -2 subevalvar 1202 1198 -4 localvars 4 - -4 cmdenviron 4 - -4 expandarg 984 973 -11 evalvar 589 574 -15 argstr 1164 1141 -23 dotcmd 335 303 -32 bltinlookup 51 5 -46 varvalue 709 596 -113 evalfun 456 270 -186 localcmd 364 44 -320 ------------------------------------------------------------------------------ (add/remove: 5/2 grow/shrink: 3/11 up/down: 724/-761) Total: -37 bytes text data bss dec hex filename 915353 485 6888 922726 e1466 busybox_old 915320 485 6880 922685 e143d busybox_unstripped Signed-off-by: Denys Vlasenko --- shell/ash.c | 62 +++++++++++++++---------------------------------------------- 1 file changed, 15 insertions(+), 47 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 5bb59355c..faa42e28c 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -6304,19 +6304,15 @@ expari(int flag) #endif /* argstr needs it */ -static char *evalvar(char *p, int flags, struct strlist *var_str_list); +static char *evalvar(char *p, int flags); /* * Perform variable and command substitution. If EXP_FULL is set, output CTLESC * characters to allow for further processing. Otherwise treat * $@ like $* since no splitting will be performed. - * - * var_str_list (can be NULL) is a list of "VAR=val" strings which take precedence - * over shell variables. Needed for "A=a B=$A; echo $B" case - we use it - * for correct expansion of "B=$A" word. */ static void -argstr(char *p, int flags, struct strlist *var_str_list) +argstr(char *p, int flags) { static const char spclchars[] ALIGN1 = { '=', @@ -6409,7 +6405,7 @@ argstr(char *p, int flags, struct strlist *var_str_list) inquotes ^= EXP_QUOTED; /* "$@" syntax adherence hack */ if (inquotes && !memcmp(p, dolatstr + 1, DOLATSTRLEN - 1)) { - p = evalvar(p + 1, flags | inquotes, /* var_str_list: */ NULL) + 1; + p = evalvar(p + 1, flags | inquotes) + 1; goto start; } addquote: @@ -6435,7 +6431,7 @@ argstr(char *p, int flags, struct strlist *var_str_list) goto addquote; case CTLVAR: TRACE(("argstr: evalvar('%s')\n", p)); - p = evalvar(p, flags | inquotes, var_str_list); + p = evalvar(p, flags | inquotes); TRACE(("argstr: evalvar:'%s'\n", (char *)stackblock())); goto start; case CTLBACKQ: @@ -6577,7 +6573,7 @@ varunset(const char *end, const char *var, const char *umsg, int varflags) static const char * subevalvar(char *p, char *varname, int strloc, int subtype, - int startloc, int varflags, int flag, struct strlist *var_str_list) + int startloc, int varflags, int flag) { struct nodelist *saveargbackq = argbackq; int quotes = flag & QUOTES_ESC; @@ -6595,8 +6591,8 @@ subevalvar(char *p, char *varname, int strloc, int subtype, // p, varname, strloc, subtype, startloc, varflags, quotes); argstr(p, EXP_TILDE | (subtype != VSASSIGN && subtype != VSQUESTION ? - (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE) : 0), - var_str_list); + (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE) : 0) + ); STPUTC('\0', expdest); argbackq = saveargbackq; startp = (char *)stackblock() + startloc; @@ -6873,7 +6869,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype, * ash -c 'echo ${#1#}' name:'1=#' */ static NOINLINE ssize_t -varvalue(char *name, int varflags, int flags, struct strlist *var_str_list, int *quotedp) +varvalue(char *name, int varflags, int flags, int *quotedp) { const char *p; int num; @@ -6965,31 +6961,6 @@ varvalue(char *name, int varflags, int flags, struct strlist *var_str_list, int goto value; default: /* NB: name has form "VAR=..." */ - - /* "A=a B=$A" case: var_str_list is a list of "A=a" strings - * which should be considered before we check variables. */ - if (var_str_list) { - unsigned name_len = (strchrnul(name, '=') - name) + 1; - p = NULL; - do { - char *str, *eq; - str = var_str_list->text; - eq = strchr(str, '='); - if (!eq) /* stop at first non-assignment */ - break; - eq++; - if (name_len == (unsigned)(eq - str) - && strncmp(str, name, name_len) == 0 - ) { - p = eq; - /* goto value; - WRONG! */ - /* think "A=1 A=2 B=$A" */ - } - var_str_list = var_str_list->next; - } while (var_str_list); - if (p) - goto value; - } p = lookupvar(name); value: if (!p) @@ -7019,7 +6990,7 @@ varvalue(char *name, int varflags, int flags, struct strlist *var_str_list, int * input string. */ static char * -evalvar(char *p, int flag, struct strlist *var_str_list) +evalvar(char *p, int flag) { char varflags; char subtype; @@ -7043,7 +7014,7 @@ evalvar(char *p, int flag, struct strlist *var_str_list) p = strchr(p, '=') + 1; //TODO: use var_end(p)? again: - varlen = varvalue(var, varflags, flag, var_str_list, "ed); + varlen = varvalue(var, varflags, flag, "ed); if (varflags & VSNUL) varlen--; @@ -7057,8 +7028,7 @@ evalvar(char *p, int flag, struct strlist *var_str_list) if (varlen < 0) { argstr( p, - flag | EXP_TILDE | EXP_WORD, - var_str_list + flag | EXP_TILDE | EXP_WORD ); goto end; } @@ -7070,7 +7040,7 @@ evalvar(char *p, int flag, struct strlist *var_str_list) goto record; subevalvar(p, var, 0, subtype, startloc, varflags, - flag & ~QUOTES_ESC, var_str_list); + flag & ~QUOTES_ESC); varflags &= ~VSNUL; /* * Remove any recorded regions beyond @@ -7123,7 +7093,7 @@ evalvar(char *p, int flag, struct strlist *var_str_list) STPUTC('\0', expdest); patloc = expdest - (char *)stackblock(); if (NULL == subevalvar(p, /* varname: */ NULL, patloc, subtype, - startloc, varflags, flag, var_str_list)) { + startloc, varflags, flag)) { int amount = expdest - ( (char *)stackblock() + patloc - 1 ); @@ -7547,8 +7517,7 @@ expandarg(union node *arg, struct arglist *arglist, int flag) argbackq = arg->narg.backquote; STARTSTACKSTR(expdest); TRACE(("expandarg: argstr('%s',flags:%x)\n", arg->narg.text, flag)); - argstr(arg->narg.text, flag, - /* var_str_list: */ arglist ? arglist->list : NULL); + argstr(arg->narg.text, flag); p = _STPUTC('\0', expdest); expdest = p - 1; if (arglist == NULL) { @@ -7615,8 +7584,7 @@ casematch(union node *pattern, char *val) setstackmark(&smark); argbackq = pattern->narg.backquote; STARTSTACKSTR(expdest); - argstr(pattern->narg.text, EXP_TILDE | EXP_CASE, - /* var_str_list: */ NULL); + argstr(pattern->narg.text, EXP_TILDE | EXP_CASE); STACKSTRNUL(expdest); ifsfree(); result = patmatch(stackblock(), val); -- cgit v1.2.3-55-g6feb From 488e609203c23b9826f75179f1b8e567617138ae Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 26 Jul 2017 23:08:36 +0200 Subject: ash: force inlining of a trivial function function old new delta bltinlookup 5 - -5 Signed-off-by: Denys Vlasenko --- shell/ash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index faa42e28c..f74fbd72f 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -2224,7 +2224,7 @@ reinit_unicode_for_ash(void) /* * Search the environment of a builtin command. */ -static inline const char * +static ALWAYS_INLINE const char * bltinlookup(const char *name) { return lookupvar(name); -- cgit v1.2.3-55-g6feb From 619d9b5e6848a72350126ea9c1e413fd133181e3 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 28 Jul 2017 15:28:33 +0200 Subject: ash: less hackish implementation of evaltreenr() Defining a function alias with __attribute__ ((alias("evaltree"),__noreturn__)) is not that usual, and clang had a bug which made it misunderstand this construct. Switch to: ALWAYS_INLINE NORETURN evaltreenr() { evaltree(); unreachable(); } Older gcc's do not know unreachable(), on them we pay the price of having a few extra calls to abort(): function old new delta evalsubshell 151 156 +5 evalpipe 357 362 +5 argstr 1141 1144 +3 On newer gcc, code size does not change. Signed-off-by: Denys Vlasenko --- include/platform.h | 7 +++++++ shell/ash.c | 20 ++++++++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) (limited to 'shell') diff --git a/include/platform.h b/include/platform.h index 8210e5c49..ea49c7e92 100644 --- a/include/platform.h +++ b/include/platform.h @@ -45,6 +45,13 @@ #define UNUSED_PARAM __attribute__ ((__unused__)) #define NORETURN __attribute__ ((__noreturn__)) + +#if __GNUC_PREREQ(4,5) +# define bb_unreachable(altcode) __builtin_unreachable() +#else +# define bb_unreachable(altcode) altcode +#endif + /* "The malloc attribute is used to tell the compiler that a function * may be treated as if any non-NULL pointer it returns cannot alias * any other pointer valid when the function returns. This will often diff --git a/shell/ash.c b/shell/ash.c index f74fbd72f..1f5a8dae0 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -6137,6 +6137,19 @@ struct backcmd { /* result of evalbackcmd */ #define EV_TESTED 02 /* exit status is checked; ignore -e flag */ static int evaltree(union node *, int); +/* An evaltree() which is known to never return. + * Used to use an alias: + * static int evaltreenr(union node *, int) __attribute__((alias("evaltree"),__noreturn__)); + * but clang was reported to "transfer" noreturn-ness to evaltree() as well. + */ +static ALWAYS_INLINE NORETURN void +evaltreenr(union node *n, int flags) +{ + evaltree(n, flags); + bb_unreachable(abort()); + /* NOTREACHED */ +} + static void FAST_FUNC evalbackcmd(union node *n, struct backcmd *result) { @@ -6173,7 +6186,7 @@ evalbackcmd(union node *n, struct backcmd *result) */ eflag = 0; ifsfree(); - evaltree(n, EV_EXIT); /* actually evaltreenr... */ + evaltreenr(n, EV_EXIT); /* NOTREACHED */ } /* parent */ @@ -8796,11 +8809,6 @@ evaltree(union node *n, int flags) return exitstatus; } -#if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3) -static -#endif -int evaltreenr(union node *, int) __attribute__ ((alias("evaltree"),__noreturn__)); - static int skiploop(void) { -- cgit v1.2.3-55-g6feb From be366e5afac1d9f5b3958bd3899a389308d5d9d3 Mon Sep 17 00:00:00 2001 From: Ron Yorston Date: Thu, 27 Jul 2017 13:53:39 +0100 Subject: ash: support platforms that don't have '%m' printf specifier The '%m' conversion specifier prints an error message based on the current value of 'errno'. It is available in the GNU C library, Cygwin (since 2012), uClibc and musl. It is not available in various BSDs, BSD-derived systems (MacOS, Android) or Microsoft Windows. Use a symbol defined in platform.h to control how error messages can be formatted to display the 'errno' message. On platforms that support it use '%m'; on other platforms use '%s' and strerror(). On platforms that have '%m' there is essentially no change in the size of the binary. Otherwise: function old new delta redirect 1287 1310 +23 xtcsetpgrp 27 44 +17 dup2_or_raise 34 51 +17 setinputfile 267 275 +8 .rodata 163379 163371 -8 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 4/1 up/down: 65/-8) Total: 57 bytes Signed-off-by: Ron Yorston Signed-off-by: Denys Vlasenko --- include/platform.h | 4 ++++ shell/ash.c | 22 +++++++++++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) (limited to 'shell') diff --git a/include/platform.h b/include/platform.h index ea49c7e92..b81c59d4e 100644 --- a/include/platform.h +++ b/include/platform.h @@ -406,6 +406,7 @@ typedef unsigned smalluint; #define HAVE_MNTENT_H 1 #define HAVE_NET_ETHERNET_H 1 #define HAVE_SYS_STATFS_H 1 +#define HAVE_PRINTF_PERCENTM 1 #if defined(__UCLIBC__) # if UCLIBC_VERSION < KERNEL_VERSION(0, 9, 32) @@ -461,6 +462,7 @@ typedef unsigned smalluint; # undef HAVE_DPRINTF # undef HAVE_UNLOCKED_STDIO # undef HAVE_UNLOCKED_LINE_OPS +# undef HAVE_PRINTF_PERCENTM #endif #if defined(__dietlibc__) @@ -483,6 +485,7 @@ typedef unsigned smalluint; # undef HAVE_STRVERSCMP # undef HAVE_XTABS # undef HAVE_UNLOCKED_LINE_OPS +# undef HAVE_PRINTF_PERCENTM # include # if __FreeBSD_version < 1000029 # undef HAVE_STRCHRNUL /* FreeBSD added strchrnul() between 1000028 and 1000029 */ @@ -517,6 +520,7 @@ typedef unsigned smalluint; # undef HAVE_STRVERSCMP # undef HAVE_UNLOCKED_LINE_OPS # undef HAVE_NET_ETHERNET_H +# undef HAVE_PRINTF_PERCENTM #endif /* diff --git a/shell/ash.c b/shell/ash.c index 1f5a8dae0..f9c78ee78 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -1306,6 +1306,18 @@ ash_msg_and_raise_error(const char *msg, ...) va_end(ap); } +/* + * Use '%m' to append error string on platforms that support it, '%s' and + * strerror() on those that don't. + * + * 'fmt' must be a string literal. + */ +#ifdef HAVE_PRINTF_PERCENTM +#define ash_msg_and_raise_perror(fmt, ...) ash_msg_and_raise_error(fmt ": %m", ##__VA_ARGS__) +#else +#define ash_msg_and_raise_perror(fmt, ...) ash_msg_and_raise_error(fmt ": %s", ##__VA_ARGS__, strerror(errno)) +#endif + static void raise_error_syntax(const char *) NORETURN; static void raise_error_syntax(const char *msg) @@ -3827,7 +3839,7 @@ static void xtcsetpgrp(int fd, pid_t pgrp) { if (tcsetpgrp(fd, pgrp)) - ash_msg_and_raise_error("can't set tty process group (%m)"); + ash_msg_and_raise_perror("can't set tty process group"); } /* @@ -5313,7 +5325,7 @@ savefd(int from) err = newfd < 0 ? errno : 0; if (err != EBADF) { if (err) - ash_msg_and_raise_error("%d: %m", from); + ash_msg_and_raise_perror("%d", from); close(from); fcntl(newfd, F_SETFD, FD_CLOEXEC); } @@ -5328,7 +5340,7 @@ dup2_or_raise(int from, int to) newfd = (from != to) ? dup2(from, to) : to; if (newfd < 0) { /* Happens when source fd is not open: try "echo >&99" */ - ash_msg_and_raise_error("%d: %m", from); + ash_msg_and_raise_perror("%d", from); } return newfd; } @@ -5459,7 +5471,7 @@ redirect(union node *redir, int flags) /* "echo >&10" and 10 is a fd opened to a sh script? */ if (is_hidden_fd(sv, right_fd)) { errno = EBADF; /* as if it is closed */ - ash_msg_and_raise_error("%d: %m", right_fd); + ash_msg_and_raise_perror("%d", right_fd); } newfd = -1; } else { @@ -5493,7 +5505,7 @@ redirect(union node *redir, int flags) if (newfd >= 0) close(newfd); errno = i; - ash_msg_and_raise_error("%d: %m", fd); + ash_msg_and_raise_perror("%d", fd); /* NOTREACHED */ } /* EBADF: it is not open - good, remember to close it */ -- cgit v1.2.3-55-g6feb From 00a1dbd230a3e0ee2cea84130b7f20d9c9a9cf4e Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 29 Jul 2017 01:20:53 +0200 Subject: ash: make tryexec(cmd) parameter const char Fewer casts this way. Signed-off-by: Denys Vlasenko --- shell/ash.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index f9c78ee78..0de81b325 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -7687,7 +7687,7 @@ static int builtinloc = -1; /* index in path of %builtin, or -1 */ static void -tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char **envp) +tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) const char *cmd, char **argv, char **envp) { #if ENABLE_FEATURE_SH_STANDALONE if (applet_no >= 0) { @@ -7713,7 +7713,7 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char ** #else execve(cmd, argv, envp); #endif - if (cmd != (char*) bb_busybox_exec_path && errno == ENOEXEC) { + if (cmd != bb_busybox_exec_path && errno == ENOEXEC) { /* Run "cmd" as a shell script: * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html * "If the execve() function fails with ENOEXEC, the shell @@ -7730,8 +7730,8 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char ** * message and exit code 126. For one, this prevents attempts * to interpret foreign ELF binaries as shell scripts. */ - argv[0] = cmd; - cmd = (char*) bb_busybox_exec_path; + argv[0] = (char*) cmd; + cmd = bb_busybox_exec_path; /* NB: this is only possible because all callers of shellexec() * ensure that the argv[-1] slot exists! */ -- cgit v1.2.3-55-g6feb