From 1ab7c2fc6daf252f20d17949e70829905a7fd72a Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 3 Nov 2016 20:17:23 +0100 Subject: ash: while (!got_sig) pause() is not reliable, use sigsuspend() dash was doing it for a reason. Unfortunately, it had no comment why... now I know. Signed-off-by: Denys Vlasenko --- shell/ash.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/shell/ash.c b/shell/ash.c index ecd2146e4..f75642868 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -3933,6 +3933,8 @@ wait_block_or_sig(int *status) int pid; do { + sigset_t mask; + /* Poll all children for changes in their state */ got_sigchld = 0; /* if job control is active, accept stopped processes too */ @@ -3941,14 +3943,13 @@ wait_block_or_sig(int *status) break; /* Error (e.g. EINTR, ECHILD) or pid */ /* Children exist, but none are ready. Sleep until interesting signal */ -#if 0 /* dash does this */ - sigset_t mask; +#if 1 sigfillset(&mask); sigprocmask(SIG_SETMASK, &mask, &mask); while (!got_sigchld && !pending_sig) sigsuspend(&mask); sigprocmask(SIG_SETMASK, &mask, NULL); -#else +#else /* unsafe: a signal can set pending_sig after check, but before pause() */ while (!got_sigchld && !pending_sig) pause(); #endif @@ -3987,9 +3988,9 @@ dowait(int block, struct job *job) * either enter a sleeping waitpid() (BUG), or need to busy-loop. * * Because of this, we run inside INT_OFF, but use a special routine - * which combines waitpid() and pause(). + * which combines waitpid() and sigsuspend(). * This is the reason why we need to have a handler for SIGCHLD: - * SIG_DFL handler does not wake pause(). + * SIG_DFL handler does not wake sigsuspend(). */ INT_OFF; if (block == DOWAIT_BLOCK_OR_SIG) { -- cgit v1.2.3-55-g6feb From 2b288236e80938d29324072a823f46861bd07cd3 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 3 Nov 2016 20:57:37 +0100 Subject: loadfont: 32k size limit is not enough Since our "read to malloced buf" routines only gradually grow allocations, let's be generous here and allow 128k. Reported by Alex Henrie Signed-off-by: Denys Vlasenko --- console-tools/loadfont.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/console-tools/loadfont.c b/console-tools/loadfont.c index 032506d6d..58073e0dc 100644 --- a/console-tools/loadfont.c +++ b/console-tools/loadfont.c @@ -319,8 +319,10 @@ int loadfont_main(int argc UNUSED_PARAM, char **argv) * We used to look at the length of the input file * with stat(); now that we accept compressed files, * just read the entire file. + * Len was 32k, but latarcyrheb-sun32.psfu is 34377 bytes + * (it has largish Unicode map). */ - len = 32*1024; // can't be larger + len = 128*1024; buffer = xmalloc_read(STDIN_FILENO, &len); // xmalloc_open_zipped_read_close(filename, &len); if (!buffer) @@ -405,7 +407,7 @@ int setfont_main(int argc UNUSED_PARAM, char **argv) } } // load font - len = 32*1024; // can't be larger + len = 128*1024; buffer = xmalloc_open_zipped_read_close(*argv, &len); if (!buffer) bb_simple_perror_msg_and_die(*argv); -- cgit v1.2.3-55-g6feb From 79e2598c48ad7e41d523f62368454c7d74f48268 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 3 Nov 2016 22:13:08 +0100 Subject: su: expand help; simplify passing of -c CMD to run_shell() Also, added a comment about bug 9401 (TIOCSTI input injection). function old new delta packed_usage 30909 30932 +23 su_main 470 487 +17 sulogin_main 260 258 -2 run_applet_and_exit 681 678 -3 run_shell 166 126 -40 Signed-off-by: Denys Vlasenko --- include/libbb.h | 2 +- libbb/executable.c | 2 +- libbb/run_shell.c | 29 ++++++++++++----------------- loginutils/login.c | 2 +- loginutils/su.c | 33 +++++++++++++++++++++++++++++---- loginutils/sulogin.c | 2 +- 6 files changed, 45 insertions(+), 25 deletions(-) diff --git a/include/libbb.h b/include/libbb.h index 3752df982..20fc7329f 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -1341,7 +1341,7 @@ char *bb_simplify_abs_path_inplace(char *path) FAST_FUNC; #endif extern void bb_do_delay(int seconds) FAST_FUNC; extern void change_identity(const struct passwd *pw) FAST_FUNC; -extern void run_shell(const char *shell, int loginshell, const char *command, const char **additional_args) NORETURN FAST_FUNC; +extern void run_shell(const char *shell, int loginshell, const char **args) NORETURN FAST_FUNC; /* Returns $SHELL, getpwuid(getuid())->pw_shell, or DEFAULT_SHELL. * Note that getpwuid result might need xstrdup'ing diff --git a/libbb/executable.c b/libbb/executable.c index 05e70312f..3a1d4ff44 100644 --- a/libbb/executable.c +++ b/libbb/executable.c @@ -97,5 +97,5 @@ void FAST_FUNC exec_prog_or_SHELL(char **argv) if (argv[0]) { BB_EXECVP_or_die(argv); } - run_shell(getenv("SHELL"), /*login:*/ 1, NULL, NULL); + run_shell(getenv("SHELL"), /*login:*/ 1, NULL); } diff --git a/libbb/run_shell.c b/libbb/run_shell.c index 4d92c3caa..b6b9360e8 100644 --- a/libbb/run_shell.c +++ b/libbb/run_shell.c @@ -50,19 +50,17 @@ void FAST_FUNC set_current_security_context(security_context_t sid) #endif /* Run SHELL, or DEFAULT_SHELL if SHELL is "" or NULL. - * If COMMAND is nonzero, pass it to the shell with the -c option. - * If ADDITIONAL_ARGS is nonzero, pass it to the shell as more - * arguments. */ -void FAST_FUNC run_shell(const char *shell, int loginshell, const char *command, const char **additional_args) + * If ADDITIONAL_ARGS is not NULL, pass them to the shell. + */ +void FAST_FUNC run_shell(const char *shell, int loginshell, const char **additional_args) { const char **args; - int argno; - int additional_args_cnt = 0; - for (args = additional_args; args && *args; args++) - additional_args_cnt++; + args = additional_args; + while (args && *args) + args++; - args = xmalloc(sizeof(char*) * (4 + additional_args_cnt)); + args = xmalloc(sizeof(char*) * (2 + (args - additional_args))); if (!shell || !shell[0]) shell = DEFAULT_SHELL; @@ -70,16 +68,13 @@ void FAST_FUNC run_shell(const char *shell, int loginshell, const char *command, args[0] = bb_get_last_path_component_nostrip(shell); if (loginshell) args[0] = xasprintf("-%s", args[0]); - argno = 1; - if (command) { - args[argno++] = "-c"; - args[argno++] = command; - } + args[1] = NULL; if (additional_args) { - for (; *additional_args; ++additional_args) - args[argno++] = *additional_args; + int cnt = 1; + for (;;) + if ((args[cnt++] = *additional_args++) == NULL) + break; } - args[argno] = NULL; #if ENABLE_SELINUX if (current_sid) diff --git a/loginutils/login.c b/loginutils/login.c index 94b6c45db..52abc1886 100644 --- a/loginutils/login.c +++ b/loginutils/login.c @@ -618,7 +618,7 @@ int login_main(int argc UNUSED_PARAM, char **argv) signal(SIGINT, SIG_DFL); /* Exec login shell with no additional parameters */ - run_shell(pw->pw_shell, 1, NULL, NULL); + run_shell(pw->pw_shell, 1, NULL); /* return EXIT_FAILURE; - not reached */ } diff --git a/loginutils/su.c b/loginutils/su.c index 3c0e8c100..24ffbde86 100644 --- a/loginutils/su.c +++ b/loginutils/su.c @@ -31,10 +31,10 @@ //kbuild:lib-$(CONFIG_SU) += su.o //usage:#define su_trivial_usage -//usage: "[OPTIONS] [-] [USER]" +//usage: "[-lmp] [-] [-s SH] [USER [SCRIPT ARGS / -c 'CMD' ARG0 ARGS]]" //usage:#define su_full_usage "\n\n" //usage: "Run shell under USER (by default, root)\n" -//usage: "\n -,-l Clear environment, run shell as login shell" +//usage: "\n -,-l Clear environment, go to home dir, run shell as login shell" //usage: "\n -p,-m Do not set new $HOME, $SHELL, $USER, $LOGNAME" //usage: "\n -c CMD Command to pass to 'sh -c'" //usage: "\n -s SH Shell to use instead of user's default" @@ -81,8 +81,12 @@ int su_main(int argc UNUSED_PARAM, char **argv) #endif const char *old_user; + /* Note: we don't use "'+': stop at first non-option" idiom here. + * For su, "SCRIPT ARGS" or "-c CMD ARGS" do not stop option parsing: + * ARGS starting with dash will be treated as su options, + * not passed to shell. (Tested on util-linux 2.28). + */ flags = getopt32(argv, "mplc:s:", &opt_command, &opt_shell); - //argc -= optind; argv += optind; if (argv[0] && LONE_DASH(argv[0])) { @@ -162,8 +166,29 @@ int su_main(int argc UNUSED_PARAM, char **argv) pw); IF_SELINUX(set_current_security_context(NULL);) + if (opt_command) { + *--argv = opt_command; + *--argv = (char*)"-c"; + } + + /* A nasty ioctl exists which can stuff data into input queue: + * #include + * int main() { + * const char *msg = "echo $UID\n"; + * while (*msg) ioctl(0, TIOCSTI, *msg++); + * return 0; + * } + * With "su USER -c EXPLOIT" run by root, exploit can make root shell + * read as input and execute arbitrary command. + * It's debatable whether we need to protect against this + * (root may hesitate to run unknown scripts interactively). + * + * Some versions of su run -c CMD in a different session: + * ioctl(TIOCSTI) works only on the controlling tty. + */ + /* Never returns */ - run_shell(opt_shell, flags & SU_OPT_l, opt_command, (const char**)argv); + run_shell(opt_shell, flags & SU_OPT_l, (const char**)argv); /* return EXIT_FAILURE; - not reached */ } diff --git a/loginutils/sulogin.c b/loginutils/sulogin.c index 6befea933..2e32e2bbd 100644 --- a/loginutils/sulogin.c +++ b/loginutils/sulogin.c @@ -89,5 +89,5 @@ int sulogin_main(int argc UNUSED_PARAM, char **argv) shell = pwd->pw_shell; /* Exec login shell with no additional parameters. Never returns. */ - run_shell(shell, 1, NULL, NULL); + run_shell(shell, 1, NULL); } -- cgit v1.2.3-55-g6feb From 834aba3b72cb0e45153b95fed991522f7f1986c9 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 4 Nov 2016 14:13:58 +0100 Subject: comment and readme updates Signed-off-by: Denys Vlasenko --- libbb/common_bufsiz.c | 9 +++++++++ loginutils/README | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/libbb/common_bufsiz.c b/libbb/common_bufsiz.c index 1a3585169..2847eb57d 100644 --- a/libbb/common_bufsiz.c +++ b/libbb/common_bufsiz.c @@ -54,6 +54,15 @@ char bb_common_bufsiz1[COMMON_BUFSIZE] ALIGNED(sizeof(long long)); #else # ifndef setup_common_bufsiz +/* For now, this is never used: + * scripts/generate_BUFSIZ.sh never generates "malloced" bufsiz1: + * enum { COMMON_BUFSIZE = 1024 }; + * extern char *const bb_common_bufsiz1; + * void setup_common_bufsiz(void); + * This has proved to be worse than the approach of defining + * larger bb_common_bufsiz1[] array. + */ + /* * It is not defined as a dummy macro. * It means we have to provide this function. diff --git a/loginutils/README b/loginutils/README index ce8851097..847b371b3 100644 --- a/loginutils/README +++ b/loginutils/README @@ -23,7 +23,7 @@ Getty should establish a new session and process group, and ensure that tty is a ctty. ??? Should getty ensure that other processes which might have opened -fds to this tty be dusconnected? agetty has a -R option which makes +fds to this tty be disconnected? agetty has a -R option which makes agetty call vhangup() after tty is opened. (Then agetty opens it again, since it probably vhangup'ed its own fd too). -- cgit v1.2.3-55-g6feb From 06b114900fc57cac0e422d26228f4d0aaf5d2288 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 4 Nov 2016 16:43:18 +0100 Subject: ash: fix "duplicate local" code (forgot to re-enable interrupts) Signed-off-by: Denys Vlasenko --- shell/ash.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/shell/ash.c b/shell/ash.c index f75642868..87f2127a1 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -32,6 +32,7 @@ #define DEBUG_TIME 0 #define DEBUG_PID 1 #define DEBUG_SIG 1 +#define DEBUG_INTONOFF 0 #define PROFILE 0 @@ -442,10 +443,18 @@ static void exitshell(void) NORETURN; * much more efficient and portable. (But hacking the kernel is so much * more fun than worrying about efficiency and portability. :-)) */ -#define INT_OFF do { \ +#if DEBUG_INTONOFF +# define INT_OFF do { \ + TRACE(("%s:%d INT_OFF(%d)\n", __func__, __LINE__, suppress_int)); \ suppress_int++; \ barrier(); \ } while (0) +#else +# define INT_OFF do { \ + suppress_int++; \ + barrier(); \ +} while (0) +#endif /* * Called to raise an exception. Since C doesn't include exceptions, we @@ -513,7 +522,14 @@ int_on(void) raise_interrupt(); } } -#define INT_ON int_on() +#if DEBUG_INTONOFF +# define INT_ON do { \ + TRACE(("%s:%d INT_ON(%d)\n", __func__, __LINE__, suppress_int-1)); \ + int_on(); \ +} while (0) +#else +# define INT_ON int_on() +#endif static IF_ASH_OPTIMIZE_FOR_SIZE(inline) void force_int_on(void) { @@ -9101,7 +9117,7 @@ mklocal(char *name) /* else: * it's a duplicate "local VAR" declaration, do nothing */ - return; + goto ret; } lvp = lvp->next; } @@ -9140,6 +9156,7 @@ mklocal(char *name) lvp->vp = vp; lvp->next = localvars; localvars = lvp; + ret: INT_ON; } -- cgit v1.2.3-55-g6feb From 672a55e606fc50e4ffe7810bd0d9cd1cf9b980a3 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 4 Nov 2016 18:46:14 +0100 Subject: hush: allow { cmd } to not be terminated by semicolon in some cases Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-misc/group_in_braces.right | 5 ++++ shell/ash_test/ash-misc/group_in_braces.tests | 11 +++++++++ shell/hush.c | 32 ++++++++++++++++++++----- shell/hush_test/hush-misc/group_in_braces.right | 5 ++++ shell/hush_test/hush-misc/group_in_braces.tests | 11 +++++++++ 5 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 shell/ash_test/ash-misc/group_in_braces.right create mode 100755 shell/ash_test/ash-misc/group_in_braces.tests create mode 100644 shell/hush_test/hush-misc/group_in_braces.right create mode 100755 shell/hush_test/hush-misc/group_in_braces.tests diff --git a/shell/ash_test/ash-misc/group_in_braces.right b/shell/ash_test/ash-misc/group_in_braces.right new file mode 100644 index 000000000..a7064499b --- /dev/null +++ b/shell/ash_test/ash-misc/group_in_braces.right @@ -0,0 +1,5 @@ +Zero:0 +Zero:0 +Zero:0 +Zero:0 +Zero:0 diff --git a/shell/ash_test/ash-misc/group_in_braces.tests b/shell/ash_test/ash-misc/group_in_braces.tests new file mode 100755 index 000000000..f6571c35d --- /dev/null +++ b/shell/ash_test/ash-misc/group_in_braces.tests @@ -0,0 +1,11 @@ +# Test cases where { cmd } does not require semicolon after "cmd" +(exit 2); { { true; } } +echo Zero:$? +(exit 2); {(true)} +echo Zero:$? +(exit 2); { true | { true; } } +echo Zero:$? +(exit 2); { while false; do :; done } +echo Zero:$? +(exit 2); { case a in b) ;; esac } +echo Zero:$? diff --git a/shell/hush.c b/shell/hush.c index 7c2f157b8..336de75ad 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -3915,12 +3915,17 @@ static int parse_group(o_string *dest, struct parse_context *ctx, command->cmd_type = CMD_SUBSHELL; } else { /* bash does not allow "{echo...", requires whitespace */ - ch = i_getch(input); - if (ch != ' ' && ch != '\t' && ch != '\n') { + ch = i_peek(input); + if (ch != ' ' && ch != '\t' && ch != '\n' + && ch != '(' /* but "{(..." is allowed (without whitespace) */ + ) { syntax_error_unexpected_ch(ch); return 1; } - nommu_addchr(&ctx->as_string, ch); + if (ch != '(') { + ch = i_getch(input); + nommu_addchr(&ctx->as_string, ch); + } } { @@ -4575,6 +4580,7 @@ static struct pipe *parse_stream(char **pstring, || dest.has_quoted_part /* ""{... - non-special */ || (next != ';' /* }; - special */ && next != ')' /* }) - special */ + && next != '(' /* {( - special */ && next != '&' /* }& and }&& ... - special */ && next != '|' /* }|| ... - special */ && !strchr(defifs, next) /* {word - non-special */ @@ -4657,17 +4663,31 @@ static struct pipe *parse_stream(char **pstring, * Pathological example: { ""}; } should exec "}" cmd */ if (ch == '}') { - if (!IS_NULL_CMD(ctx.command) /* cmd } */ - || dest.length != 0 /* word} */ + if (dest.length != 0 /* word} */ || dest.has_quoted_part /* ""} */ ) { goto ordinary_char; } + if (!IS_NULL_CMD(ctx.command)) { /* cmd } */ + /* Generally, there should be semicolon: "cmd; }" + * However, bash allows to omit it if "cmd" is + * a group. Examples: + * { { echo 1; } } + * {(echo 1)} + * { echo 0 >&2 | { echo 1; } } + * { while false; do :; done } + * { case a in b) ;; esac } + */ + if (ctx.command->group) + goto term_group; + goto ordinary_char; + } if (!IS_NULL_PIPE(ctx.pipe)) /* cmd | } */ + /* Can't be an end of {cmd}, skip the check */ goto skip_end_trigger; /* else: } does terminate a group */ } - + term_group: if (end_trigger && end_trigger == ch && (ch != ';' || heredoc_cnt == 0) #if ENABLE_HUSH_CASE diff --git a/shell/hush_test/hush-misc/group_in_braces.right b/shell/hush_test/hush-misc/group_in_braces.right new file mode 100644 index 000000000..a7064499b --- /dev/null +++ b/shell/hush_test/hush-misc/group_in_braces.right @@ -0,0 +1,5 @@ +Zero:0 +Zero:0 +Zero:0 +Zero:0 +Zero:0 diff --git a/shell/hush_test/hush-misc/group_in_braces.tests b/shell/hush_test/hush-misc/group_in_braces.tests new file mode 100755 index 000000000..f6571c35d --- /dev/null +++ b/shell/hush_test/hush-misc/group_in_braces.tests @@ -0,0 +1,11 @@ +# Test cases where { cmd } does not require semicolon after "cmd" +(exit 2); { { true; } } +echo Zero:$? +(exit 2); {(true)} +echo Zero:$? +(exit 2); { true | { true; } } +echo Zero:$? +(exit 2); { while false; do :; done } +echo Zero:$? +(exit 2); { case a in b) ;; esac } +echo Zero:$? -- cgit v1.2.3-55-g6feb From 30bfcf612b0862e4dd98d923eabb308b54012d24 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 4 Nov 2016 18:52:48 +0100 Subject: hush: non-matching "case" statement sets $? to 0 Signed-off-by: Denys Vlasenko --- shell/hush.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shell/hush.c b/shell/hush.c index 336de75ad..4c2ed6cea 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -7874,6 +7874,8 @@ static int run_list(struct pipe *pi) #endif #if ENABLE_HUSH_CASE if (rword == RES_CASE) { + /* Case which does not match and execute anything still sets $? to 0 */ + G.last_exitcode = rcode = EXIT_SUCCESS; case_word = expand_strvec_to_string(pi->cmds->argv); continue; } -- cgit v1.2.3-55-g6feb From aeaee43d5a4fc95405cc2047227d096b1ada55b6 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 4 Nov 2016 20:14:04 +0100 Subject: hush: case logic for setting $? was still wrong Resetting to 0 should happen in "esac". Matched branch must still see previous $?. Signed-off-by: Denys Vlasenko --- shell/hush.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index 4c2ed6cea..0bc67ecc9 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -7874,14 +7874,14 @@ static int run_list(struct pipe *pi) #endif #if ENABLE_HUSH_CASE if (rword == RES_CASE) { - /* Case which does not match and execute anything still sets $? to 0 */ - G.last_exitcode = rcode = EXIT_SUCCESS; + debug_printf_exec("CASE cond_code:%d\n", cond_code); case_word = expand_strvec_to_string(pi->cmds->argv); continue; } if (rword == RES_MATCH) { char **argv; + debug_printf_exec("MATCH cond_code:%d\n", cond_code); if (!case_word) /* "case ... matched_word) ... WORD)": we executed selected branch, stop */ break; /* all prev words didn't match, does this one match? */ @@ -7892,8 +7892,8 @@ static int run_list(struct pipe *pi) cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0); free(pattern); if (cond_code == 0) { /* match! we will execute this branch */ - free(case_word); /* make future "word)" stop */ - case_word = NULL; + free(case_word); + case_word = NULL; /* make future "word)" stop */ break; } argv++; @@ -7901,9 +7901,17 @@ static int run_list(struct pipe *pi) continue; } if (rword == RES_CASE_BODY) { /* inside of a case branch */ + debug_printf_exec("CASE_BODY cond_code:%d\n", cond_code); if (cond_code != 0) continue; /* not matched yet, skip this pipe */ } + if (rword == RES_ESAC) { + debug_printf_exec("ESAC cond_code:%d\n", cond_code); + if (case_word) { + /* "case" did not match anything: still set $? (to 0) */ + G.last_exitcode = rcode = EXIT_SUCCESS; + } + } #endif /* Just pressing in shell should check for jobs. * OTOH, in non-interactive shell this is useless -- cgit v1.2.3-55-g6feb From 46443a383cdd977d3b7644ffdac8041fa55e51da Mon Sep 17 00:00:00 2001 From: Aaro Koskinen Date: Thu, 3 Nov 2016 00:25:05 +0200 Subject: cpio: add ownership (-R) test cases Add ownership (-R) test cases. Signed-off-by: Aaro Koskinen Signed-off-by: Denys Vlasenko --- testsuite/cpio.tests | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/testsuite/cpio.tests b/testsuite/cpio.tests index 4cd441a60..d44c95b10 100755 --- a/testsuite/cpio.tests +++ b/testsuite/cpio.tests @@ -127,6 +127,30 @@ testing "cpio extracts in existing directory" \ " "" "" SKIP= +testing "cpio uses by default uid/gid" \ +"echo $0 | cpio -o -H newc | cpio -tv 2>&1 | tail -n +2 | awk ' { print \$2 } '; echo \$?" \ +"\ +$user/$group +0 +" "" "" +SKIP= + +testing "cpio -R with create" \ +"echo $0 | cpio -o -H newc -R 1234:5678 | cpio -tv 2>&1 | tail -n +2 | awk ' { print \$2 } '; echo \$?" \ +"\ +1234/5678 +0 +" "" "" +SKIP= + +testing "cpio -R with extract" \ +"echo $0 | cpio -o -H newc | cpio -tv -R 8765:4321 2>&1 | tail -n +2 | awk ' { print \$2 } '; echo \$?" \ +"\ +8765/4321 +0 +" "" "" +SKIP= + # Clean up rm -rf cpio.testdir cpio.testdir2 2>/dev/null -- cgit v1.2.3-55-g6feb From 4224647c8d0990f8ffb17d8481655827161a94d4 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 7 Nov 2016 16:22:35 +0100 Subject: hush: do not allow sh -c '{ echo boo }' Signed-off-by: Denys Vlasenko --- shell/hush.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index 0bc67ecc9..a01db9c75 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -4533,12 +4533,14 @@ static struct pipe *parse_stream(char **pstring, syntax_error_unterm_str("here document"); goto parse_error; } - /* end_trigger == '}' case errors out earlier, - * checking only ')' */ if (end_trigger == ')') { syntax_error_unterm_ch('('); goto parse_error; } + if (end_trigger == '}') { + syntax_error_unterm_ch('{'); + goto parse_error; + } if (done_word(&dest, &ctx)) { goto parse_error; -- cgit v1.2.3-55-g6feb From 5d5a611df2cfd8efa721866e3571e12a45c3b8d8 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 7 Nov 2016 19:36:50 +0100 Subject: hush: comment fix Signed-off-by: Denys Vlasenko --- shell/hush.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index a01db9c75..aa474afb2 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -5009,8 +5009,8 @@ static struct pipe *parse_stream(char **pstring, * Run it from interactive shell, watch pmap `pidof hush`. * while if false; then false; fi; do break; fi * Samples to catch leaks at execution: - * while if (true | {true;}); then echo ok; fi; do break; done - * while if (true | {true;}); then echo ok; fi; do (if echo ok; break; then :; fi) | cat; break; done + * while if (true | { true;}); then echo ok; fi; do break; done + * while if (true | { true;}); then echo ok; fi; do (if echo ok; break; then :; fi) | cat; break; done */ pctx = &ctx; do { -- cgit v1.2.3-55-g6feb From 4e1c8b4f6a5765e1c2b5033f6fd2196408874233 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 7 Nov 2016 20:06:40 +0100 Subject: hush: factor out %jobspec parsing Signed-off-by: Denys Vlasenko --- shell/hush.c | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index aa474afb2..3b87d283c 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -9226,10 +9226,28 @@ static int FAST_FUNC builtin_type(char **argv) } #if ENABLE_HUSH_JOB +static struct pipe *parse_jobspec(const char *str) +{ + struct pipe *pi; + int jobnum; + + if (sscanf(str, "%%%d", &jobnum) != 1) { + bb_error_msg("bad argument '%s'", str); + return NULL; + } + for (pi = G.job_list; pi; pi = pi->next) { + if (pi->jobid == jobnum) { + return pi; + } + } + bb_error_msg("%d: no such job", jobnum); + return NULL; +} + /* built-in 'fg' and 'bg' handler */ static int FAST_FUNC builtin_fg_bg(char **argv) { - int i, jobnum; + int i; struct pipe *pi; if (!G_interactive_fd) @@ -9245,17 +9263,10 @@ static int FAST_FUNC builtin_fg_bg(char **argv) bb_error_msg("%s: no current job", argv[0]); return EXIT_FAILURE; } - if (sscanf(argv[1], "%%%d", &jobnum) != 1) { - bb_error_msg("%s: bad argument '%s'", argv[0], argv[1]); + + pi = parse_jobspec(argv[1]); + if (!pi) return EXIT_FAILURE; - } - for (pi = G.job_list; pi; pi = pi->next) { - if (pi->jobid == jobnum) { - goto found; - } - } - bb_error_msg("%s: %d: no such job", argv[0], jobnum); - return EXIT_FAILURE; found: /* TODO: bash prints a string representation * of job being foregrounded (like "sleep 1 | cat") */ -- cgit v1.2.3-55-g6feb From 62b717b75ebff3ab00aae2fee0087a8106208a39 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 7 Nov 2016 22:12:18 +0100 Subject: hush: implement "wait %jobspec" function old new delta parse_jobspec - 83 +83 job_exited_or_stopped - 79 +79 builtin_wait 236 302 +66 wait_for_child_or_signal 199 228 +29 checkjobs 142 158 +16 builtin_jobs 59 68 +9 process_wait_result 453 408 -45 builtin_fg_bg 272 203 -69 ------------------------------------------------------------------------------ (add/remove: 2/0 grow/shrink: 4/2 up/down: 282/-114) Total: 168 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 117 ++++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 88 insertions(+), 29 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index 3b87d283c..b842d6eec 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -45,7 +45,6 @@ * tilde expansion * aliases * kill %jobspec - * wait %jobspec * follow IFS rules more precisely, including update semantics * builtins mandated by standards we don't support: * [un]alias, command, fc, getopts, newgrp, readonly, times @@ -7065,6 +7064,27 @@ static void delete_finished_bg_job(struct pipe *pi) } #endif /* JOB */ +static int job_exited_or_stopped(struct pipe *pi) +{ + int rcode, i; + + if (pi->alive_cmds != pi->stopped_cmds) + return -1; + + /* All processes in fg pipe have exited or stopped */ + rcode = 0; + i = pi->num_cmds; + while (--i >= 0) { + rcode = pi->cmds[i].cmd_exitcode; + /* usually last process gives overall exitstatus, + * but with "set -o pipefail", last *failed* process does */ + if (G.o_opt[OPT_O_PIPEFAIL] == 0 || rcode != 0) + break; + } + IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) + return rcode; +} + static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status) { #if ENABLE_HUSH_JOB @@ -7088,7 +7108,10 @@ static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status) /* Were we asked to wait for a fg pipe? */ if (fg_pipe) { i = fg_pipe->num_cmds; + while (--i >= 0) { + int rcode; + debug_printf_jobs("check pid %d\n", fg_pipe->cmds[i].pid); if (fg_pipe->cmds[i].pid != childpid) continue; @@ -7117,18 +7140,8 @@ static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status) } debug_printf_jobs("fg_pipe: alive_cmds %d stopped_cmds %d\n", fg_pipe->alive_cmds, fg_pipe->stopped_cmds); - if (fg_pipe->alive_cmds == fg_pipe->stopped_cmds) { - /* All processes in fg pipe have exited or stopped */ - int rcode = 0; - i = fg_pipe->num_cmds; - while (--i >= 0) { - rcode = fg_pipe->cmds[i].cmd_exitcode; - /* usually last process gives overall exitstatus, - * but with "set -o pipefail", last *failed* process does */ - if (G.o_opt[OPT_O_PIPEFAIL] == 0 || rcode != 0) - break; - } - IF_HAS_KEYWORDS(if (fg_pipe->pi_inverted) rcode = !rcode;) + rcode = job_exited_or_stopped(fg_pipe); + if (rcode >= 0) { /* Note: *non-interactive* bash does not continue if all processes in fg pipe * are stopped. Testcase: "cat | cat" in a script (not on command line!) * and "killall -STOP cat" */ @@ -7185,9 +7198,18 @@ static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status) /* Check to see if any processes have exited -- if they have, * figure out why and see if a job has completed. - * Alternatively (fg_pipe == NULL, waitfor_pid != 0), - * wait for a specific pid to complete, return exitcode+1 - * (this allows to distinguish zero as "no children exited" result). + * + * If non-NULL fg_pipe: wait for its completion or stop. + * Return its exitcode or zero if stopped. + * + * Alternatively (fg_pipe == NULL, waitfor_pid != 0): + * waitpid(WNOHANG), if waitfor_pid exits or stops, return exitcode+1, + * else return <0 if waitpid errors out (e.g. ECHILD: nothing to wait for) + * or 0 if no children changed status. + * + * Alternatively (fg_pipe == NULL, waitfor_pid == 0), + * return <0 if waitpid errors out (e.g. ECHILD: nothing to wait for) + * or 0 if no children changed status. */ static int checkjobs(struct pipe *fg_pipe, pid_t waitfor_pid) { @@ -7256,9 +7278,13 @@ static int checkjobs(struct pipe *fg_pipe, pid_t waitfor_pid) break; } if (childpid == waitfor_pid) { + debug_printf_exec("childpid==waitfor_pid:%d status:0x%08x\n", childpid, status); rcode = WEXITSTATUS(status); if (WIFSIGNALED(status)) rcode = 128 + WTERMSIG(status); + if (WIFSTOPPED(status)) + /* bash: "cmd & wait $!" and cmd stops: $? = 128 + stopsig */ + rcode = 128 + WSTOPSIG(status); rcode++; break; /* "wait PID" called us, give it exitcode+1 */ } @@ -9329,6 +9355,7 @@ static int FAST_FUNC builtin_jobs(char **argv UNUSED_PARAM) struct pipe *job; const char *status_string; + checkjobs(NULL, 0 /*(no pid to wait for)*/); for (job = G.job_list; job; job = job->next) { if (job->alive_cmds == job->stopped_cmds) status_string = "Stopped"; @@ -9481,12 +9508,15 @@ static int FAST_FUNC builtin_umask(char **argv) } /* http://www.opengroup.org/onlinepubs/9699919799/utilities/wait.html */ -static int wait_for_child_or_signal(pid_t waitfor_pid) +#if !ENABLE_HUSH_JOB +# define wait_for_child_or_signal(pipe,pid) wait_for_child_or_signal(pid) +#endif +static int wait_for_child_or_signal(struct pipe *waitfor_pipe, pid_t waitfor_pid) { int ret = 0; for (;;) { int sig; - sigset_t oldset, allsigs; + sigset_t oldset; /* waitpid is not interruptible by SA_RESTARTed * signals which we use. Thus, this ugly dance: @@ -9495,10 +9525,10 @@ static int wait_for_child_or_signal(pid_t waitfor_pid) /* Make sure possible SIGCHLD is stored in kernel's * pending signal mask before we call waitpid. * Or else we may race with SIGCHLD, lose it, - * and get stuck in sigwaitinfo... + * and get stuck in sigsuspend... */ - sigfillset(&allsigs); - sigprocmask(SIG_SETMASK, &allsigs, &oldset); + sigfillset(&oldset); /* block all signals, remember old set */ + sigprocmask(SIG_SETMASK, &oldset, &oldset); if (!sigisemptyset(&G.pending_set)) { /* Crap! we raced with some signal! */ @@ -9507,19 +9537,31 @@ static int wait_for_child_or_signal(pid_t waitfor_pid) } /*errno = 0; - checkjobs does this */ +/* Can't pass waitfor_pipe into checkjobs(): it won't be interruptible */ ret = checkjobs(NULL, waitfor_pid); /* waitpid(WNOHANG) inside */ + debug_printf_exec("checkjobs:%d\n", ret); +#if ENABLE_HUSH_JOB + if (waitfor_pipe) { + int rcode = job_exited_or_stopped(waitfor_pipe); + debug_printf_exec("job_exited_or_stopped:%d\n", rcode); + if (rcode >= 0) { + ret = rcode; + sigprocmask(SIG_SETMASK, &oldset, NULL); + break; + } + } +#endif /* if ECHILD, there are no children (ret is -1 or 0) */ /* if ret == 0, no children changed state */ /* if ret != 0, it's exitcode+1 of exited waitfor_pid child */ - if (errno == ECHILD || ret--) { - if (ret < 0) /* if ECHILD, may need to fix */ + if (errno == ECHILD || ret) { + ret--; + if (ret < 0) /* if ECHILD, may need to fix "ret" */ ret = 0; sigprocmask(SIG_SETMASK, &oldset, NULL); break; } - /* Wait for SIGCHLD or any other signal */ - //sig = sigwaitinfo(&allsigs, NULL); /* It is vitally important for sigsuspend that SIGCHLD has non-DFL handler! */ /* Note: sigsuspend invokes signal handler */ sigsuspend(&oldset); @@ -9544,6 +9586,7 @@ static int FAST_FUNC builtin_wait(char **argv) { int ret; int status; + struct pipe *wait_pipe = NULL; argv = skip_dash_dash(argv); if (argv[0] == NULL) { @@ -9563,18 +9606,27 @@ static int FAST_FUNC builtin_wait(char **argv) * ^C <-- after ~4 sec from keyboard * $ */ - return wait_for_child_or_signal(0 /*(no pid to wait for)*/); + return wait_for_child_or_signal(NULL, 0 /*(no job and no pid to wait for)*/); } - /* TODO: support "wait %jobspec" */ do { pid_t pid = bb_strtou(*argv, NULL, 10); if (errno || pid <= 0) { +#if ENABLE_HUSH_JOB + if (argv[0][0] == '%') { + wait_pipe = parse_jobspec(*argv); + if (wait_pipe) { + pid = - wait_pipe->pgrp; + goto do_wait; + } + } +#endif /* mimic bash message */ bb_error_msg("wait: '%s': not a pid or valid job spec", *argv); ret = EXIT_FAILURE; continue; /* bash checks all argv[] */ } + IF_HUSH_JOB(do_wait:) /* Do we have such child? */ ret = waitpid(pid, &status, WNOHANG); if (ret < 0) { @@ -9599,13 +9651,20 @@ static int FAST_FUNC builtin_wait(char **argv) } if (ret == 0) { /* Yes, and it still runs */ - ret = wait_for_child_or_signal(pid); + ret = wait_for_child_or_signal(wait_pipe, wait_pipe ? 0 : pid); } else { /* Yes, and it just exited */ - process_wait_result(NULL, pid, status); + process_wait_result(NULL, ret, status); ret = WEXITSTATUS(status); if (WIFSIGNALED(status)) ret = 128 + WTERMSIG(status); +#if ENABLE_HUSH_JOB + if (wait_pipe) { + ret = job_exited_or_stopped(wait_pipe); + if (ret < 0) + goto do_wait; + } +#endif } } while (*++argv); -- cgit v1.2.3-55-g6feb From 26ad94bedcc6a4aa3feb07ea032709bcd517ee46 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 7 Nov 2016 23:07:21 +0100 Subject: hush: "wait $!; echo $?" should return 127 if $! already exited It would be nice to provide bash-like "remember las exitcode" thingy, but it's a bit complex. For now, match ash and dash. Signed-off-by: Denys Vlasenko --- shell/hush.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index b842d6eec..7683a3749 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -9636,12 +9636,13 @@ static int FAST_FUNC builtin_wait(char **argv) /* "wait $!" but last bg task has already exited. Try: * (sleep 1; exit 3) & sleep 2; echo $?; wait $!; echo $? * In bash it prints exitcode 0, then 3. + * In dash, it is 127. */ - ret = 0; /* FIXME */ - continue; + /* ret = G.last_bg_pid_exitstatus - FIXME */ + } else { + /* Example: "wait 1". mimic bash message */ + bb_error_msg("wait: pid %d is not a child of this shell", (int)pid); } - /* Example: "wait 1". mimic bash message */ - bb_error_msg("wait: pid %d is not a child of this shell", (int)pid); } else { /* ??? */ bb_perror_msg("wait %s", *argv); -- cgit v1.2.3-55-g6feb From 02affb4afd4a71b0c1ca6286f9b0f6bf3b10e0a1 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 8 Nov 2016 00:59:29 +0100 Subject: hush: rework "wait %jobspec" to work in non-interactive shells too Also add tests. wait5.tests so far fails (but works for ash and dash). function old new delta builtin_wait 305 283 -22 Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-misc/wait4.right | 1 + shell/ash_test/ash-misc/wait4.tests | 2 ++ shell/ash_test/ash-misc/wait5.right | 2 ++ shell/ash_test/ash-misc/wait5.tests | 5 +++++ shell/hush.c | 21 ++++++++------------- shell/hush_test/hush-misc/wait4.right | 1 + shell/hush_test/hush-misc/wait4.tests | 2 ++ shell/hush_test/hush-misc/wait5.right | 2 ++ shell/hush_test/hush-misc/wait5.tests | 5 +++++ 9 files changed, 28 insertions(+), 13 deletions(-) create mode 100644 shell/ash_test/ash-misc/wait4.right create mode 100755 shell/ash_test/ash-misc/wait4.tests create mode 100644 shell/ash_test/ash-misc/wait5.right create mode 100755 shell/ash_test/ash-misc/wait5.tests create mode 100644 shell/hush_test/hush-misc/wait4.right create mode 100755 shell/hush_test/hush-misc/wait4.tests create mode 100644 shell/hush_test/hush-misc/wait5.right create mode 100755 shell/hush_test/hush-misc/wait5.tests diff --git a/shell/ash_test/ash-misc/wait4.right b/shell/ash_test/ash-misc/wait4.right new file mode 100644 index 000000000..f7987db32 --- /dev/null +++ b/shell/ash_test/ash-misc/wait4.right @@ -0,0 +1 @@ +Three:3 diff --git a/shell/ash_test/ash-misc/wait4.tests b/shell/ash_test/ash-misc/wait4.tests new file mode 100755 index 000000000..cc34059ac --- /dev/null +++ b/shell/ash_test/ash-misc/wait4.tests @@ -0,0 +1,2 @@ +sleep 1 | (sleep 1;exit 3) & wait %1 +echo Three:$? diff --git a/shell/ash_test/ash-misc/wait5.right b/shell/ash_test/ash-misc/wait5.right new file mode 100644 index 000000000..82c9d5696 --- /dev/null +++ b/shell/ash_test/ash-misc/wait5.right @@ -0,0 +1,2 @@ +Zero:0 +Three:3 diff --git a/shell/ash_test/ash-misc/wait5.tests b/shell/ash_test/ash-misc/wait5.tests new file mode 100755 index 000000000..1b4762d89 --- /dev/null +++ b/shell/ash_test/ash-misc/wait5.tests @@ -0,0 +1,5 @@ +sleep 0 | (sleep 0;exit 3) & +sleep 1 +echo Zero:$? +wait %1 +echo Three:$? diff --git a/shell/hush.c b/shell/hush.c index 7683a3749..ddbf2f7d8 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -9586,7 +9586,6 @@ static int FAST_FUNC builtin_wait(char **argv) { int ret; int status; - struct pipe *wait_pipe = NULL; argv = skip_dash_dash(argv); if (argv[0] == NULL) { @@ -9614,10 +9613,13 @@ static int FAST_FUNC builtin_wait(char **argv) if (errno || pid <= 0) { #if ENABLE_HUSH_JOB if (argv[0][0] == '%') { + struct pipe *wait_pipe; wait_pipe = parse_jobspec(*argv); if (wait_pipe) { - pid = - wait_pipe->pgrp; - goto do_wait; + ret = job_exited_or_stopped(wait_pipe); + if (ret < 0) + ret = wait_for_child_or_signal(wait_pipe, 0); + continue; } } #endif @@ -9626,7 +9628,7 @@ static int FAST_FUNC builtin_wait(char **argv) ret = EXIT_FAILURE; continue; /* bash checks all argv[] */ } - IF_HUSH_JOB(do_wait:) + /* Do we have such child? */ ret = waitpid(pid, &status, WNOHANG); if (ret < 0) { @@ -9652,20 +9654,13 @@ static int FAST_FUNC builtin_wait(char **argv) } if (ret == 0) { /* Yes, and it still runs */ - ret = wait_for_child_or_signal(wait_pipe, wait_pipe ? 0 : pid); + ret = wait_for_child_or_signal(NULL, pid); } else { /* Yes, and it just exited */ - process_wait_result(NULL, ret, status); + process_wait_result(NULL, pid, status); ret = WEXITSTATUS(status); if (WIFSIGNALED(status)) ret = 128 + WTERMSIG(status); -#if ENABLE_HUSH_JOB - if (wait_pipe) { - ret = job_exited_or_stopped(wait_pipe); - if (ret < 0) - goto do_wait; - } -#endif } } while (*++argv); diff --git a/shell/hush_test/hush-misc/wait4.right b/shell/hush_test/hush-misc/wait4.right new file mode 100644 index 000000000..f7987db32 --- /dev/null +++ b/shell/hush_test/hush-misc/wait4.right @@ -0,0 +1 @@ +Three:3 diff --git a/shell/hush_test/hush-misc/wait4.tests b/shell/hush_test/hush-misc/wait4.tests new file mode 100755 index 000000000..cc34059ac --- /dev/null +++ b/shell/hush_test/hush-misc/wait4.tests @@ -0,0 +1,2 @@ +sleep 1 | (sleep 1;exit 3) & wait %1 +echo Three:$? diff --git a/shell/hush_test/hush-misc/wait5.right b/shell/hush_test/hush-misc/wait5.right new file mode 100644 index 000000000..82c9d5696 --- /dev/null +++ b/shell/hush_test/hush-misc/wait5.right @@ -0,0 +1,2 @@ +Zero:0 +Three:3 diff --git a/shell/hush_test/hush-misc/wait5.tests b/shell/hush_test/hush-misc/wait5.tests new file mode 100755 index 000000000..1b4762d89 --- /dev/null +++ b/shell/hush_test/hush-misc/wait5.tests @@ -0,0 +1,5 @@ +sleep 0 | (sleep 0;exit 3) & +sleep 1 +echo Zero:$? +wait %1 +echo Three:$? -- cgit v1.2.3-55-g6feb From 830ea35484cecb8b4cdbe0f30cc5d573ff0e3411 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 8 Nov 2016 04:59:11 +0100 Subject: hush: make "wait %1" less likely to play with signal mask Was playing with "sleep 3 | exit 3 & wait %1" and noticed that often SIGCHLD arrives even before I get to signal masking. Can avoid it in this case. function old new delta wait_for_child_or_signal 228 265 +37 Signed-off-by: Denys Vlasenko --- shell/hush.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index ddbf2f7d8..5e51adfdc 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -8146,11 +8146,11 @@ static void install_fatal_sighandlers(void) /* We will restore tty pgrp on these signals */ mask = 0 - + (1 << SIGILL ) * HUSH_DEBUG - + (1 << SIGFPE ) * HUSH_DEBUG + /*+ (1 << SIGILL ) * HUSH_DEBUG*/ + /*+ (1 << SIGFPE ) * HUSH_DEBUG*/ + (1 << SIGBUS ) * HUSH_DEBUG + (1 << SIGSEGV) * HUSH_DEBUG - + (1 << SIGTRAP) * HUSH_DEBUG + /*+ (1 << SIGTRAP) * HUSH_DEBUG*/ + (1 << SIGABRT) /* bash 3.2 seems to handle these just like 'fatal' ones */ + (1 << SIGPIPE) @@ -9518,6 +9518,9 @@ static int wait_for_child_or_signal(struct pipe *waitfor_pipe, pid_t waitfor_pid int sig; sigset_t oldset; + if (!sigisemptyset(&G.pending_set)) + goto check_sig; + /* waitpid is not interruptible by SA_RESTARTed * signals which we use. Thus, this ugly dance: */ @@ -9532,7 +9535,6 @@ static int wait_for_child_or_signal(struct pipe *waitfor_pipe, pid_t waitfor_pid if (!sigisemptyset(&G.pending_set)) { /* Crap! we raced with some signal! */ - // sig = 0; goto restore; } @@ -9567,13 +9569,10 @@ static int wait_for_child_or_signal(struct pipe *waitfor_pipe, pid_t waitfor_pid sigsuspend(&oldset); restore: sigprocmask(SIG_SETMASK, &oldset, NULL); - + check_sig: /* So, did we get a signal? */ - //if (sig > 0) - // raise(sig); /* run handler */ sig = check_and_run_traps(); if (sig /*&& sig != SIGCHLD - always true */) { - /* see note 2 */ ret = 128 + sig; break; } -- cgit v1.2.3-55-g6feb From 1eada9ad8d31addd57213a072ddfc278e5776740 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 8 Nov 2016 17:28:45 +0100 Subject: hush: simplify insert_bg_jobs function old new delta insert_bg_job 366 281 -85 Signed-off-by: Denys Vlasenko --- shell/hush.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index 5e51adfdc..78a8f5c03 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -6983,12 +6983,12 @@ static const char *get_cmdtext(struct pipe *pi) * On subsequent bg argv is trashed, but we won't use it */ if (pi->cmdtext) return pi->cmdtext; + argv = pi->cmds[0].argv; - if (!argv || !argv[0]) { + if (!argv) { pi->cmdtext = xzalloc(1); return pi->cmdtext; } - len = 0; do { len += strlen(*argv) + 1; @@ -6997,9 +6997,7 @@ static const char *get_cmdtext(struct pipe *pi) pi->cmdtext = p; argv = pi->cmds[0].argv; do { - len = strlen(*argv); - memcpy(p, *argv, len); - p += len; + p = stpcpy(p, *argv); *p++ = ' '; } while (*++argv); p[-1] = '\0'; @@ -7965,8 +7963,8 @@ static int run_list(struct pipe *pi) /* We ran a builtin, function, or group. * rcode is already known * and we don't need to wait for anything. */ - G.last_exitcode = rcode; debug_printf_exec(": builtin/func exitcode %d\n", rcode); + G.last_exitcode = rcode; check_and_run_traps(); #if ENABLE_HUSH_LOOPS /* Was it "break" or "continue"? */ @@ -7998,30 +7996,30 @@ static int run_list(struct pipe *pi) /* even bash 3.2 doesn't do that well with nested bg: * try "{ { sleep 10; echo DEEP; } & echo HERE; } &". * I'm NOT treating inner &'s as jobs */ - check_and_run_traps(); #if ENABLE_HUSH_JOB if (G.run_list_level == 1) insert_bg_job(pi); #endif /* Last command's pid goes to $! */ G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid; - G.last_exitcode = rcode = EXIT_SUCCESS; debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n"); +/* Check pi->pi_inverted? "! sleep 1 & echo $?": bash says 1. dash and ash says 0 */ + G.last_exitcode = rcode = EXIT_SUCCESS; + check_and_run_traps(); } else { #if ENABLE_HUSH_JOB if (G.run_list_level == 1 && G_interactive_fd) { /* Waits for completion, then fg's main shell */ rcode = checkjobs_and_fg_shell(pi); debug_printf_exec(": checkjobs_and_fg_shell exitcode %d\n", rcode); - check_and_run_traps(); } else #endif { /* This one just waits for completion */ rcode = checkjobs(pi, 0 /*(no pid to wait for)*/); debug_printf_exec(": checkjobs exitcode %d\n", rcode); - check_and_run_traps(); } G.last_exitcode = rcode; + check_and_run_traps(); } } -- cgit v1.2.3-55-g6feb From 5cc9bf6a21ae0738528c2fb301ff4be2ab662ee9 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 8 Nov 2016 17:34:44 +0100 Subject: hush: deindent large block of code, no code changes Signed-off-by: Denys Vlasenko --- shell/hush.c | 115 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 57 insertions(+), 58 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index 78a8f5c03..a5f059924 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -7803,6 +7803,8 @@ static int run_list(struct pipe *pi) /* Go through list of pipes, (maybe) executing them. */ for (; pi; pi = IF_HUSH_LOOPS(rword == RES_DONE ? loop_top : ) pi->next) { + int r; + if (G.flag_SIGINT) break; if (G_flag_return_in_progress == 1) @@ -7953,74 +7955,71 @@ static int run_list(struct pipe *pi) * after run_pipe to collect any background children, * even if list execution is to be stopped. */ debug_printf_exec(": run_pipe with %d members\n", pi->num_cmds); - { - int r; -#if ENABLE_HUSH_LOOPS - G.flag_break_continue = 0; -#endif - rcode = r = run_pipe(pi); /* NB: rcode is a smallint */ - if (r != -1) { - /* We ran a builtin, function, or group. - * rcode is already known - * and we don't need to wait for anything. */ - debug_printf_exec(": builtin/func exitcode %d\n", rcode); - G.last_exitcode = rcode; - check_and_run_traps(); #if ENABLE_HUSH_LOOPS - /* Was it "break" or "continue"? */ - if (G.flag_break_continue) { - smallint fbc = G.flag_break_continue; - /* We might fall into outer *loop*, - * don't want to break it too */ - if (loop_top) { - G.depth_break_continue--; - if (G.depth_break_continue == 0) - G.flag_break_continue = 0; - /* else: e.g. "continue 2" should *break* once, *then* continue */ - } /* else: "while... do... { we are here (innermost list is not a loop!) };...done" */ - if (G.depth_break_continue != 0 || fbc == BC_BREAK) { - checkjobs(NULL, 0 /*(no pid to wait for)*/); - break; - } - /* "continue": simulate end of loop */ - rword = RES_DONE; - continue; - } + G.flag_break_continue = 0; #endif - if (G_flag_return_in_progress == 1) { + rcode = r = run_pipe(pi); /* NB: rcode is a smalluint, r is int */ + if (r != -1) { + /* We ran a builtin, function, or group. + * rcode is already known + * and we don't need to wait for anything. */ + debug_printf_exec(": builtin/func exitcode %d\n", rcode); + G.last_exitcode = rcode; + check_and_run_traps(); +#if ENABLE_HUSH_LOOPS + /* Was it "break" or "continue"? */ + if (G.flag_break_continue) { + smallint fbc = G.flag_break_continue; + /* We might fall into outer *loop*, + * don't want to break it too */ + if (loop_top) { + G.depth_break_continue--; + if (G.depth_break_continue == 0) + G.flag_break_continue = 0; + /* else: e.g. "continue 2" should *break* once, *then* continue */ + } /* else: "while... do... { we are here (innermost list is not a loop!) };...done" */ + if (G.depth_break_continue != 0 || fbc == BC_BREAK) { checkjobs(NULL, 0 /*(no pid to wait for)*/); break; } - } else if (pi->followup == PIPE_BG) { - /* What does bash do with attempts to background builtins? */ - /* even bash 3.2 doesn't do that well with nested bg: - * try "{ { sleep 10; echo DEEP; } & echo HERE; } &". - * I'm NOT treating inner &'s as jobs */ + /* "continue": simulate end of loop */ + rword = RES_DONE; + continue; + } +#endif + if (G_flag_return_in_progress == 1) { + checkjobs(NULL, 0 /*(no pid to wait for)*/); + break; + } + } else if (pi->followup == PIPE_BG) { + /* What does bash do with attempts to background builtins? */ + /* even bash 3.2 doesn't do that well with nested bg: + * try "{ { sleep 10; echo DEEP; } & echo HERE; } &". + * I'm NOT treating inner &'s as jobs */ #if ENABLE_HUSH_JOB - if (G.run_list_level == 1) - insert_bg_job(pi); + if (G.run_list_level == 1) + insert_bg_job(pi); #endif - /* Last command's pid goes to $! */ - G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid; - debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n"); + /* Last command's pid goes to $! */ + G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid; + debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n"); /* Check pi->pi_inverted? "! sleep 1 & echo $?": bash says 1. dash and ash says 0 */ - G.last_exitcode = rcode = EXIT_SUCCESS; - check_and_run_traps(); - } else { + G.last_exitcode = rcode = EXIT_SUCCESS; + check_and_run_traps(); + } else { #if ENABLE_HUSH_JOB - if (G.run_list_level == 1 && G_interactive_fd) { - /* Waits for completion, then fg's main shell */ - rcode = checkjobs_and_fg_shell(pi); - debug_printf_exec(": checkjobs_and_fg_shell exitcode %d\n", rcode); - } else -#endif - { /* This one just waits for completion */ - rcode = checkjobs(pi, 0 /*(no pid to wait for)*/); - debug_printf_exec(": checkjobs exitcode %d\n", rcode); - } - G.last_exitcode = rcode; - check_and_run_traps(); + if (G.run_list_level == 1 && G_interactive_fd) { + /* Waits for completion, then fg's main shell */ + rcode = checkjobs_and_fg_shell(pi); + debug_printf_exec(": checkjobs_and_fg_shell exitcode %d\n", rcode); + } else +#endif + { /* This one just waits for completion */ + rcode = checkjobs(pi, 0 /*(no pid to wait for)*/); + debug_printf_exec(": checkjobs exitcode %d\n", rcode); } + G.last_exitcode = rcode; + check_and_run_traps(); } /* Analyze how result affects subsequent commands */ -- cgit v1.2.3-55-g6feb From 6c635d62d41477e92f0b30b0f525c7838e64a07d Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 8 Nov 2016 20:26:11 +0100 Subject: hush: small optimization in run_list I thought gcc can detect this itself. It doesn't. function old new delta run_list 1030 1021 -9 Signed-off-by: Denys Vlasenko --- shell/hush.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index a5f059924..5a36a7692 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -8004,20 +8004,21 @@ static int run_list(struct pipe *pi) G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid; debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n"); /* Check pi->pi_inverted? "! sleep 1 & echo $?": bash says 1. dash and ash says 0 */ - G.last_exitcode = rcode = EXIT_SUCCESS; - check_and_run_traps(); + rcode = EXIT_SUCCESS; + goto check_traps; } else { #if ENABLE_HUSH_JOB if (G.run_list_level == 1 && G_interactive_fd) { /* Waits for completion, then fg's main shell */ rcode = checkjobs_and_fg_shell(pi); debug_printf_exec(": checkjobs_and_fg_shell exitcode %d\n", rcode); - } else -#endif - { /* This one just waits for completion */ - rcode = checkjobs(pi, 0 /*(no pid to wait for)*/); - debug_printf_exec(": checkjobs exitcode %d\n", rcode); + goto check_traps; } +#endif + /* This one just waits for completion */ + rcode = checkjobs(pi, 0 /*(no pid to wait for)*/); + debug_printf_exec(": checkjobs exitcode %d\n", rcode); + check_traps: G.last_exitcode = rcode; check_and_run_traps(); } -- cgit v1.2.3-55-g6feb From 00a06b971531031103c25d650e2078c801afbe39 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 8 Nov 2016 20:35:53 +0100 Subject: hush: renumber PIPE_foo, make PIPE_SEQ = 0 PIPE_SEQ is used most often, having it zero makes code smaller: function old new delta done_word 719 707 -12 parse_stream 2546 2531 -15 Signed-off-by: Denys Vlasenko --- shell/hush.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index 5a36a7692..57252a17a 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -588,10 +588,10 @@ struct pipe { IF_HAS_KEYWORDS(smallint res_word;) /* needed for if, for, while, until... */ }; typedef enum pipe_style { - PIPE_SEQ = 1, - PIPE_AND = 2, - PIPE_OR = 3, - PIPE_BG = 4, + PIPE_SEQ = 0, + PIPE_AND = 1, + PIPE_OR = 2, + PIPE_BG = 3, } pipe_style; /* Is there anything in this pipe at all? */ #define IS_NULL_PIPE(pi) \ @@ -3139,7 +3139,6 @@ static struct pipe *new_pipe(void) { struct pipe *pi; pi = xzalloc(sizeof(struct pipe)); - /*pi->followup = 0; - deliberately invalid value */ /*pi->res_word = RES_NONE; - RES_NONE is 0 anyway */ return pi; } -- cgit v1.2.3-55-g6feb From 87e039d0160be16a9a242f74af2e90cdb9f97e12 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 8 Nov 2016 22:35:05 +0100 Subject: hush: make getch/peek functions directly called Indirect calls are more difficult to predict. Unfortunately, on x64 direct call is 5 bytes while indirect "call (reg+ofs)" is 3 bytes: function old new delta i_getch - 82 +82 i_peek - 63 +63 parse_stream 2531 2579 +48 parse_dollar 771 797 +26 parse_redirect 296 321 +25 add_till_closing_bracket 408 420 +12 encode_string 256 265 +9 i_peek_and_eat_bkslash_nl 93 99 +6 add_till_backquote 110 114 +4 parse_and_run_stream 139 141 +2 expand_vars_to_list 1143 1144 +1 static_peek 6 - -6 setup_string_in_str 39 18 -21 setup_file_in_str 40 19 -21 static_get 27 - -27 file_peek 52 - -52 file_get 65 - -65 ------------------------------------------------------------------------------ (add/remove: 2/4 grow/shrink: 9/2 up/down: 278/-192) Total: 86 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 51 ++++++++++++++++++++++++--------------------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index 57252a17a..2f07f4ac1 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -469,11 +469,7 @@ typedef struct in_str { int peek_buf[2]; int last_char; FILE *file; - int (*get) (struct in_str *) FAST_FUNC; - int (*peek) (struct in_str *) FAST_FUNC; } in_str; -#define i_getch(input) ((input)->get(input)) -#define i_peek(input) ((input)->peek(input)) /* The descrip member of this structure is only used to make * debugging output pretty */ @@ -2259,10 +2255,23 @@ static inline int fgetc_interactive(struct in_str *i) } #endif /* INTERACTIVE */ -static int FAST_FUNC file_get(struct in_str *i) +static int i_getch(struct in_str *i) { int ch; + if (!i->file) { + /* string-based in_str */ + ch = (unsigned char)*i->p; + if (ch != '\0') { + i->p++; + i->last_char = ch; + return ch; + } + return EOF; + } + + /* FILE-based in_str */ + #if ENABLE_FEATURE_EDITING /* This can be stdin, check line editing char[] buffer */ if (i->p && *i->p != '\0') { @@ -2288,10 +2297,18 @@ static int FAST_FUNC file_get(struct in_str *i) return ch; } -static int FAST_FUNC file_peek(struct in_str *i) +static int i_peek(struct in_str *i) { int ch; + if (!i->file) { + /* string-based in_str */ + /* Doesn't report EOF on NUL. None of the callers care. */ + return (unsigned char)*i->p; + } + + /* FILE-based in_str */ + #if ENABLE_FEATURE_EDITING && ENABLE_HUSH_INTERACTIVE /* This can be stdin, check line editing char[] buffer */ if (i->p && *i->p != '\0') @@ -2318,23 +2335,6 @@ static int FAST_FUNC file_peek(struct in_str *i) return ch; } -static int FAST_FUNC static_get(struct in_str *i) -{ - int ch = (unsigned char)*i->p; - if (ch != '\0') { - i->p++; - i->last_char = ch; - return ch; - } - return EOF; -} - -static int FAST_FUNC static_peek(struct in_str *i) -{ - /* Doesn't report EOF on NUL. None of the callers care. */ - return (unsigned char)*i->p; -} - /* Only ever called if i_peek() was called, and did not return EOF. * IOW: we know the previous peek saw an ordinary char, not EOF, not NUL, * not end-of-line. Therefore we never need to read a new editing line here. @@ -2370,8 +2370,6 @@ static int i_peek2(struct in_str *i) static void setup_file_in_str(struct in_str *i, FILE *f) { memset(i, 0, sizeof(*i)); - i->get = file_get; - i->peek = file_peek; /* i->promptmode = 0; - PS1 (memset did it) */ i->file = f; /* i->p = NULL; */ @@ -2380,9 +2378,8 @@ static void setup_file_in_str(struct in_str *i, FILE *f) static void setup_string_in_str(struct in_str *i, const char *s) { memset(i, 0, sizeof(*i)); - i->get = static_get; - i->peek = static_peek; /* i->promptmode = 0; - PS1 (memset did it) */ + /*i->file = NULL */; i->p = s; } -- cgit v1.2.3-55-g6feb