X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=shell%2Fhush.c;h=b9e763cc84053717c27934708854f043784f41cb;hb=32f774cd344cf12336c22b0947f102274a99ee31;hp=ae2876ac71cddbe0064538c87d1b6d9e91bf258b;hpb=27c56f12670295286a881bbb87d506f0a5bfd40e;p=platform%2Fupstream%2Fbusybox.git diff --git a/shell/hush.c b/shell/hush.c index ae2876a..b9e763c 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -8,6 +8,8 @@ * Copyright (C) 2000,2001 Larry Doolittle * Copyright (C) 2008,2009 Denys Vlasenko * + * Licensed under GPLv2 or later, see file LICENSE in this source tree. + * * Credits: * The parser routines proper are all original material, first * written Dec 2000 and Jan 2001 by Larry Doolittle. The @@ -50,7 +52,6 @@ * * Bash compat TODO: * redirection of stdout+stderr: &> and >& - * brace expansion: one/{two,three,four} * reserved words: function select * advanced test: [[ ]] * process substitution: <(list) and >(list) @@ -63,7 +64,9 @@ * The EXPR is evaluated according to ARITHMETIC EVALUATION. * This is exactly equivalent to let "EXPR". * $[EXPR]: synonym for $((EXPR)) - * export builtin should be special, its arguments are assignments + * + * Won't do: + * In bash, export builtin is special, its arguments are assignments * and therefore expansion of them should be "one-word" expansion: * $ export i=`echo 'a b'` # export has one arg: "i=a b" * compare with: @@ -77,17 +80,20 @@ * aaa bbb * $ "export" i=`echo 'aaa bbb'`; echo "$i" * aaa - * - * Licensed under GPLv2 or later, see file LICENSE in this source tree. */ -#include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */ -#include /* for malloc_trim */ +#if !(defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) \ + || defined(__APPLE__) \ + ) +# include /* for malloc_trim */ +#endif #include /* #include */ #if ENABLE_HUSH_CASE # include #endif +#include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */ +#include "unicode.h" #include "shell_common.h" #include "math.h" #include "match.h" @@ -100,14 +106,6 @@ # define PIPE_BUF 4096 /* amount of buffering in a pipe */ #endif -//applet:IF_HUSH(APPLET(hush, _BB_DIR_BIN, _BB_SUID_DROP)) -//applet:IF_MSH(APPLET(msh, _BB_DIR_BIN, _BB_SUID_DROP)) -//applet:IF_FEATURE_SH_IS_HUSH(APPLET_ODDNAME(sh, hush, _BB_DIR_BIN, _BB_SUID_DROP, sh)) -//applet:IF_FEATURE_BASH_IS_HUSH(APPLET_ODDNAME(bash, hush, _BB_DIR_BIN, _BB_SUID_DROP, bash)) - -//kbuild:lib-$(CONFIG_HUSH) += hush.o match.o shell_common.o -//kbuild:lib-$(CONFIG_HUSH_RANDOM_SUPPORT) += random.o - //config:config HUSH //config: bool "hush" //config: default y @@ -119,8 +117,8 @@ //config: //config: It will compile and work on no-mmu systems. //config: -//config: It does not handle select, aliases, brace expansion, -//config: tilde expansion, &>file and >&file redirection of stdout+stderr. +//config: It does not handle select, aliases, tilde expansion, +//config: &>file and >&file redirection of stdout+stderr. //config: //config:config HUSH_BASH_COMPAT //config: bool "bash-compatible extensions" @@ -129,6 +127,13 @@ //config: help //config: Enable bash-compatible extensions. //config: +//config:config HUSH_BRACE_EXPANSION +//config: bool "Brace expansion" +//config: default y +//config: depends on HUSH_BASH_COMPAT +//config: help +//config: Enable {abc,def} extension. +//config: //config:config HUSH_HELP //config: bool "help builtin" //config: default y @@ -146,6 +151,13 @@ //config: from stdin just like a shell script from a file. //config: No prompt, no PS1/PS2 magic shell variables. //config: +//config:config HUSH_SAVEHISTORY +//config: bool "Save command history to .hush_history" +//config: default y +//config: depends on HUSH_INTERACTIVE && FEATURE_EDITING_SAVEHISTORY +//config: help +//config: Enable history saving in hush. +//config: //config:config HUSH_JOB //config: bool "Job control" //config: default y @@ -230,10 +242,35 @@ //config: msh is deprecated and will be removed, please migrate to hush. //config: -//usage:#define hush_trivial_usage NOUSAGE_STR -//usage:#define hush_full_usage "" -//usage:#define msh_trivial_usage NOUSAGE_STR -//usage:#define msh_full_usage "" +//applet:IF_HUSH(APPLET(hush, BB_DIR_BIN, BB_SUID_DROP)) +//applet:IF_MSH(APPLET(msh, BB_DIR_BIN, BB_SUID_DROP)) +//applet:IF_FEATURE_SH_IS_HUSH(APPLET_ODDNAME(sh, hush, BB_DIR_BIN, BB_SUID_DROP, sh)) +//applet:IF_FEATURE_BASH_IS_HUSH(APPLET_ODDNAME(bash, hush, BB_DIR_BIN, BB_SUID_DROP, bash)) + +//kbuild:lib-$(CONFIG_HUSH) += hush.o match.o shell_common.o +//kbuild:lib-$(CONFIG_HUSH_RANDOM_SUPPORT) += random.o + +/* -i (interactive) and -s (read stdin) are also accepted, + * but currently do nothing, therefore aren't shown in help. + * NOMMU-specific options are not meant to be used by users, + * therefore we don't show them either. + */ +//usage:#define hush_trivial_usage +//usage: "[-nxl] [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]" +//usage:#define hush_full_usage "\n\n" +//usage: "Unix shell interpreter" + +//usage:#define msh_trivial_usage hush_trivial_usage +//usage:#define msh_full_usage hush_full_usage + +//usage:#if ENABLE_FEATURE_SH_IS_HUSH +//usage:# define sh_trivial_usage hush_trivial_usage +//usage:# define sh_full_usage hush_full_usage +//usage:#endif +//usage:#if ENABLE_FEATURE_BASH_IS_HUSH +//usage:# define bash_trivial_usage hush_trivial_usage +//usage:# define bash_full_usage hush_full_usage +//usage:#endif /* Build knobs */ @@ -283,6 +320,8 @@ # define ENABLE_FEATURE_EDITING 0 # undef ENABLE_FEATURE_EDITING_FANCY_PROMPT # define ENABLE_FEATURE_EDITING_FANCY_PROMPT 0 +# undef ENABLE_FEATURE_EDITING_SAVE_ON_EXIT +# define ENABLE_FEATURE_EDITING_SAVE_ON_EXIT 0 #endif /* Do we support ANY keywords? */ @@ -349,7 +388,7 @@ typedef struct nommu_save_t { } nommu_save_t; #endif -typedef enum reserved_style { +enum { RES_NONE = 0, #if ENABLE_HUSH_IF RES_IF , @@ -378,16 +417,13 @@ typedef enum reserved_style { #endif RES_XXXX , RES_SNTX -} reserved_style; +}; typedef struct o_string { char *data; int length; /* position where data is appended */ int maxlen; - /* Protect newly added chars against globbing - * (by prepending \ to *, ?, [, \) */ - smallint o_escape; - smallint o_glob; + int o_expflags; /* At least some part of the string was inside '' or "", * possibly empty one: word"", wo''rd etc. */ smallint has_quoted_part; @@ -395,25 +431,40 @@ typedef struct o_string { smallint o_assignment; /* 0:maybe, 1:yes, 2:no */ } o_string; enum { - MAYBE_ASSIGNMENT = 0, + EXP_FLAG_SINGLEWORD = 0x80, /* must be 0x80 */ + EXP_FLAG_GLOB = 0x2, + /* Protect newly added chars against globbing + * by prepending \ to *, ?, [, \ */ + EXP_FLAG_ESC_GLOB_CHARS = 0x1, +}; +enum { + MAYBE_ASSIGNMENT = 0, DEFINITELY_ASSIGNMENT = 1, - NOT_ASSIGNMENT = 2, - WORD_IS_KEYWORD = 3, /* not assigment, but next word may be: "if v=xyz cmd;" */ + NOT_ASSIGNMENT = 2, + /* Not an assigment, but next word may be: "if v=xyz cmd;" */ + WORD_IS_KEYWORD = 3, }; /* Used for initialization: o_string foo = NULL_O_STRING; */ #define NULL_O_STRING { NULL } -/* I can almost use ordinary FILE*. Is open_memstream() universally - * available? Where is it documented? */ +#ifndef debug_printf_parse +static const char *const assignment_flag[] = { + "MAYBE_ASSIGNMENT", + "DEFINITELY_ASSIGNMENT", + "NOT_ASSIGNMENT", + "WORD_IS_KEYWORD", +}; +#endif + typedef struct in_str { const char *p; /* eof_flag=1: last char in ->p is really an EOF */ char eof_flag; /* meaningless if ->p == NULL */ char peek_buf[2]; #if ENABLE_HUSH_INTERACTIVE - smallint promptme; smallint promptmode; /* 0: PS1, 1: PS2 */ #endif + int last_char; FILE *file; int (*get) (struct in_str *) FAST_FUNC; int (*peek) (struct in_str *) FAST_FUNC; @@ -471,7 +522,6 @@ typedef enum redir_type { struct command { pid_t pid; /* 0 if exited */ int assignment_cnt; /* how many argv[i] are assignments? */ - smallint is_stopped; /* is the command currently running? */ smallint cmd_type; /* CMD_xxx */ #define CMD_NORMAL 0 #define CMD_SUBSHELL 1 @@ -483,6 +533,7 @@ struct command { # define CMD_FUNCDEF 3 #endif + smalluint cmd_exitcode; /* if non-NULL, this "command" is { list }, ( list ), or a compound statement */ struct pipe *group; #if !BB_MMU @@ -518,7 +569,6 @@ struct command { #define IS_NULL_CMD(cmd) \ (!(cmd)->group && !(cmd)->argv && !(cmd)->redirects) - struct pipe { struct pipe *next; int num_cmds; /* total number of commands in pipe */ @@ -613,6 +663,53 @@ struct function { #endif +/* set -/+o OPT support. (TODO: make it optional) + * bash supports the following opts: + * allexport off + * braceexpand on + * emacs on + * errexit off + * errtrace off + * functrace off + * hashall on + * histexpand off + * history on + * ignoreeof off + * interactive-comments on + * keyword off + * monitor on + * noclobber off + * noexec off + * noglob off + * nolog off + * notify off + * nounset off + * onecmd off + * physical off + * pipefail off + * posix off + * privileged off + * verbose off + * vi off + * xtrace off + */ +static const char o_opt_strings[] ALIGN1 = + "pipefail\0" + "noexec\0" +#if ENABLE_HUSH_MODE_X + "xtrace\0" +#endif + ; +enum { + OPT_O_PIPEFAIL, + OPT_O_NOEXEC, +#if ENABLE_HUSH_MODE_X + OPT_O_XTRACE, +#endif + NUM_OPT_O +}; + + /* "Globals" within this file */ /* Sorted roughly by size (smaller offsets == smaller code) */ struct globals { @@ -655,6 +752,12 @@ struct globals { #else # define G_saved_tty_pgrp 0 #endif + char o_opt[NUM_OPT_O]; +#if ENABLE_HUSH_MODE_X +# define G_x_mode (G.o_opt[OPT_O_XTRACE]) +#else +# define G_x_mode 0 +#endif smallint flag_SIGINT; #if ENABLE_HUSH_LOOPS smallint flag_break_continue; @@ -666,19 +769,11 @@ struct globals { */ smallint flag_return_in_progress; #endif - smallint n_mode; -#if ENABLE_HUSH_MODE_X - smallint x_mode; -# define G_x_mode (G.x_mode) -#else -# define G_x_mode 0 -#endif smallint exiting; /* used to prevent EXIT trap recursion */ /* These four support $?, $#, and $1 */ smalluint last_exitcode; /* are global_argv and global_argv[1..n] malloced? (note: not [0]) */ smalluint global_args_malloced; - smalluint inherited_set_is_saved; /* how many non-NULL argv's we have. NB: $# + 1 */ int global_argc; char **global_argv; @@ -691,8 +786,7 @@ struct globals { #endif const char *ifs; const char *cwd; - struct variable *top_var; /* = &G.shell_ver (set in main()) */ - struct variable shell_ver; + struct variable *top_var; char **expanded_assignments; #if ENABLE_HUSH_FUNCTIONS struct function *top_func; @@ -707,15 +801,27 @@ struct globals { unsigned handled_SIGCHLD; smallint we_have_children; #endif - /* which signals have non-DFL handler (even with no traps set)? */ - unsigned non_DFL_mask; + /* Which signals have non-DFL handler (even with no traps set)? + * Set at the start to: + * (SIGQUIT + maybe SPECIAL_INTERACTIVE_SIGS + maybe SPECIAL_JOBSTOP_SIGS) + * SPECIAL_INTERACTIVE_SIGS are cleared after fork. + * The rest is cleared right before execv syscalls. + * Other than these two times, never modified. + */ + unsigned special_sig_mask; +#if ENABLE_HUSH_JOB + unsigned fatal_sig_mask; +# define G_fatal_sig_mask G.fatal_sig_mask +#else +# define G_fatal_sig_mask 0 +#endif char **traps; /* char *traps[NSIG] */ - sigset_t blocked_set; - sigset_t inherited_set; + sigset_t pending_set; #if HUSH_DEBUG unsigned long memleak_value; int debug_indent; #endif + struct sigaction sa; char user_input_buf[ENABLE_FEATURE_EDITING ? CONFIG_FEATURE_EDITING_MAX_LEN : 2]; }; #define G (*ptr_to_globals) @@ -724,6 +830,9 @@ struct globals { * is global, thus "G." prefix is a useful hint */ #define INIT_G() do { \ SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ + /* memset(&G.sa, 0, sizeof(G.sa)); */ \ + sigfillset(&G.sa.sa_mask); \ + G.sa.sa_flags = SA_RESTART; \ } while (0) @@ -852,7 +961,7 @@ static const struct built_in_command bltins2[] = { */ #if HUSH_DEBUG /* prevent disasters with G.debug_indent < 0 */ -# define indent() fprintf(stderr, "%*s", (G.debug_indent * 2) & 0xff, "") +# define indent() fdprintf(2, "%*s", (G.debug_indent * 2) & 0xff, "") # define debug_enter() (G.debug_indent++) # define debug_leave() (G.debug_indent--) #else @@ -862,56 +971,56 @@ static const struct built_in_command bltins2[] = { #endif #ifndef debug_printf -# define debug_printf(...) (indent(), fprintf(stderr, __VA_ARGS__)) +# define debug_printf(...) (indent(), fdprintf(2, __VA_ARGS__)) #endif #ifndef debug_printf_parse -# define debug_printf_parse(...) (indent(), fprintf(stderr, __VA_ARGS__)) +# define debug_printf_parse(...) (indent(), fdprintf(2, __VA_ARGS__)) #endif #ifndef debug_printf_exec -#define debug_printf_exec(...) (indent(), fprintf(stderr, __VA_ARGS__)) +#define debug_printf_exec(...) (indent(), fdprintf(2, __VA_ARGS__)) #endif #ifndef debug_printf_env -# define debug_printf_env(...) (indent(), fprintf(stderr, __VA_ARGS__)) +# define debug_printf_env(...) (indent(), fdprintf(2, __VA_ARGS__)) #endif #ifndef debug_printf_jobs -# define debug_printf_jobs(...) (indent(), fprintf(stderr, __VA_ARGS__)) +# define debug_printf_jobs(...) (indent(), fdprintf(2, __VA_ARGS__)) # define DEBUG_JOBS 1 #else # define DEBUG_JOBS 0 #endif #ifndef debug_printf_expand -# define debug_printf_expand(...) (indent(), fprintf(stderr, __VA_ARGS__)) +# define debug_printf_expand(...) (indent(), fdprintf(2, __VA_ARGS__)) # define DEBUG_EXPAND 1 #else # define DEBUG_EXPAND 0 #endif #ifndef debug_printf_varexp -# define debug_printf_varexp(...) (indent(), fprintf(stderr, __VA_ARGS__)) +# define debug_printf_varexp(...) (indent(), fdprintf(2, __VA_ARGS__)) #endif #ifndef debug_printf_glob -# define debug_printf_glob(...) (indent(), fprintf(stderr, __VA_ARGS__)) +# define debug_printf_glob(...) (indent(), fdprintf(2, __VA_ARGS__)) # define DEBUG_GLOB 1 #else # define DEBUG_GLOB 0 #endif #ifndef debug_printf_list -# define debug_printf_list(...) (indent(), fprintf(stderr, __VA_ARGS__)) +# define debug_printf_list(...) (indent(), fdprintf(2, __VA_ARGS__)) #endif #ifndef debug_printf_subst -# define debug_printf_subst(...) (indent(), fprintf(stderr, __VA_ARGS__)) +# define debug_printf_subst(...) (indent(), fdprintf(2, __VA_ARGS__)) #endif #ifndef debug_printf_clean -# define debug_printf_clean(...) (indent(), fprintf(stderr, __VA_ARGS__)) +# define debug_printf_clean(...) (indent(), fdprintf(2, __VA_ARGS__)) # define DEBUG_CLEAN 1 #else # define DEBUG_CLEAN 0 @@ -921,9 +1030,9 @@ static const struct built_in_command bltins2[] = { static void debug_print_strings(const char *prefix, char **vv) { indent(); - fprintf(stderr, "%s:\n", prefix); + fdprintf(2, "%s:\n", prefix); while (*vv) - fprintf(stderr, " '%s'\n", *vv++); + fdprintf(2, " '%s'\n", *vv++); } #else # define debug_print_strings(prefix, vv) ((void)0) @@ -991,43 +1100,36 @@ static void die_if_script(unsigned lineno, const char *fmt, ...) xfunc_die(); } -static void syntax_error(unsigned lineno, const char *msg) +static void syntax_error(unsigned lineno UNUSED_PARAM, const char *msg) { if (msg) - die_if_script(lineno, "syntax error: %s", msg); + bb_error_msg("syntax error: %s", msg); else - die_if_script(lineno, "syntax error", NULL); + bb_error_msg("syntax error"); } -static void syntax_error_at(unsigned lineno, const char *msg) +static void syntax_error_at(unsigned lineno UNUSED_PARAM, const char *msg) { - die_if_script(lineno, "syntax error at '%s'", msg); + bb_error_msg("syntax error at '%s'", msg); } -static void syntax_error_unterm_str(unsigned lineno, const char *s) +static void syntax_error_unterm_str(unsigned lineno UNUSED_PARAM, const char *s) { - die_if_script(lineno, "syntax error: unterminated %s", s); + bb_error_msg("syntax error: unterminated %s", s); } -/* It so happens that all such cases are totally fatal - * even if shell is interactive: EOF while looking for closing - * delimiter. There is nowhere to read stuff from after that, - * it's EOF! The only choice is to terminate. - */ -static void syntax_error_unterm_ch(unsigned lineno, char ch) NORETURN; static void syntax_error_unterm_ch(unsigned lineno, char ch) { char msg[2] = { ch, '\0' }; syntax_error_unterm_str(lineno, msg); - xfunc_die(); } -static void syntax_error_unexpected_ch(unsigned lineno, int ch) +static void syntax_error_unexpected_ch(unsigned lineno UNUSED_PARAM, int ch) { char msg[2]; msg[0] = ch; msg[1] = '\0'; - die_if_script(lineno, "syntax error: unexpected %s", ch == EOF ? "EOF" : msg); + bb_error_msg("syntax error: unexpected %s", ch == EOF ? "EOF" : msg); } #if HUSH_DEBUG < 2 @@ -1241,12 +1343,14 @@ static void restore_G_args(save_arg_t *sv, char **argv) * "echo $$; sleep 5 & wait; ls -l" + "kill -INT " * Example 3: this does not wait 5 sec, but executes ls: * "sleep 5; ls -l" + press ^C + * Example 4: this does not wait and does not execute ls: + * "sleep 5 & wait; ls -l" + press ^C * * (What happens to signals which are IGN on shell start?) * (What happens with signal mask on shell start?) * - * Implementation in hush - * ====================== + * Old implementation + * ================== * We use in-kernel pending signal mask to determine which signals were sent. * We block all signals which we don't want to take action immediately, * i.e. we block all signals which need to have special handling as described @@ -1254,11 +1358,11 @@ static void restore_G_args(save_arg_t *sv, char **argv) * After each pipe execution, we extract any pending signals via sigtimedwait() * and act on them. * - * unsigned non_DFL_mask: a mask of such "special" signals + * unsigned special_sig_mask: a mask of such "special" signals * sigset_t blocked_set: current blocked signal set * * "trap - SIGxxx": - * clear bit in blocked_set unless it is also in non_DFL_mask + * clear bit in blocked_set unless it is also in special_sig_mask * "trap 'cmd' SIGxxx": * set bit in blocked_set (even if 'cmd' is '') * after [v]fork, if we plan to be a shell: @@ -1277,6 +1381,49 @@ static void restore_G_args(save_arg_t *sv, char **argv) * Standard says "When a subshell is entered, traps that are not being ignored * are set to the default actions". bash interprets it so that traps which * are set to '' (ignore) are NOT reset to defaults. We do the same. + * + * Problem: the above approach makes it unwieldy to catch signals while + * we are in read builtin, of while we read commands from stdin: + * masked signals are not visible! + * + * New implementation + * ================== + * We record each signal we are interested in by installing signal handler + * for them - a bit like emulating kernel pending signal mask in userspace. + * We are interested in: signals which need to have special handling + * as described above, and all signals which have traps set. + * Signals are rocorded in pending_set. + * After each pipe execution, we extract any pending signals + * and act on them. + * + * unsigned special_sig_mask: a mask of shell-special signals. + * unsigned fatal_sig_mask: a mask of signals on which we restore tty pgrp. + * char *traps[sig] if trap for sig is set (even if it's ''). + * sigset_t pending_set: set of sigs we received. + * + * "trap - SIGxxx": + * if sig is in special_sig_mask, set handler back to: + * record_pending_signo, or to IGN if it's a tty stop signal + * if sig is in fatal_sig_mask, set handler back to sigexit. + * else: set handler back to SIG_DFL + * "trap 'cmd' SIGxxx": + * set handler to record_pending_signo. + * "trap '' SIGxxx": + * set handler to SIG_IGN. + * after [v]fork, if we plan to be a shell: + * set signals with special interactive handling to SIG_DFL + * (because child shell is not interactive), + * unset all traps except '' (note: regardless of child shell's type - {}, (), etc) + * after [v]fork, if we plan to exec: + * POSIX says fork clears pending signal mask in child - no need to clear it. + * + * To make wait builtin interruptible, we handle SIGCHLD as special signal, + * otherwise (if we leave it SIG_DFL) sigsuspend in wait builtin will not wake up on it. + * + * Note (compat): + * Standard says "When a subshell is entered, traps that are not being ignored + * are set to the default actions". bash interprets it so that traps which + * are set to '' (ignore) are NOT reset to defaults. We do the same. */ enum { SPECIAL_INTERACTIVE_SIGS = 0 @@ -1284,21 +1431,43 @@ enum { | (1 << SIGINT) | (1 << SIGHUP) , - SPECIAL_JOB_SIGS = 0 + SPECIAL_JOBSTOP_SIGS = 0 #if ENABLE_HUSH_JOB | (1 << SIGTTIN) | (1 << SIGTTOU) | (1 << SIGTSTP) #endif + , }; -#if ENABLE_HUSH_FAST -static void SIGCHLD_handler(int sig UNUSED_PARAM) +static void record_pending_signo(int sig) { - G.count_SIGCHLD++; + sigaddset(&G.pending_set, sig); +#if ENABLE_HUSH_FAST + if (sig == SIGCHLD) { + G.count_SIGCHLD++; //bb_error_msg("[%d] SIGCHLD_handler: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); -} + } #endif +} + +static sighandler_t install_sighandler(int sig, sighandler_t handler) +{ + struct sigaction old_sa; + + /* We could use signal() to install handlers... almost: + * except that we need to mask ALL signals while handlers run. + * I saw signal nesting in strace, race window isn't small. + * SA_RESTART is also needed, but in Linux, signal() + * sets SA_RESTART too. + */ + /* memset(&G.sa, 0, sizeof(G.sa)); - already done */ + /* sigfillset(&G.sa.sa_mask); - already done */ + /* G.sa.sa_flags = SA_RESTART; - already done */ + G.sa.sa_handler = handler; + sigaction(sig, &G.sa, &old_sa); + return old_sa.sa_handler; +} #if ENABLE_HUSH_JOB @@ -1315,13 +1484,15 @@ static void SIGCHLD_handler(int sig UNUSED_PARAM) static void sigexit(int sig) NORETURN; static void sigexit(int sig) { - /* Disable all signals: job control, SIGPIPE, etc. */ - sigprocmask_allsigs(SIG_BLOCK); - /* Careful: we can end up here after [v]fork. Do not restore * tty pgrp then, only top-level shell process does that */ - if (G_saved_tty_pgrp && getpid() == G.root_pid) + if (G_saved_tty_pgrp && getpid() == G.root_pid) { + /* Disable all signals: job control, SIGPIPE, etc. + * Mostly paranoid measure, to prevent infinite SIGTTOU. + */ + sigprocmask_allsigs(SIG_BLOCK); tcsetpgrp(G_interactive_fd, G_saved_tty_pgrp); + } /* Not a signal, just exit */ if (sig <= 0) @@ -1336,24 +1507,70 @@ static void sigexit(int sig) #endif +static sighandler_t pick_sighandler(unsigned sig) +{ + sighandler_t handler = SIG_DFL; + if (sig < sizeof(unsigned)*8) { + unsigned sigmask = (1 << sig); + +#if ENABLE_HUSH_JOB + /* is sig fatal? */ + if (G_fatal_sig_mask & sigmask) + handler = sigexit; + else +#endif + /* sig has special handling? */ + if (G.special_sig_mask & sigmask) { + handler = record_pending_signo; + /* TTIN/TTOU/TSTP can't be set to record_pending_signo + * in order to ignore them: they will be raised + * in an endless loop when we try to do some + * terminal ioctls! We do have to _ignore_ these. + */ + if (SPECIAL_JOBSTOP_SIGS & sigmask) + handler = SIG_IGN; + } + } + return handler; +} + /* Restores tty foreground process group, and exits. */ static void hush_exit(int exitcode) NORETURN; static void hush_exit(int exitcode) { +#if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT + save_history(G.line_input_state); +#endif + + fflush_all(); if (G.exiting <= 0 && G.traps && G.traps[0] && G.traps[0][0]) { - /* Prevent recursion: - * trap "echo Hi; exit" EXIT; exit - */ char *argv[3]; /* argv[0] is unused */ argv[1] = G.traps[0]; argv[2] = NULL; - G.traps[0] = NULL; - G.exiting = 1; + G.exiting = 1; /* prevent EXIT trap recursion */ + /* Note: G.traps[0] is not cleared! + * "trap" will still show it, if executed + * in the handler */ builtin_eval(argv); - /* free(argv[1]); - why bother */ } +#if ENABLE_FEATURE_CLEAN_UP + { + struct variable *cur_var; + if (G.cwd != bb_msg_unknown) + free((char*)G.cwd); + cur_var = G.top_var; + while (cur_var) { + struct variable *tmp = cur_var; + if (!cur_var->max_len) + free(cur_var->varstr); + cur_var = cur_var->next; + free(tmp); + } + } +#endif + #if ENABLE_HUSH_JOB fflush_all(); sigexit(- (exitcode & 0xff)); @@ -1362,23 +1579,31 @@ static void hush_exit(int exitcode) #endif } -static int check_and_run_traps(int sig) + +//TODO: return a mask of ALL handled sigs? +static int check_and_run_traps(void) { - static const struct timespec zero_timespec; - smalluint save_rcode; int last_sig = 0; - if (sig) - goto jump_in; while (1) { - sig = sigtimedwait(&G.blocked_set, NULL, &zero_timespec); - if (sig <= 0) + int sig; + + if (sigisemptyset(&G.pending_set)) break; - jump_in: - last_sig = sig; + sig = 0; + do { + sig++; + if (sigismember(&G.pending_set, sig)) { + sigdelset(&G.pending_set, sig); + goto got_sig; + } + } while (sig < NSIG); + break; + got_sig: if (G.traps && G.traps[sig]) { if (G.traps[sig][0]) { /* We have user-defined handler */ + smalluint save_rcode; char *argv[3]; /* argv[0] is unused */ argv[1] = G.traps[sig]; @@ -1386,21 +1611,17 @@ static int check_and_run_traps(int sig) save_rcode = G.last_exitcode; builtin_eval(argv); G.last_exitcode = save_rcode; + last_sig = sig; } /* else: "" trap, ignoring signal */ continue; } /* not a trap: special action */ switch (sig) { -#if ENABLE_HUSH_FAST - case SIGCHLD: - G.count_SIGCHLD++; -//bb_error_msg("[%d] check_and_run_traps: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); - break; -#endif case SIGINT: /* Builtin was ^C'ed, make it look prettier: */ bb_putchar('\n'); G.flag_SIGINT = 1; + last_sig = sig; break; #if ENABLE_HUSH_JOB case SIGHUP: { @@ -1417,8 +1638,23 @@ static int check_and_run_traps(int sig) sigexit(SIGHUP); } #endif +#if ENABLE_HUSH_FAST + case SIGCHLD: + G.count_SIGCHLD++; +//bb_error_msg("[%d] check_and_run_traps: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); + /* Note: + * We dont do 'last_sig = sig' here -> NOT returning this sig. + * This simplifies wait builtin a bit. + */ + break; +#endif default: /* ignored: */ /* SIGTERM, SIGQUIT, SIGTTIN, SIGTTOU, SIGTSTP */ + /* Note: + * We dont do 'last_sig = sig' here -> NOT returning this sig. + * Example: wait is not interrupted by TERM + * in interactive shell, because TERM is ignored. + */ break; } } @@ -1671,24 +1907,6 @@ static void unset_vars(char **strings) free(strings); } -#if ENABLE_SH_MATH_SUPPORT -# define is_name(c) ((c) == '_' || isalpha((unsigned char)(c))) -# define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c))) -static char* FAST_FUNC endofname(const char *name) -{ - char *p; - - p = (char *) name; - if (!is_name(*p)) - return p; - while (*++p) { - if (!is_in_name(*p)) - break; - } - return p; -} -#endif - static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val) { char *var = xasprintf("%s=%s", name, val); @@ -1758,6 +1976,7 @@ static int FAST_FUNC static_get(struct in_str *i) int ch = *i->p; if (ch != '\0') { i->p++; + i->last_char = ch; return ch; } return EOF; @@ -1815,12 +2034,18 @@ static void get_user_input(struct in_str *i) /* Enable command line editing only while a command line * is actually being read */ do { + /* Unicode support should be activated even if LANG is set + * _during_ shell execution, not only if it was set when + * shell was started. Therefore, re-check LANG every time: + */ + reinit_unicode(get_local_var_value("LANG")); + G.flag_SIGINT = 0; /* buglet: SIGINT will not make new prompt to appear _at once_, * only after . (^C will work) */ - r = read_line_input(prompt_str, G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1, G.line_input_state); + r = read_line_input(G.line_input_state, prompt_str, G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1, /*timeout*/ -1); /* catch *SIGINT* etc (^C is handled by read_line_input) */ - check_and_run_traps(0); + check_and_run_traps(); } while (r == 0 || G.flag_SIGINT); /* repeat if ^C or SIGINT */ i->eof_flag = (r < 0); if (i->eof_flag) { /* EOF/error detected */ @@ -1830,11 +2055,18 @@ static void get_user_input(struct in_str *i) # else do { G.flag_SIGINT = 0; - fputs(prompt_str, stdout); + if (i->last_char == '\0' || i->last_char == '\n') { + /* Why check_and_run_traps here? Try this interactively: + * $ trap 'echo INT' INT; (sleep 2; kill -INT $$) & + * $ <[enter], repeatedly...> + * Without check_and_run_traps, handler never runs. + */ + check_and_run_traps(); + fputs(prompt_str, stdout); + } fflush_all(); G.user_input_buf[0] = r = fgetc(i->file); /*G.user_input_buf[1] = '\0'; - already is and never changed */ -//do we need check_and_run_traps(0)? (maybe only if stdin) } while (G.flag_SIGINT); i->eof_flag = (r == EOF); # endif @@ -1862,22 +2094,18 @@ static int FAST_FUNC file_get(struct in_str *i) /* need to double check i->file because we might be doing something * more complicated by now, like sourcing or substituting. */ #if ENABLE_HUSH_INTERACTIVE - if (G_interactive_fd && i->promptme && i->file == stdin) { + if (G_interactive_fd && i->file == stdin) { do { get_user_input(i); } while (!*i->p); /* need non-empty line */ i->promptmode = 1; /* PS2 */ - i->promptme = 0; goto take_cached; } #endif do ch = fgetc(i->file); while (ch == '\0'); } debug_printf("file_get: got '%c' %d\n", ch, ch); -#if ENABLE_HUSH_INTERACTIVE - if (ch == '\n') - i->promptme = 1; -#endif + i->last_char = ch; return ch; } @@ -1904,26 +2132,22 @@ static int FAST_FUNC file_peek(struct in_str *i) static void setup_file_in_str(struct in_str *i, FILE *f) { + memset(i, 0, sizeof(*i)); i->peek = file_peek; i->get = file_get; -#if ENABLE_HUSH_INTERACTIVE - i->promptme = 1; - i->promptmode = 0; /* PS1 */ -#endif + /* i->promptmode = 0; - PS1 (memset did it) */ i->file = f; - i->p = NULL; + /* i->p = NULL; */ } static void setup_string_in_str(struct in_str *i, const char *s) { + memset(i, 0, sizeof(*i)); i->peek = static_peek; i->get = static_get; -#if ENABLE_HUSH_INTERACTIVE - i->promptme = 1; - i->promptmode = 0; /* PS1 */ -#endif + /* i->promptmode = 0; - PS1 (memset did it) */ i->p = s; - i->eof_flag = 0; + /* i->eof_flag = 0; */ } @@ -1996,26 +2220,8 @@ static void o_addstr_with_NUL(o_string *o, const char *str) o_addblock(o, str, strlen(str) + 1); } -static void o_addblock_duplicate_backslash(o_string *o, const char *str, int len) -{ - while (len) { - len--; - o_addchr(o, *str); - if (*str++ == '\\') { - /* \z -> \\\z; \ -> \\ */ - o_addchr(o, '\\'); - if (len) { - len--; - o_addchr(o, '\\'); - o_addchr(o, *str++); - } - } - } -} - -#undef HUSH_BRACE_EXP /* - * HUSH_BRACE_EXP code needs corresponding quoting on variable expansion side. + * HUSH_BRACE_EXPANSION code needs corresponding quoting on variable expansion side. * Currently, "v='{q,w}'; echo $v" erroneously expands braces in $v. * Apparently, on unquoted $v bash still does globbing * ("v='*.txt'; echo $v" prints all .txt files), @@ -2025,7 +2231,7 @@ static void o_addblock_duplicate_backslash(o_string *o, const char *str, int len * We have only second one. */ -#ifdef HUSH_BRACE_EXP +#if ENABLE_HUSH_BRACE_EXPANSION # define MAYBE_BRACES "{}" #else # define MAYBE_BRACES "" @@ -2053,7 +2259,9 @@ static void o_addqchr(o_string *o, int ch) static void o_addQchr(o_string *o, int ch) { int sz = 1; - if (o->o_escape && strchr("*?[\\" MAYBE_BRACES, ch)) { + if ((o->o_expflags & EXP_FLAG_ESC_GLOB_CHARS) + && strchr("*?[\\" MAYBE_BRACES, ch) + ) { sz++; o->data[o->length] = '\\'; o->length++; @@ -2074,7 +2282,7 @@ static void o_addqblock(o_string *o, const char *str, int len) ordinary_cnt = len; o_addblock(o, str, ordinary_cnt); if (ordinary_cnt == len) - return; + return; /* NUL is already added by o_addblock */ str += ordinary_cnt; len -= ordinary_cnt + 1; /* we are processing + 1 char below */ @@ -2088,13 +2296,13 @@ static void o_addqblock(o_string *o, const char *str, int len) o_grow_by(o, sz); o->data[o->length] = ch; o->length++; - o->data[o->length] = '\0'; } + o->data[o->length] = '\0'; } static void o_addQblock(o_string *o, const char *str, int len) { - if (!o->o_escape) { + if (!(o->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)) { o_addblock(o, str, len); return; } @@ -2124,19 +2332,22 @@ static void debug_print_list(const char *prefix, o_string *o, int n) int i = 0; indent(); - fprintf(stderr, "%s: list:%p n:%d string_start:%d length:%d maxlen:%d glob:%d quoted:%d escape:%d\n", - prefix, list, n, string_start, o->length, o->maxlen, o->o_glob, o->has_quoted_part, o->o_escape); + fdprintf(2, "%s: list:%p n:%d string_start:%d length:%d maxlen:%d glob:%d quoted:%d escape:%d\n", + prefix, list, n, string_start, o->length, o->maxlen, + !!(o->o_expflags & EXP_FLAG_GLOB), + o->has_quoted_part, + !!(o->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); while (i < n) { indent(); - fprintf(stderr, " list[%d]=%d '%s' %p\n", i, (int)list[i], - o->data + (int)list[i] + string_start, - o->data + (int)list[i] + string_start); + fdprintf(2, " list[%d]=%d '%s' %p\n", i, (int)(uintptr_t)list[i], + o->data + (int)(uintptr_t)list[i] + string_start, + o->data + (int)(uintptr_t)list[i] + string_start); i++; } if (n) { - const char *p = o->data + (int)list[n - 1] + string_start; + const char *p = o->data + (int)(uintptr_t)list[n - 1] + string_start; indent(); - fprintf(stderr, " total_sz:%ld\n", (long)((p + strlen(p) + 1) - o->data)); + fdprintf(2, " total_sz:%ld\n", (long)((p + strlen(p) + 1) - o->data)); } } #else @@ -2175,6 +2386,7 @@ static int o_save_ptr_helper(o_string *o, int n) n, string_len, string_start); o->has_empty_slot = 0; } + o->has_quoted_part = 0; list[n] = (char*)(uintptr_t)string_len; return n + 1; } @@ -2188,7 +2400,7 @@ static int o_get_last_ptr(o_string *o, int n) return ((int)(uintptr_t)list[n-1]) + string_start; } -#ifdef HUSH_BRACE_EXP +#if ENABLE_HUSH_BRACE_EXPANSION /* There in a GNU extension, GLOB_BRACE, but it is not usable: * first, it processes even {a} (no commas), second, * I didn't manage to make it return strings when they don't match @@ -2384,7 +2596,7 @@ static int perform_glob(o_string *o, int n) return n; } -#else /* !HUSH_BRACE_EXP */ +#else /* !HUSH_BRACE_EXPANSION */ /* Helper */ static int glob_needed(const char *s) @@ -2461,13 +2673,13 @@ static int perform_glob(o_string *o, int n) return n; } -#endif /* !HUSH_BRACE_EXP */ +#endif /* !HUSH_BRACE_EXPANSION */ -/* If o->o_glob == 1, glob the string so far remembered. +/* If o->o_expflags & EXP_FLAG_GLOB, glob the string so far remembered. * Otherwise, just finish current list[] and start new */ static int o_save_ptr(o_string *o, int n) { - if (o->o_glob) { /* if globbing is requested */ + if (o->o_expflags & EXP_FLAG_GLOB) { /* If o->has_empty_slot, list[n] was already globbed * (if it was requested back then when it was filled) * so don't do that again! */ @@ -2584,6 +2796,94 @@ static void free_pipe_list(struct pipe *pi) /*** Parsing routines ***/ +#ifndef debug_print_tree +static void debug_print_tree(struct pipe *pi, int lvl) +{ + static const char *const PIPE[] = { + [PIPE_SEQ] = "SEQ", + [PIPE_AND] = "AND", + [PIPE_OR ] = "OR" , + [PIPE_BG ] = "BG" , + }; + static const char *RES[] = { + [RES_NONE ] = "NONE" , +# if ENABLE_HUSH_IF + [RES_IF ] = "IF" , + [RES_THEN ] = "THEN" , + [RES_ELIF ] = "ELIF" , + [RES_ELSE ] = "ELSE" , + [RES_FI ] = "FI" , +# endif +# if ENABLE_HUSH_LOOPS + [RES_FOR ] = "FOR" , + [RES_WHILE] = "WHILE", + [RES_UNTIL] = "UNTIL", + [RES_DO ] = "DO" , + [RES_DONE ] = "DONE" , +# endif +# if ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE + [RES_IN ] = "IN" , +# endif +# if ENABLE_HUSH_CASE + [RES_CASE ] = "CASE" , + [RES_CASE_IN ] = "CASE_IN" , + [RES_MATCH] = "MATCH", + [RES_CASE_BODY] = "CASE_BODY", + [RES_ESAC ] = "ESAC" , +# endif + [RES_XXXX ] = "XXXX" , + [RES_SNTX ] = "SNTX" , + }; + static const char *const CMDTYPE[] = { + "{}", + "()", + "[noglob]", +# if ENABLE_HUSH_FUNCTIONS + "func()", +# endif + }; + + int pin, prn; + + pin = 0; + while (pi) { + fdprintf(2, "%*spipe %d res_word=%s followup=%d %s\n", lvl*2, "", + pin, RES[pi->res_word], pi->followup, PIPE[pi->followup]); + prn = 0; + while (prn < pi->num_cmds) { + struct command *command = &pi->cmds[prn]; + char **argv = command->argv; + + fdprintf(2, "%*s cmd %d assignment_cnt:%d", + lvl*2, "", prn, + command->assignment_cnt); + if (command->group) { + fdprintf(2, " group %s: (argv=%p)%s%s\n", + CMDTYPE[command->cmd_type], + argv +# if !BB_MMU + , " group_as_string:", command->group_as_string +# else + , "", "" +# endif + ); + debug_print_tree(command->group, lvl+1); + prn++; + continue; + } + if (argv) while (*argv) { + fdprintf(2, " '%s'", *argv); + argv++; + } + fdprintf(2, "\n"); + prn++; + } + pi = pi->next; + pin++; + } +} +#endif /* debug_print_tree */ + static struct pipe *new_pipe(void) { struct pipe *pi; @@ -2744,29 +3044,29 @@ static const struct reserved_combo* match_reserved_word(o_string *word) */ static const struct reserved_combo reserved_list[] = { # if ENABLE_HUSH_IF - { "!", RES_NONE, NOT_ASSIGNMENT , 0 }, - { "if", RES_IF, WORD_IS_KEYWORD, FLAG_THEN | FLAG_START }, - { "then", RES_THEN, WORD_IS_KEYWORD, FLAG_ELIF | FLAG_ELSE | FLAG_FI }, - { "elif", RES_ELIF, WORD_IS_KEYWORD, FLAG_THEN }, - { "else", RES_ELSE, WORD_IS_KEYWORD, FLAG_FI }, - { "fi", RES_FI, NOT_ASSIGNMENT , FLAG_END }, + { "!", RES_NONE, NOT_ASSIGNMENT , 0 }, + { "if", RES_IF, MAYBE_ASSIGNMENT, FLAG_THEN | FLAG_START }, + { "then", RES_THEN, MAYBE_ASSIGNMENT, FLAG_ELIF | FLAG_ELSE | FLAG_FI }, + { "elif", RES_ELIF, MAYBE_ASSIGNMENT, FLAG_THEN }, + { "else", RES_ELSE, MAYBE_ASSIGNMENT, FLAG_FI }, + { "fi", RES_FI, NOT_ASSIGNMENT , FLAG_END }, # endif # if ENABLE_HUSH_LOOPS - { "for", RES_FOR, NOT_ASSIGNMENT , FLAG_IN | FLAG_DO | FLAG_START }, - { "while", RES_WHILE, WORD_IS_KEYWORD, FLAG_DO | FLAG_START }, - { "until", RES_UNTIL, WORD_IS_KEYWORD, FLAG_DO | FLAG_START }, - { "in", RES_IN, NOT_ASSIGNMENT , FLAG_DO }, - { "do", RES_DO, WORD_IS_KEYWORD, FLAG_DONE }, - { "done", RES_DONE, NOT_ASSIGNMENT , FLAG_END }, + { "for", RES_FOR, NOT_ASSIGNMENT , FLAG_IN | FLAG_DO | FLAG_START }, + { "while", RES_WHILE, MAYBE_ASSIGNMENT, FLAG_DO | FLAG_START }, + { "until", RES_UNTIL, MAYBE_ASSIGNMENT, FLAG_DO | FLAG_START }, + { "in", RES_IN, NOT_ASSIGNMENT , FLAG_DO }, + { "do", RES_DO, MAYBE_ASSIGNMENT, FLAG_DONE }, + { "done", RES_DONE, NOT_ASSIGNMENT , FLAG_END }, # endif # if ENABLE_HUSH_CASE - { "case", RES_CASE, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_START }, - { "esac", RES_ESAC, NOT_ASSIGNMENT , FLAG_END }, + { "case", RES_CASE, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_START }, + { "esac", RES_ESAC, NOT_ASSIGNMENT , FLAG_END }, # endif }; const struct reserved_combo *r; - for (r = reserved_list; r < reserved_list + ARRAY_SIZE(reserved_list); r++) { + for (r = reserved_list; r < reserved_list + ARRAY_SIZE(reserved_list); r++) { if (strcmp(word->data, r->literal) == 0) return r; } @@ -2827,6 +3127,7 @@ static int reserved_word(o_string *word, struct parse_context *ctx) ctx->ctx_res_w = r->res; ctx->old_flag = r->flag; word->o_assignment = r->assignment_flag; + debug_printf_parse("word->o_assignment='%s'\n", assignment_flag[word->o_assignment]); if (ctx->old_flag & FLAG_END) { struct parse_context *old; @@ -2893,18 +3194,6 @@ static int done_word(o_string *word, struct parse_context *ctx) debug_printf_parse("word stored in rd_filename: '%s'\n", word->data); ctx->pending_redirect = NULL; } else { - /* If this word wasn't an assignment, next ones definitely - * can't be assignments. Even if they look like ones. */ - if (word->o_assignment != DEFINITELY_ASSIGNMENT - && word->o_assignment != WORD_IS_KEYWORD - ) { - word->o_assignment = NOT_ASSIGNMENT; - } else { - if (word->o_assignment == DEFINITELY_ASSIGNMENT) - command->assignment_cnt++; - word->o_assignment = MAYBE_ASSIGNMENT; - } - #if HAS_KEYWORDS # if ENABLE_HUSH_CASE if (ctx->ctx_dsemicolon @@ -2924,8 +3213,9 @@ static int done_word(o_string *word, struct parse_context *ctx) && ctx->ctx_res_w != RES_CASE # endif ) { - debug_printf_parse("checking '%s' for reserved-ness\n", word->data); - if (reserved_word(word, ctx)) { + int reserved = reserved_word(word, ctx); + debug_printf_parse("checking for reserved-ness: %d\n", reserved); + if (reserved) { o_reset_to_empty_unquoted(word); debug_printf_parse("done_word return %d\n", (ctx->ctx_res_w == RES_SNTX)); @@ -2946,6 +3236,23 @@ static int done_word(o_string *word, struct parse_context *ctx) "groups and arglists don't mix\n"); return 1; } + + /* If this word wasn't an assignment, next ones definitely + * can't be assignments. Even if they look like ones. */ + if (word->o_assignment != DEFINITELY_ASSIGNMENT + && word->o_assignment != WORD_IS_KEYWORD + ) { + word->o_assignment = NOT_ASSIGNMENT; + } else { + if (word->o_assignment == DEFINITELY_ASSIGNMENT) { + command->assignment_cnt++; + debug_printf_parse("++assignment_cnt=%d\n", command->assignment_cnt); + } + debug_printf_parse("word->o_assignment was:'%s'\n", assignment_flag[word->o_assignment]); + word->o_assignment = MAYBE_ASSIGNMENT; + } + debug_printf_parse("word->o_assignment='%s'\n", assignment_flag[word->o_assignment]); + if (word->has_quoted_part /* optimization: and if it's ("" or '') or ($v... or `cmd`...): */ && (word->data[0] == '\0' || word->data[0] == SPECIAL_VAR_SYMBOL) @@ -2959,14 +3266,6 @@ static int done_word(o_string *word, struct parse_context *ctx) ) { p += 3; } - if (p == word->data || p[0] != '\0') { - /* saw no "$@", or not only "$@" but some - * real text is there too */ - /* insert "empty variable" reference, this makes - * e.g. "", $empty"" etc to not disappear */ - o_addchr(word, SPECIAL_VAR_SYMBOL); - o_addchr(word, SPECIAL_VAR_SYMBOL); - } } command->argv = add_string_to_strings(command->argv, xstrdup(word->data)); debug_print_strings("word appended to argv", command->argv); @@ -3159,16 +3458,17 @@ static char *fetch_till_str(o_string *as_string, int heredoc_flags) { o_string heredoc = NULL_O_STRING; - int past_EOL = 0; + unsigned past_EOL; int prev = 0; /* not \ */ int ch; goto jump_in; + while (1) { ch = i_getch(input); - nommu_addchr(as_string, ch); - if (ch == '\n' - /* TODO: or EOF? (heredoc delimiter may end with , not only ) */ + if (ch != EOF) + nommu_addchr(as_string, ch); + if ((ch == '\n' || ch == EOF) && ((heredoc_flags & HEREDOC_QUOTED) || prev != '\\') ) { if (strcmp(heredoc.data + past_EOL, word) == 0) { @@ -3176,28 +3476,29 @@ static char *fetch_till_str(o_string *as_string, debug_printf_parse("parsed heredoc '%s'\n", heredoc.data); return heredoc.data; } - do { - o_addchr(&heredoc, '\n'); - prev = 0; /* not \ */ - past_EOL = heredoc.length; + while (ch == '\n') { + o_addchr(&heredoc, ch); + prev = ch; jump_in: + past_EOL = heredoc.length; do { ch = i_getch(input); - nommu_addchr(as_string, ch); + if (ch != EOF) + nommu_addchr(as_string, ch); } while ((heredoc_flags & HEREDOC_SKIPTABS) && ch == '\t'); - } while (ch == '\n'); + } } if (ch == EOF) { o_free_unsafe(&heredoc); return NULL; } o_addchr(&heredoc, ch); + nommu_addchr(as_string, ch); if (prev == '\\' && ch == '\\') /* Correctly handle foo\\ (not a line cont.) */ prev = 0; /* not \ */ else prev = ch; - nommu_addchr(as_string, ch); } } @@ -3374,39 +3675,40 @@ static int parse_group(o_string *dest, struct parse_context *ctx, #if ENABLE_HUSH_TICK || ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_DOLLAR_OPS /* Subroutines for copying $(...) and `...` things */ -static void add_till_backquote(o_string *dest, struct in_str *input); +static int add_till_backquote(o_string *dest, struct in_str *input, int in_dquote); /* '...' */ -static void add_till_single_quote(o_string *dest, struct in_str *input) +static int add_till_single_quote(o_string *dest, struct in_str *input) { while (1) { int ch = i_getch(input); if (ch == EOF) { syntax_error_unterm_ch('\''); - /*xfunc_die(); - redundant */ + return 0; } if (ch == '\'') - return; + return 1; o_addchr(dest, ch); } } /* "...\"...`..`...." - do we need to handle "...$(..)..." too? */ -static void add_till_double_quote(o_string *dest, struct in_str *input) +static int add_till_double_quote(o_string *dest, struct in_str *input) { while (1) { int ch = i_getch(input); if (ch == EOF) { syntax_error_unterm_ch('"'); - /*xfunc_die(); - redundant */ + return 0; } if (ch == '"') - return; + return 1; if (ch == '\\') { /* \x. Copy both chars. */ o_addchr(dest, ch); ch = i_getch(input); } o_addchr(dest, ch); if (ch == '`') { - add_till_backquote(dest, input); + if (!add_till_backquote(dest, input, /*in_dquote:*/ 1)) + return 0; o_addchr(dest, ch); continue; } @@ -3427,26 +3729,26 @@ static void add_till_double_quote(o_string *dest, struct in_str *input) * Example Output * echo `echo '\'TEST\`echo ZZ\`BEST` \TESTZZBEST */ -static void add_till_backquote(o_string *dest, struct in_str *input) +static int add_till_backquote(o_string *dest, struct in_str *input, int in_dquote) { while (1) { int ch = i_getch(input); - if (ch == EOF) { - syntax_error_unterm_ch('`'); - /*xfunc_die(); - redundant */ - } if (ch == '`') - return; + return 1; if (ch == '\\') { - /* \x. Copy both chars unless it is \` */ - int ch2 = i_getch(input); - if (ch2 == EOF) { - syntax_error_unterm_ch('`'); - /*xfunc_die(); - redundant */ + /* \x. Copy both unless it is \`, \$, \\ and maybe \" */ + ch = i_getch(input); + if (ch != '`' + && ch != '$' + && ch != '\\' + && (!in_dquote || ch != '"') + ) { + o_addchr(dest, '\\'); } - if (ch2 != '`' && ch2 != '$' && ch2 != '\\') - o_addchr(dest, ch); - ch = ch2; + } + if (ch == EOF) { + syntax_error_unterm_ch('`'); + return 0; } o_addchr(dest, ch); } @@ -3482,7 +3784,7 @@ static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsign ch = i_getch(input); if (ch == EOF) { syntax_error_unterm_ch(end_ch); - /*xfunc_die(); - redundant */ + return 0; } if (ch == end_ch IF_HUSH_BASH_COMPAT( || ch == end_char2)) { if (!dbl) @@ -3496,22 +3798,26 @@ static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsign o_addchr(dest, ch); if (ch == '(' || ch == '{') { ch = (ch == '(' ? ')' : '}'); - add_till_closing_bracket(dest, input, ch); + if (!add_till_closing_bracket(dest, input, ch)) + return 0; o_addchr(dest, ch); continue; } if (ch == '\'') { - add_till_single_quote(dest, input); + if (!add_till_single_quote(dest, input)) + return 0; o_addchr(dest, ch); continue; } if (ch == '"') { - add_till_double_quote(dest, input); + if (!add_till_double_quote(dest, input)) + return 0; o_addchr(dest, ch); continue; } if (ch == '`') { - add_till_backquote(dest, input); + if (!add_till_backquote(dest, input, /*in_dquote:*/ 0)) + return 0; o_addchr(dest, ch); continue; } @@ -3520,7 +3826,7 @@ static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsign ch = i_getch(input); if (ch == EOF) { syntax_error_unterm_ch(')'); - /*xfunc_die(); - redundant */ + return 0; } o_addchr(dest, ch); continue; @@ -3532,16 +3838,15 @@ static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsign /* Return code: 0 for OK, 1 for syntax error */ #if BB_MMU -#define parse_dollar(as_string, dest, input) \ - parse_dollar(dest, input) +#define parse_dollar(as_string, dest, input, quote_mask) \ + parse_dollar(dest, input, quote_mask) #define as_string NULL #endif static int parse_dollar(o_string *as_string, o_string *dest, - struct in_str *input) + struct in_str *input, unsigned char quote_mask) { int ch = i_peek(input); /* first character after the $ */ - unsigned char quote_mask = dest->o_escape ? 0x80 : 0; debug_printf_parse("parse_dollar entered: ch='%c'\n", ch); if (isalpha(ch)) { @@ -3583,17 +3888,19 @@ static int parse_dollar(o_string *as_string, nommu_addchr(as_string, ch); ch = i_getch(input); /* first char after '{' */ - nommu_addchr(as_string, ch); /* It should be ${?}, or ${#var}, * or even ${?+subst} - operator acting on a special variable, * or the beginning of variable name. */ - if (!strchr(_SPECIAL_VARS_STR, ch) && !isalnum(ch)) { /* not one of those */ + if (ch == EOF + || (!strchr(_SPECIAL_VARS_STR, ch) && !isalnum(ch)) /* not one of those */ + ) { bad_dollar_syntax: syntax_error_unterm_str("${name}"); - debug_printf_parse("parse_dollar return 1: unterminated ${name}\n"); - return 1; + debug_printf_parse("parse_dollar return 0: unterminated ${name}\n"); + return 0; } + nommu_addchr(as_string, ch); ch |= quote_mask; /* It's possible to just call add_till_closing_bracket() at this point. @@ -3647,6 +3954,8 @@ static int parse_dollar(o_string *as_string, pos = dest->length; #if ENABLE_HUSH_DOLLAR_OPS last_ch = add_till_closing_bracket(dest, input, end_ch); + if (last_ch == 0) /* error? */ + return 0; #else #error Simple code to only allow ${var} is not implemented #endif @@ -3691,7 +4000,8 @@ static int parse_dollar(o_string *as_string, o_addchr(dest, /*quote_mask |*/ '+'); if (!BB_MMU) pos = dest->length; - add_till_closing_bracket(dest, input, ')' | DOUBLE_CLOSE_CHAR_FLAG); + if (!add_till_closing_bracket(dest, input, ')' | DOUBLE_CLOSE_CHAR_FLAG)) + return 0; /* error */ if (as_string) { o_addstr(as_string, dest->data + pos); o_addchr(as_string, ')'); @@ -3706,7 +4016,8 @@ static int parse_dollar(o_string *as_string, o_addchr(dest, quote_mask | '`'); if (!BB_MMU) pos = dest->length; - add_till_closing_bracket(dest, input, ')'); + if (!add_till_closing_bracket(dest, input, ')')) + return 0; /* error */ if (as_string) { o_addstr(as_string, dest->data + pos); o_addchr(as_string, ')'); @@ -3733,21 +4044,40 @@ static int parse_dollar(o_string *as_string, default: o_addQchr(dest, '$'); } - debug_printf_parse("parse_dollar return 0\n"); - return 0; + debug_printf_parse("parse_dollar return 1 (ok)\n"); + return 1; #undef as_string } #if BB_MMU -#define parse_stream_dquoted(as_string, dest, input, dquote_end) \ - parse_stream_dquoted(dest, input, dquote_end) +# if ENABLE_HUSH_BASH_COMPAT +#define encode_string(as_string, dest, input, dquote_end, process_bkslash) \ + encode_string(dest, input, dquote_end, process_bkslash) +# else +/* only ${var/pattern/repl} (its pattern part) needs additional mode */ +#define encode_string(as_string, dest, input, dquote_end, process_bkslash) \ + encode_string(dest, input, dquote_end) +# endif #define as_string NULL + +#else /* !MMU */ + +# if ENABLE_HUSH_BASH_COMPAT +/* all parameters are needed, no macro tricks */ +# else +#define encode_string(as_string, dest, input, dquote_end, process_bkslash) \ + encode_string(as_string, dest, input, dquote_end) +# endif #endif -static int parse_stream_dquoted(o_string *as_string, +static int encode_string(o_string *as_string, o_string *dest, struct in_str *input, - int dquote_end) + int dquote_end, + int process_bkslash) { +#if !ENABLE_HUSH_BASH_COMPAT + const int process_bkslash = 1; +#endif int ch; int next; @@ -3756,23 +4086,21 @@ static int parse_stream_dquoted(o_string *as_string, if (ch != EOF) nommu_addchr(as_string, ch); if (ch == dquote_end) { /* may be only '"' or EOF */ - if (dest->o_assignment == NOT_ASSIGNMENT) - dest->o_escape ^= 1; - debug_printf_parse("parse_stream_dquoted return 0\n"); - return 0; + debug_printf_parse("encode_string return 1 (ok)\n"); + return 1; } /* note: can't move it above ch == dquote_end check! */ if (ch == EOF) { syntax_error_unterm_ch('"'); - /*xfunc_die(); - redundant */ + return 0; /* error */ } next = '\0'; if (ch != '\n') { next = i_peek(input); } debug_printf_parse("\" ch=%c (%d) escape=%d\n", - ch, ch, dest->o_escape); - if (ch == '\\') { + ch, ch, !!(dest->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); + if (process_bkslash && ch == '\\') { if (next == EOF) { syntax_error("\\"); xfunc_die(); @@ -3782,25 +4110,23 @@ static int parse_stream_dquoted(o_string *as_string, * only when followed by one of the following characters: * $, `, ", \, or . A double quote may be quoted * within double quotes by preceding it with a backslash." - * NB: in (unquoted) heredoc, above does not apply to ". + * NB: in (unquoted) heredoc, above does not apply to ", + * therefore we check for it by "next == dquote_end" cond. */ - if (next == dquote_end || strchr("$`\\\n", next) != NULL) { - ch = i_getch(input); - if (ch != '\n') { - o_addqchr(dest, ch); - nommu_addchr(as_string, ch); - } - } else { - o_addqchr(dest, '\\'); - nommu_addchr(as_string, '\\'); - } + if (next == dquote_end || strchr("$`\\\n", next)) { + ch = i_getch(input); /* eat next */ + if (ch == '\n') + goto again; /* skip \ */ + } /* else: ch remains == '\\', and we double it below: */ + o_addqchr(dest, ch); /* \c if c is a glob char, else just c */ + nommu_addchr(as_string, ch); goto again; } if (ch == '$') { - if (parse_dollar(as_string, dest, input) != 0) { - debug_printf_parse("parse_stream_dquoted return 1: " - "parse_dollar returned non-0\n"); - return 1; + if (!parse_dollar(as_string, dest, input, /*quote_mask:*/ 0x80)) { + debug_printf_parse("encode_string return 0: " + "parse_dollar returned 0 (error)\n"); + return 0; } goto again; } @@ -3809,20 +4135,14 @@ static int parse_stream_dquoted(o_string *as_string, //unsigned pos = dest->length; o_addchr(dest, SPECIAL_VAR_SYMBOL); o_addchr(dest, 0x80 | '`'); - add_till_backquote(dest, input); + if (!add_till_backquote(dest, input, /*in_dquote:*/ dquote_end == '"')) + return 0; /* error */ o_addchr(dest, SPECIAL_VAR_SYMBOL); //debug_printf_subst("SUBST RES3 '%s'\n", dest->data + pos); goto again; } #endif o_addQchr(dest, ch); - if (ch == '=' - && (dest->o_assignment == MAYBE_ASSIGNMENT - || dest->o_assignment == WORD_IS_KEYWORD) - && is_well_formed_var_name(dest->data, '=') - ) { - dest->o_assignment = DEFINITELY_ASSIGNMENT; - } goto again; #undef as_string } @@ -3831,7 +4151,7 @@ static int parse_stream_dquoted(o_string *as_string, * Scan input until EOF or end_trigger char. * Return a list of pipes to execute, or NULL on EOF * or if end_trigger character is met. - * On syntax error, exit is shell is not interactive, + * On syntax error, exit if shell is not interactive, * reset parsing machinery and start parsing anew, * or return ERR_PTR. */ @@ -3841,12 +4161,10 @@ static struct pipe *parse_stream(char **pstring, { struct parse_context ctx; o_string dest = NULL_O_STRING; - int is_in_dquote; int heredoc_cnt; - /* Double-quote state is handled in the state variable is_in_dquote. - * A single-quote triggers a bypass of the main loop until its mate is - * found. When recursing, quote state is passed in via dest->o_escape. + /* Single-quote triggers a bypass of the main loop until its mate is + * found. When recursing, quote state is passed in via dest->o_expflags. */ debug_printf_parse("parse_stream entered, end_trigger='%c'\n", end_trigger ? end_trigger : 'X'); @@ -3857,37 +4175,26 @@ static struct pipe *parse_stream(char **pstring, o_addchr(&dest, '\0'); dest.length = 0; - G.ifs = get_local_var_value("IFS"); - if (G.ifs == NULL) - G.ifs = defifs; + /* We used to separate words on $IFS here. This was wrong. + * $IFS is used only for word splitting when $var is expanded, + * here we should use blank chars as separators, not $IFS + */ - reset: -#if ENABLE_HUSH_INTERACTIVE - input->promptmode = 0; /* PS1 */ -#endif - /* dest.o_assignment = MAYBE_ASSIGNMENT; - already is */ + if (MAYBE_ASSIGNMENT != 0) + dest.o_assignment = MAYBE_ASSIGNMENT; initialize_context(&ctx); - is_in_dquote = 0; heredoc_cnt = 0; while (1) { - const char *is_ifs; + const char *is_blank; const char *is_special; int ch; int next; int redir_fd; redir_type redir_style; - if (is_in_dquote) { - /* dest.has_quoted_part = 1; - already is (see below) */ - if (parse_stream_dquoted(&ctx.as_string, &dest, input, '"')) { - goto parse_error; - } - /* We reached closing '"' */ - is_in_dquote = 0; - } ch = i_getch(input); debug_printf_parse(": ch=%c (%d) escape=%d\n", - ch, ch, dest.o_escape); + ch, ch, !!(dest.o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); if (ch == EOF) { struct pipe *pi; @@ -3898,8 +4205,8 @@ static struct pipe *parse_stream(char **pstring, /* end_trigger == '}' case errors out earlier, * checking only ')' */ if (end_trigger == ')') { - syntax_error_unterm_ch('('); /* exits */ - /* goto parse_error; */ + syntax_error_unterm_ch('('); + goto parse_error; } if (done_word(&dest, &ctx)) { @@ -3940,20 +4247,20 @@ static struct pipe *parse_stream(char **pstring, if (ctx.command->argv /* word [word]{... - non-special */ || dest.length /* word{... - non-special */ || dest.has_quoted_part /* ""{... - non-special */ - || (next != ';' /* }; - special */ - && next != ')' /* }) - special */ - && next != '&' /* }& and }&& ... - special */ - && next != '|' /* }|| ... - special */ - && !strchr(G.ifs, next) /* {word - non-special */ + || (next != ';' /* }; - special */ + && next != ')' /* }) - special */ + && next != '&' /* }& and }&& ... - special */ + && next != '|' /* }|| ... - special */ + && !strchr(defifs, next) /* {word - non-special */ ) ) { /* They are not special, skip "{}" */ is_special += 2; } is_special = strchr(is_special, ch); - is_ifs = strchr(G.ifs, ch); + is_blank = strchr(defifs, ch); - if (!is_special && !is_ifs) { /* ordinary char */ + if (!is_special && !is_blank) { /* ordinary char */ ordinary_char: o_addQchr(&dest, ch); if ((dest.o_assignment == MAYBE_ASSIGNMENT @@ -3962,24 +4269,46 @@ static struct pipe *parse_stream(char **pstring, && is_well_formed_var_name(dest.data, '=') ) { dest.o_assignment = DEFINITELY_ASSIGNMENT; + debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); } continue; } - if (is_ifs) { + if (is_blank) { if (done_word(&dest, &ctx)) { goto parse_error; } if (ch == '\n') { -#if ENABLE_HUSH_CASE - /* "case ... in word) ..." - - * newlines are ignored (but ';' wouldn't be) */ - if (ctx.command->argv == NULL - && ctx.ctx_res_w == RES_MATCH + /* Is this a case when newline is simply ignored? + * Some examples: + * "cmd | cmd ..." + * "case ... in word) ..." + */ + if (IS_NULL_CMD(ctx.command) + && dest.length == 0 && !dest.has_quoted_part ) { - continue; + /* This newline can be ignored. But... + * Without check #1, interactive shell + * ignores even bare , + * and shows the continuation prompt: + * ps1_prompt$ + * ps2> _ <=== wrong, should be ps1 + * Without check #2, "cmd & " + * is similarly mistreated. + * (BTW, this makes "cmd & cmd" + * and "cmd && cmd" non-orthogonal. + * Really, ask yourself, why + * "cmd && " doesn't start + * cmd but waits for more input? + * No reason...) + */ + struct pipe *pi = ctx.list_head; + if (pi->num_cmds != 0 /* check #1 */ + && pi->followup != PIPE_BG /* check #2 */ + ) { + continue; + } } -#endif /* Treat newline as a command separator. */ done_pipe(&ctx, PIPE_SEQ); debug_printf_parse("heredoc_cnt:%d\n", heredoc_cnt); @@ -3990,8 +4319,9 @@ static struct pipe *parse_stream(char **pstring, heredoc_cnt = 0; } dest.o_assignment = MAYBE_ASSIGNMENT; + debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); ch = ';'; - /* note: if (is_ifs) continue; + /* note: if (is_blank) continue; * will still trigger for us */ } } @@ -4039,6 +4369,7 @@ static struct pipe *parse_stream(char **pstring, } done_pipe(&ctx, PIPE_SEQ); dest.o_assignment = MAYBE_ASSIGNMENT; + debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); /* Do we sit outside of any if's, loops or case's? */ if (!HAS_KEYWORDS IF_HAS_KEYWORDS(|| (ctx.ctx_res_w == RES_NONE && ctx.old_flag == 0)) @@ -4059,7 +4390,7 @@ static struct pipe *parse_stream(char **pstring, } } skip_end_trigger: - if (is_ifs) + if (is_blank) continue; /* Catch <, > before deciding whether this word is @@ -4111,6 +4442,31 @@ static struct pipe *parse_stream(char **pstring, if (parse_redirect(&ctx, redir_fd, redir_style, input)) goto parse_error; continue; /* back to top of while (1) */ + case '#': + if (dest.length == 0 && !dest.has_quoted_part) { + /* skip "#comment" */ + while (1) { + ch = i_peek(input); + if (ch == EOF || ch == '\n') + break; + i_getch(input); + /* note: we do not add it to &ctx.as_string */ + } + nommu_addchr(&ctx.as_string, '\n'); + continue; /* back to top of while (1) */ + } + break; + case '\\': + if (next == '\n') { + /* It's "\" */ +#if !BB_MMU + /* Remove trailing '\' from ctx.as_string */ + ctx.as_string.data[--ctx.as_string.length] = '\0'; +#endif + ch = i_getch(input); /* eat it */ + continue; /* back to top of while (1) */ + } + break; } if (dest.o_assignment == MAYBE_ASSIGNMENT @@ -4120,24 +4476,14 @@ static struct pipe *parse_stream(char **pstring, /* ch is a special char and thus this word * cannot be an assignment */ dest.o_assignment = NOT_ASSIGNMENT; + debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); } /* Note: nommu_addchr(&ctx.as_string, ch) is already done */ switch (ch) { - case '#': - if (dest.length == 0) { - while (1) { - ch = i_peek(input); - if (ch == EOF || ch == '\n') - break; - i_getch(input); - /* note: we do not add it to &ctx.as_string */ - } - nommu_addchr(&ctx.as_string, '\n'); - } else { - o_addQchr(&dest, ch); - } + case '#': /* non-comment #: "echo a#b" etc */ + o_addQchr(&dest, ch); break; case '\\': if (next == EOF) { @@ -4145,57 +4491,63 @@ static struct pipe *parse_stream(char **pstring, xfunc_die(); } ch = i_getch(input); - if (ch != '\n') { - o_addchr(&dest, '\\'); - /*nommu_addchr(&ctx.as_string, '\\'); - already done */ - o_addchr(&dest, ch); - nommu_addchr(&ctx.as_string, ch); - /* Example: echo Hello \2>file - * we need to know that word 2 is quoted */ - dest.has_quoted_part = 1; - } -#if !BB_MMU - else { - /* It's "\". Remove trailing '\' from ctx.as_string */ - ctx.as_string.data[--ctx.as_string.length] = '\0'; - } -#endif + /* note: ch != '\n' (that case does not reach this place) */ + o_addchr(&dest, '\\'); + /*nommu_addchr(&ctx.as_string, '\\'); - already done */ + o_addchr(&dest, ch); + nommu_addchr(&ctx.as_string, ch); + /* Example: echo Hello \2>file + * we need to know that word 2 is quoted */ + dest.has_quoted_part = 1; break; case '$': - if (parse_dollar(&ctx.as_string, &dest, input) != 0) { + if (!parse_dollar(&ctx.as_string, &dest, input, /*quote_mask:*/ 0)) { debug_printf_parse("parse_stream parse error: " - "parse_dollar returned non-0\n"); + "parse_dollar returned 0 (error)\n"); goto parse_error; } break; case '\'': dest.has_quoted_part = 1; - while (1) { - ch = i_getch(input); - if (ch == EOF) { - syntax_error_unterm_ch('\''); - /*xfunc_die(); - redundant */ + if (next == '\'' && !ctx.pending_redirect) { + insert_empty_quoted_str_marker: + nommu_addchr(&ctx.as_string, next); + i_getch(input); /* eat second ' */ + o_addchr(&dest, SPECIAL_VAR_SYMBOL); + o_addchr(&dest, SPECIAL_VAR_SYMBOL); + } else { + while (1) { + ch = i_getch(input); + if (ch == EOF) { + syntax_error_unterm_ch('\''); + goto parse_error; + } + nommu_addchr(&ctx.as_string, ch); + if (ch == '\'') + break; + o_addqchr(&dest, ch); } - nommu_addchr(&ctx.as_string, ch); - if (ch == '\'') - break; - o_addqchr(&dest, ch); } break; case '"': dest.has_quoted_part = 1; - is_in_dquote ^= 1; /* invert */ + if (next == '"' && !ctx.pending_redirect) + goto insert_empty_quoted_str_marker; if (dest.o_assignment == NOT_ASSIGNMENT) - dest.o_escape ^= 1; + dest.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS; + if (!encode_string(&ctx.as_string, &dest, input, '"', /*process_bkslash:*/ 1)) + goto parse_error; + dest.o_expflags &= ~EXP_FLAG_ESC_GLOB_CHARS; break; #if ENABLE_HUSH_TICK case '`': { - unsigned pos; + USE_FOR_NOMMU(unsigned pos;) o_addchr(&dest, SPECIAL_VAR_SYMBOL); o_addchr(&dest, '`'); - pos = dest.length; - add_till_backquote(&dest, input); + USE_FOR_NOMMU(pos = dest.length;) + if (!add_till_backquote(&dest, input, /*in_dquote:*/ 0)) + goto parse_error; # if !BB_MMU o_addstr(&ctx.as_string, dest.data + pos); o_addchr(&ctx.as_string, '`'); @@ -4233,6 +4585,7 @@ static struct pipe *parse_stream(char **pstring, /* We just finished a cmd. New one may start * with an assignment */ dest.o_assignment = MAYBE_ASSIGNMENT; + debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); break; case '&': if (done_word(&dest, &ctx)) { @@ -4333,23 +4686,15 @@ static struct pipe *parse_stream(char **pstring, } IF_HAS_KEYWORDS(pctx = p2;) } while (HAS_KEYWORDS && pctx); - /* Free text, clear all dest fields */ + o_free(&dest); - /* If we are not in top-level parse, we return, - * our caller will propagate error. - */ - if (end_trigger != ';') { + G.last_exitcode = 1; #if !BB_MMU - if (pstring) - *pstring = NULL; + if (pstring) + *pstring = NULL; #endif - debug_leave(); - return ERR_PTR; - } - /* Discard cached input, force prompt */ - input->p = NULL; - IF_HUSH_INTERACTIVE(input->promptme = 1;) - goto reset; + debug_leave(); + return ERR_PTR; } } @@ -4357,8 +4702,15 @@ static struct pipe *parse_stream(char **pstring, /*** Execution routines ***/ /* Expansion can recurse, need forward decls: */ -static char *expand_string_to_string(const char *str); +#if !ENABLE_HUSH_BASH_COMPAT +/* only ${var/pattern/repl} (its pattern part) needs additional mode */ +#define expand_string_to_string(str, do_unbackslash) \ + expand_string_to_string(str) +#endif +static char *expand_string_to_string(const char *str, int do_unbackslash); +#if ENABLE_HUSH_TICK static int process_command_subs(o_string *dest, const char *s); +#endif /* expand_strvec_to_strvec() takes a list of strings, expands * all variable references within and returns a pointer to @@ -4366,39 +4718,100 @@ static int process_command_subs(o_string *dest, const char *s); * of strings. (Think VAR="a b"; echo $VAR). * This new list is allocated as a single malloc block. * NULL-terminated list of char* pointers is at the beginning of it, - * followed by strings themself. + * followed by strings themselves. * Caller can deallocate entire list by single free(list). */ +/* A horde of its helpers come first: */ + +static void o_addblock_duplicate_backslash(o_string *o, const char *str, int len) +{ + while (--len >= 0) { + char c = *str++; + +#if ENABLE_HUSH_BRACE_EXPANSION + if (c == '{' || c == '}') { + /* { -> \{, } -> \} */ + o_addchr(o, '\\'); + /* And now we want to add { or } and continue: + * o_addchr(o, c); + * continue; + * luckily, just falling throught achieves this. + */ + } +#endif + o_addchr(o, c); + if (c == '\\') { + /* \z -> \\\z; \ -> \\ */ + o_addchr(o, '\\'); + if (len) { + len--; + o_addchr(o, '\\'); + o_addchr(o, *str++); + } + } + } +} + /* Store given string, finalizing the word and starting new one whenever * we encounter IFS char(s). This is used for expanding variable values. - * End-of-string does NOT finalize word: think about 'echo -$VAR-' */ -static int expand_on_ifs(o_string *output, int n, const char *str) + * End-of-string does NOT finalize word: think about 'echo -$VAR-'. + * Return in *ended_with_ifs: + * 1 - ended with IFS char, else 0 (this includes case of empty str). + */ +static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const char *str) { + int last_is_ifs = 0; + while (1) { - int word_len = strcspn(str, G.ifs); + int word_len; + + if (!*str) /* EOL - do not finalize word */ + break; + word_len = strcspn(str, G.ifs); if (word_len) { - if (output->o_escape) - o_addqblock(output, str, word_len); - else if (!output->o_glob) + /* We have WORD_LEN leading non-IFS chars */ + if (!(output->o_expflags & EXP_FLAG_GLOB)) { o_addblock(output, str, word_len); - else /* if (!escape && glob) */ { + } else { /* Protect backslashes against globbing up :) - * Example: "v='\*'; echo b$v" + * Example: "v='\*'; echo b$v" prints "b\*" + * (and does not try to glob on "*") */ o_addblock_duplicate_backslash(output, str, word_len); /*/ Why can't we do it easier? */ /*o_addblock(output, str, word_len); - WRONG: "v='\*'; echo Z$v" prints "Z*" instead of "Z\*" */ /*o_addqblock(output, str, word_len); - WRONG: "v='*'; echo Z$v" prints "Z*" instead of Z* files */ } + last_is_ifs = 0; str += word_len; + if (!*str) /* EOL - do not finalize word */ + break; } + + /* We know str here points to at least one IFS char */ + last_is_ifs = 1; + str += strspn(str, G.ifs); /* skip IFS chars */ if (!*str) /* EOL - do not finalize word */ break; - o_addchr(output, '\0'); - debug_print_list("expand_on_ifs", output, n); - n = o_save_ptr(output, n); - str += strspn(str, G.ifs); /* skip ifs chars */ + + /* Start new word... but not always! */ + /* Case "v=' a'; echo ''$v": we do need to finalize empty word: */ + if (output->has_quoted_part + /* Case "v=' a'; echo $v": + * here nothing precedes the space in $v expansion, + * therefore we should not finish the word + * (IOW: if there *is* word to finalize, only then do it): + */ + || (n > 0 && output->data[output->length - 1]) + ) { + o_addchr(output, '\0'); + debug_print_list("expand_on_ifs", output, n); + n = o_save_ptr(output, n); + } } + + if (ended_with_ifs) + *ended_with_ifs = last_is_ifs; debug_print_list("expand_on_ifs[1]", output, n); return n; } @@ -4410,7 +4823,12 @@ static int expand_on_ifs(o_string *output, int n, const char *str) * Returns malloced string. * As an optimization, we return NULL if expansion is not needed. */ -static char *expand_pseudo_dquoted(const char *str) +#if !ENABLE_HUSH_BASH_COMPAT +/* only ${var/pattern/repl} (its pattern part) needs additional mode */ +#define encode_then_expand_string(str, process_bkslash, do_unbackslash) \ + encode_then_expand_string(str) +#endif +static char *encode_then_expand_string(const char *str, int process_bkslash, int do_unbackslash) { char *exp_str; struct in_str input; @@ -4429,27 +4847,32 @@ static char *expand_pseudo_dquoted(const char *str) * echo $(($a + `echo 1`)) $((1 + $((2)) )) */ setup_string_in_str(&input, str); - parse_stream_dquoted(NULL, &dest, &input, EOF); + encode_string(NULL, &dest, &input, EOF, process_bkslash); +//TODO: error check (encode_string returns 0 on error)? //bb_error_msg("'%s' -> '%s'", str, dest.data); - exp_str = expand_string_to_string(dest.data); + exp_str = expand_string_to_string(dest.data, /*unbackslash:*/ do_unbackslash); //bb_error_msg("'%s' -> '%s'", dest.data, exp_str); o_free_unsafe(&dest); return exp_str; } #if ENABLE_SH_MATH_SUPPORT -static arith_t expand_and_evaluate_arith(const char *arg, int *errcode_p) +static arith_t expand_and_evaluate_arith(const char *arg, const char **errmsg_p) { - arith_eval_hooks_t hooks; + arith_state_t math_state; arith_t res; char *exp_str; - hooks.lookupvar = get_local_var_value; - hooks.setvar = set_local_var_from_halves; - hooks.endofname = endofname; - exp_str = expand_pseudo_dquoted(arg); - res = arith(exp_str ? exp_str : arg, errcode_p, &hooks); + math_state.lookupvar = get_local_var_value; + math_state.setvar = set_local_var_from_halves; + //math_state.endofname = endofname; + exp_str = encode_then_expand_string(arg, /*process_bkslash:*/ 1, /*unbackslash:*/ 1); + res = arith(&math_state, exp_str ? exp_str : arg); free(exp_str); + if (errmsg_p) + *errmsg_p = math_state.errmsg; + if (math_state.errmsg) + die_if_script(math_state.errmsg); return res; } #endif @@ -4511,7 +4934,7 @@ static char *replace_pattern(char *val, const char *pattern, const char *repl, c /* Helper: * Handles varname... construct. */ -static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, char **pp, char first_ch) +static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, char **pp) { const char *val = NULL; char *to_be_freed = NULL; @@ -4522,19 +4945,23 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha char exp_save = exp_save; /* for compiler */ char *exp_saveptr; /* points to expansion operator */ char *exp_word = exp_word; /* for compiler */ + char arg0; + *p = '\0'; /* replace trailing SPECIAL_VAR_SYMBOL */ var = arg; - *p = '\0'; exp_saveptr = arg[1] ? strchr(VAR_ENCODED_SUBST_OPS, arg[1]) : NULL; - first_char = arg[0] = first_ch & 0x7f; + arg0 = arg[0]; + first_char = arg[0] = arg0 & 0x7f; exp_op = 0; - if (first_char == '#' && arg[1] && !exp_saveptr) { - /* handle length expansion ${#var} */ + if (first_char == '#' /* ${#... */ + && arg[1] && !exp_saveptr /* not ${#} and not ${#...} */ + ) { + /* It must be length operator: ${#var} */ var++; exp_op = 'L'; } else { - /* maybe handle parameter expansion */ + /* Maybe handle parameter expansion */ if (exp_saveptr /* if 2nd char is one of expansion operators */ && strchr(NUMERIC_SPECVARS_STR, first_char) /* 1st char is special variable */ ) { @@ -4549,8 +4976,9 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha exp_word = exp_saveptr + 1; if (exp_op == ':') { exp_op = *exp_word++; +//TODO: try ${var:} and ${var:bogus} in non-bash config if (ENABLE_HUSH_BASH_COMPAT - && (exp_op == '\0' || !strchr(MINUS_PLUS_EQUAL_QUESTION, exp_op)) + && (!exp_op || !strchr(MINUS_PLUS_EQUAL_QUESTION, exp_op)) ) { /* oops... it's ${var:N[:M]}, not ${var:?xxx} or some such */ exp_op = ':'; @@ -4561,9 +4989,9 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha } /* else: it's not an expansion op, but bare ${var} */ } - /* lookup the variable in question */ + /* Look up the variable in question */ if (isdigit(var[0])) { - /* parse_dollar() should have vetted var for us */ + /* parse_dollar should have vetted var for us */ int n = xatoi_positive(var); if (n < G.global_argc) val = G.global_argv[n]; @@ -4604,27 +5032,29 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha * Then var's value is matched to it and matching part removed. */ if (val && val[0]) { + char *t; char *exp_exp_word; char *loc; unsigned scan_flags = pick_scan(exp_op, *exp_word); - if (exp_op == *exp_word) /* ## or %% */ + if (exp_op == *exp_word) /* ## or %% */ exp_word++; -//TODO: avoid xstrdup unless needed -// (see HACK ALERT below for an example) - val = to_be_freed = xstrdup(val); -//TODO: fix expansion rules: - exp_exp_word = expand_pseudo_dquoted(exp_word); + exp_exp_word = encode_then_expand_string(exp_word, /*process_bkslash:*/ 1, /*unbackslash:*/ 1); if (exp_exp_word) exp_word = exp_exp_word; - loc = scan_and_match(to_be_freed, exp_word, scan_flags); + /* HACK ALERT. We depend here on the fact that + * G.global_argv and results of utoa and get_local_var_value + * are actually in writable memory: + * scan_and_match momentarily stores NULs there. */ + t = (char*)val; + loc = scan_and_match(t, exp_word, scan_flags); //bb_error_msg("op:%c str:'%s' pat:'%s' res:'%s'", - // exp_op, to_be_freed, exp_word, loc); + // exp_op, t, exp_word, loc); free(exp_exp_word); if (loc) { /* match was found */ if (scan_flags & SCAN_MATCH_LEFT_HALF) /* #[#] */ - val = loc; + val = loc; /* take right part */ else /* %[%] */ - *loc = '\0'; + val = to_be_freed = xstrndup(val, loc - val); /* left */ } } } @@ -4633,14 +5063,14 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha /* It's ${var/[/]pattern[/repl]} thing. * Note that in encoded form it has TWO parts: * var/patternrepl + * and if // is used, it is encoded as \: + * var\patternrepl */ /* Empty variable always gives nothing: */ // "v=''; echo ${v/*/w}" prints "", not "w" if (val && val[0]) { - /* It's ${var/[/]pattern[/repl]} thing */ - /* - * Pattern is taken literally, while - * repl should be de-backslased and globbed + /* pattern uses non-standard expansion. + * repl should be unbackslashed and globbed * by the usual expansion rules: * >az; >bz; * v='a bz'; echo "${v/a*z/a*z}" prints "a*z" @@ -4649,9 +5079,8 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha * v='a bz'; echo ${v/a*z/\z} prints "z" * (note that a*z _pattern_ is never globbed!) */ -//TODO: fix expansion rules: char *pattern, *repl, *t; - pattern = expand_pseudo_dquoted(exp_word); + pattern = encode_then_expand_string(exp_word, /*process_bkslash:*/ 0, /*unbackslash:*/ 0); if (!pattern) pattern = xstrdup(exp_word); debug_printf_varexp("pattern:'%s'->'%s'\n", exp_word, pattern); @@ -4659,7 +5088,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha exp_word = p; p = strchr(p, SPECIAL_VAR_SYMBOL); *p = '\0'; - repl = expand_pseudo_dquoted(exp_word); + repl = encode_then_expand_string(exp_word, /*process_bkslash:*/ arg0 & 0x80, /*unbackslash:*/ 1); debug_printf_varexp("repl:'%s'->'%s'\n", exp_word, repl); /* HACK ALERT. We depend here on the fact that * G.global_argv and results of utoa and get_local_var_value @@ -4684,24 +5113,28 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha * var:NM */ arith_t beg, len; - int errcode = 0; + const char *errmsg; - beg = expand_and_evaluate_arith(exp_word, &errcode); + beg = expand_and_evaluate_arith(exp_word, &errmsg); + if (errmsg) + goto arith_err; debug_printf_varexp("beg:'%s'=%lld\n", exp_word, (long long)beg); *p++ = SPECIAL_VAR_SYMBOL; exp_word = p; p = strchr(p, SPECIAL_VAR_SYMBOL); *p = '\0'; - len = expand_and_evaluate_arith(exp_word, &errcode); + len = expand_and_evaluate_arith(exp_word, &errmsg); + if (errmsg) + goto arith_err; debug_printf_varexp("len:'%s'=%lld\n", exp_word, (long long)len); - - if (errcode >= 0 && len >= 0) { /* bash compat: len < 0 is illegal */ + if (len >= 0) { /* bash compat: len < 0 is illegal */ if (beg < 0) /* bash compat */ beg = 0; debug_printf_varexp("from val:'%s'\n", val); - if (len == 0 || !val || beg >= strlen(val)) - val = ""; - else { + if (len == 0 || !val || beg >= strlen(val)) { + arith_err: + val = NULL; + } else { /* Paranoia. What if user entered 9999999999999 * which fits in arith_t but not int? */ if (len >= INT_MAX) @@ -4713,7 +5146,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha #endif { die_if_script("malformed ${%s:...}", var); - val = ""; + val = NULL; } } else { /* one of "-=+?" */ /* Standard-mandated substitution ops: @@ -4744,7 +5177,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op, (exp_save == ':') ? "true" : "false", use_word); if (use_word) { - to_be_freed = expand_pseudo_dquoted(exp_word); + to_be_freed = encode_then_expand_string(exp_word, /*process_bkslash:*/ 1, /*unbackslash:*/ 1); if (to_be_freed) exp_word = to_be_freed; if (exp_op == '?') { @@ -4775,7 +5208,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha *exp_saveptr = exp_save; } /* if (exp_op) */ - arg[0] = first_ch; + arg[0] = arg0; *pp = p; *to_be_freed_pp = to_be_freed; @@ -4787,18 +5220,17 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha * to be filled). This routine is extremely tricky: has to deal with * variables/parameters with whitespace, $* and $@, and constructs like * 'echo -$*-'. If you play here, you must run testsuite afterwards! */ -static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask) +static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) { - /* or_mask is either 0 (normal case) or 0x80 - + /* output->o_expflags & EXP_FLAG_SINGLEWORD (0x80) if we are in * expansion of right-hand side of assignment == 1-element expand. - * It will also do no globbing, and thus we must not backslash-quote! */ - char ored_ch; + char cant_be_null = 0; /* only bit 0x80 matters */ + int ended_in_ifs = 0; /* did last unquoted expansion end with IFS chars? */ char *p; - ored_ch = 0; - - debug_printf_expand("expand_vars_to_list: arg:'%s' or_mask:%x\n", arg, or_mask); + debug_printf_expand("expand_vars_to_list: arg:'%s' singleword:%x\n", arg, + !!(output->o_expflags & EXP_FLAG_SINGLEWORD)); debug_print_list("expand_vars_to_list", output, n); n = o_save_ptr(output, n); debug_print_list("expand_vars_to_list[0]", output, n); @@ -4813,16 +5245,29 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char #if ENABLE_SH_MATH_SUPPORT char arith_buf[sizeof(arith_t)*3 + 2]; #endif + + if (ended_in_ifs) { + o_addchr(output, '\0'); + n = o_save_ptr(output, n); + ended_in_ifs = 0; + } + o_addblock(output, arg, p - arg); debug_print_list("expand_vars_to_list[1]", output, n); arg = ++p; p = strchr(p, SPECIAL_VAR_SYMBOL); - first_ch = arg[0] | or_mask; /* forced to "quoted" if or_mask = 0x80 */ - /* "$@" is special. Even if quoted, it can still - * expand to nothing (not even an empty string) */ + /* Fetch special var name (if it is indeed one of them) + * and quote bit, force the bit on if singleword expansion - + * important for not getting v=$@ expand to many words. */ + first_ch = arg[0] | (output->o_expflags & EXP_FLAG_SINGLEWORD); + + /* Is this variable quoted and thus expansion can't be null? + * "$@" is special. Even if quoted, it can still + * expand to nothing (not even an empty string), + * thus it is excluded. */ if ((first_ch & 0x7f) != '@') - ored_ch |= first_ch; + cant_be_null |= first_ch; switch (first_ch & 0x7f) { /* Highest bit in first_ch indicates that var is double-quoted */ @@ -4832,13 +5277,10 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char if (!G.global_argv[1]) break; i = 1; - ored_ch |= first_ch; /* do it for "$@" _now_, when we know it's not empty */ + cant_be_null |= first_ch; /* do it for "$@" _now_, when we know it's not empty */ if (!(first_ch & 0x80)) { /* unquoted $* or $@ */ - smallint sv = output->o_escape; - /* unquoted var's contents should be globbed, so don't escape */ - output->o_escape = 0; while (G.global_argv[i]) { - n = expand_on_ifs(output, n, G.global_argv[i]); + n = expand_on_ifs(NULL, output, n, G.global_argv[i]); debug_printf_expand("expand_vars_to_list: argv %d (last %d)\n", i, G.global_argc - 1); if (G.global_argv[i++][0] && G.global_argv[i]) { /* this argv[] is not empty and not last: @@ -4849,11 +5291,12 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char debug_print_list("expand_vars_to_list[3]", output, n); } } - output->o_escape = sv; } else - /* If or_mask is nonzero, we handle assignment 'a=....$@.....' + /* If EXP_FLAG_SINGLEWORD, we handle assignment 'a=....$@.....' * and in this case should treat it like '$*' - see 'else...' below */ - if (first_ch == ('@'|0x80) && !or_mask) { /* quoted $@ */ + if (first_ch == ('@'|0x80) /* quoted $@ */ + && !(output->o_expflags & EXP_FLAG_SINGLEWORD) /* not v="$@" case */ + ) { while (1) { o_addQstr(output, G.global_argv[i]); if (++i >= G.global_argc) @@ -4862,7 +5305,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char debug_print_list("expand_vars_to_list[4]", output, n); n = o_save_ptr(output, n); } - } else { /* quoted $*: add as one word */ + } else { /* quoted $* (or v="$@" case): add as one word */ while (1) { o_addQstr(output, G.global_argv[i]); if (!G.global_argv[++i]) @@ -4870,17 +5313,19 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char if (G.ifs[0]) o_addchr(output, G.ifs[0]); } + output->has_quoted_part = 1; } break; } case SPECIAL_VAR_SYMBOL: /* */ /* "Empty variable", used to make "" etc to not disappear */ + output->has_quoted_part = 1; arg++; - ored_ch = 0x80; + cant_be_null = 0x80; break; #if ENABLE_HUSH_TICK case '`': /* `cmd */ - *p = '\0'; + *p = '\0'; /* replace trailing */ arg++; /* Can't just stuff it into output o_string, * expanded result may need to be globbed @@ -4894,49 +5339,31 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char #if ENABLE_SH_MATH_SUPPORT case '+': { /* +cmd */ arith_t res; - int errcode; arg++; /* skip '+' */ *p = '\0'; /* replace trailing */ debug_printf_subst("ARITH '%s' first_ch %x\n", arg, first_ch); - res = expand_and_evaluate_arith(arg, &errcode); - - if (errcode < 0) { - const char *msg = "error in arithmetic"; - switch (errcode) { - case -3: - msg = "exponent less than 0"; - break; - case -2: - msg = "divide by 0"; - break; - case -5: - msg = "expression recursion loop detected"; - break; - } - die_if_script(msg); - } - debug_printf_subst("ARITH RES '"arith_t_fmt"'\n", res); - sprintf(arith_buf, arith_t_fmt, res); + res = expand_and_evaluate_arith(arg, NULL); + debug_printf_subst("ARITH RES '"ARITH_FMT"'\n", res); + sprintf(arith_buf, ARITH_FMT, res); val = arith_buf; break; } #endif default: - val = expand_one_var(&to_be_freed, arg, &p, first_ch); + val = expand_one_var(&to_be_freed, arg, &p); IF_HUSH_TICK(store_val:) if (!(first_ch & 0x80)) { /* unquoted $VAR */ - debug_printf_expand("unquoted '%s', output->o_escape:%d\n", val, output->o_escape); + debug_printf_expand("unquoted '%s', output->o_escape:%d\n", val, + !!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); if (val && val[0]) { - /* unquoted var's contents should be globbed, so don't escape */ - smallint sv = output->o_escape; - output->o_escape = 0; - n = expand_on_ifs(output, n, val); + n = expand_on_ifs(&ended_in_ifs, output, n, val); val = NULL; - output->o_escape = sv; } } else { /* quoted $VAR, val will be appended below */ - debug_printf_expand("quoted '%s', output->o_escape:%d\n", val, output->o_escape); + output->has_quoted_part = 1; + debug_printf_expand("quoted '%s', output->o_escape:%d\n", val, + !!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); } break; @@ -4946,7 +5373,9 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char o_addQstr(output, val); } free(to_be_freed); - /* Do the check to avoid writing to a const string */ + + /* Restore NULL'ed SPECIAL_VAR_SYMBOL. + * Do the check to avoid writing to a const string. */ if (*p != SPECIAL_VAR_SYMBOL) *p = SPECIAL_VAR_SYMBOL; @@ -4957,13 +5386,17 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char } /* end of "while (SPECIAL_VAR_SYMBOL is found) ..." */ if (arg[0]) { + if (ended_in_ifs) { + o_addchr(output, '\0'); + n = o_save_ptr(output, n); + } debug_print_list("expand_vars_to_list[a]", output, n); /* this part is literal, and it was already pre-quoted * if needed (much earlier), do not use o_addQstr here! */ o_addstr_with_NUL(output, arg); debug_print_list("expand_vars_to_list[b]", output, n); } else if (output->length == o_get_last_ptr(output, n) /* expansion is empty */ - && !(ored_ch & 0x80) /* and all vars were not quoted. */ + && !(cant_be_null & 0x80) /* and all vars were not quoted. */ ) { n--; /* allow to reuse list[n] later without re-growth */ @@ -4975,25 +5408,17 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char return n; } -enum { - EXPVAR_FLAG_GLOB = 0x200, - EXPVAR_FLAG_ESCAPE_VARS = 0x100, - EXPVAR_FLAG_SINGLEWORD = 0x80, /* must be 0x80 */ -}; -static char **expand_variables(char **argv, unsigned or_mask) +static char **expand_variables(char **argv, unsigned expflags) { int n; char **list; o_string output = NULL_O_STRING; - /* protect against globbing for "$var"? */ - /* (unquoted $var will temporarily switch it off) */ - output.o_escape = 1 & (or_mask / EXPVAR_FLAG_ESCAPE_VARS); - output.o_glob = 1 & (or_mask / EXPVAR_FLAG_GLOB); + output.o_expflags = expflags; n = 0; while (*argv) { - n = expand_vars_to_list(&output, n, *argv, (unsigned char)or_mask); + n = expand_vars_to_list(&output, n, *argv); argv++; } debug_print_list("expand_variables", &output, n); @@ -5006,43 +5431,54 @@ static char **expand_variables(char **argv, unsigned or_mask) static char **expand_strvec_to_strvec(char **argv) { - return expand_variables(argv, EXPVAR_FLAG_GLOB | EXPVAR_FLAG_ESCAPE_VARS); + return expand_variables(argv, EXP_FLAG_GLOB | EXP_FLAG_ESC_GLOB_CHARS); } #if ENABLE_HUSH_BASH_COMPAT static char **expand_strvec_to_strvec_singleword_noglob(char **argv) { - return expand_variables(argv, EXPVAR_FLAG_SINGLEWORD); + return expand_variables(argv, EXP_FLAG_SINGLEWORD); } #endif -/* Used for expansion of right hand of assignments */ -/* NB: should NOT do globbing! - * "export v=/bin/c*; env | grep ^v=" outputs "v=/bin/c*" */ -static char *expand_string_to_string(const char *str) +/* Used for expansion of right hand of assignments, + * $((...)), heredocs, variable espansion parts. + * + * NB: should NOT do globbing! + * "export v=/bin/c*; env | grep ^v=" outputs "v=/bin/c*" + */ +static char *expand_string_to_string(const char *str, int do_unbackslash) { +#if !ENABLE_HUSH_BASH_COMPAT + const int do_unbackslash = 1; +#endif char *argv[2], **list; + debug_printf_expand("string_to_string<='%s'\n", str); /* This is generally an optimization, but it also * handles "", which otherwise trips over !list[0] check below. * (is this ever happens that we actually get str="" here?) */ if (!strchr(str, SPECIAL_VAR_SYMBOL) && !strchr(str, '\\')) { //TODO: Can use on strings with \ too, just unbackslash() them? - debug_printf_expand("string_to_string(fast)='%s'\n", str); + debug_printf_expand("string_to_string(fast)=>'%s'\n", str); return xstrdup(str); } argv[0] = (char*)str; argv[1] = NULL; - list = expand_variables(argv, EXPVAR_FLAG_ESCAPE_VARS | EXPVAR_FLAG_SINGLEWORD); + list = expand_variables(argv, do_unbackslash + ? EXP_FLAG_ESC_GLOB_CHARS | EXP_FLAG_SINGLEWORD + : EXP_FLAG_SINGLEWORD + ); if (HUSH_DEBUG) if (!list[0] || list[1]) bb_error_msg_and_die("BUG in varexp2"); /* actually, just move string 2*sizeof(char*) bytes back */ overlapping_strcpy((char*)list, list[0]); - unbackslash((char*)list); - debug_printf_expand("string_to_string='%s'\n", (char*)list); + if (do_unbackslash) + unbackslash((char*)list); + debug_printf_expand("string_to_string=>'%s'\n", (char*)list); return (char*)list; } @@ -5051,7 +5487,7 @@ static char* expand_strvec_to_string(char **argv) { char **list; - list = expand_variables(argv, EXPVAR_FLAG_SINGLEWORD); + list = expand_variables(argv, EXP_FLAG_SINGLEWORD); /* Convert all NULs to spaces */ if (list[0]) { int n = 1; @@ -5077,13 +5513,32 @@ static char **expand_assignments(char **argv, int count) G.expanded_assignments = p = NULL; /* Expand assignments into one string each */ for (i = 0; i < count; i++) { - G.expanded_assignments = p = add_string_to_strings(p, expand_string_to_string(argv[i])); + G.expanded_assignments = p = add_string_to_strings(p, expand_string_to_string(argv[i], /*unbackslash:*/ 1)); } G.expanded_assignments = NULL; return p; } +static void switch_off_special_sigs(unsigned mask) +{ + unsigned sig = 0; + while ((mask >>= 1) != 0) { + sig++; + if (!(mask & 1)) + continue; + if (G.traps) { + if (G.traps[sig] && !G.traps[sig][0]) + /* trap is '', has to remain SIG_IGN */ + continue; + free(G.traps[sig]); + G.traps[sig] = NULL; + } + /* We are here only if no trap or trap was not '' */ + install_sighandler(sig, SIG_DFL); + } +} + #if BB_MMU /* never called */ void re_execute_shell(char ***to_free, const char *s, @@ -5103,47 +5558,36 @@ static void reset_traps_to_defaults(void) * Testcase: (while :; do :; done) + ^Z should background. * Same goes for SIGTERM, SIGHUP, SIGINT. */ - if (!G.traps && !(G.non_DFL_mask & SPECIAL_INTERACTIVE_SIGS)) - return; /* already no traps and no SPECIAL_INTERACTIVE_SIGS */ - - /* Switching off SPECIAL_INTERACTIVE_SIGS. - * Stupid. It can be done with *single* &= op, but we can't use - * the fact that G.blocked_set is implemented as a bitmask - * in libc... */ - mask = (SPECIAL_INTERACTIVE_SIGS >> 1); - sig = 1; - while (1) { - if (mask & 1) { - /* Careful. Only if no trap or trap is not "" */ - if (!G.traps || !G.traps[sig] || G.traps[sig][0]) - sigdelset(&G.blocked_set, sig); - } - mask >>= 1; - if (!mask) - break; - sig++; - } - /* Our homegrown sig mask is saner to work with :) */ - G.non_DFL_mask &= ~SPECIAL_INTERACTIVE_SIGS; + mask = (G.special_sig_mask & SPECIAL_INTERACTIVE_SIGS) | G_fatal_sig_mask; + if (!G.traps && !mask) + return; /* already no traps and no special sigs */ - /* Resetting all traps to default except empty ones */ - mask = G.non_DFL_mask; - if (G.traps) for (sig = 0; sig < NSIG; sig++, mask >>= 1) { - if (!G.traps[sig] || !G.traps[sig][0]) - continue; + /* Switch off special sigs */ + switch_off_special_sigs(mask); +#if ENABLE_HUSH_JOB + G_fatal_sig_mask = 0; +#endif + G.special_sig_mask &= ~SPECIAL_INTERACTIVE_SIGS; + /* SIGQUIT,SIGCHLD and maybe SPECIAL_JOBSTOP_SIGS + * remain set in G.special_sig_mask */ + + if (!G.traps) + return; + + /* Reset all sigs to default except ones with empty traps */ + for (sig = 0; sig < NSIG; sig++) { + if (!G.traps[sig]) + continue; /* no trap: nothing to do */ + if (!G.traps[sig][0]) + continue; /* empty trap: has to remain SIG_IGN */ + /* sig has non-empty trap, reset it: */ free(G.traps[sig]); G.traps[sig] = NULL; - /* There is no signal for 0 (EXIT) */ + /* There is no signal for trap 0 (EXIT) */ if (sig == 0) continue; - /* There was a trap handler, we just removed it. - * But if sig still has non-DFL handling, - * we should not unblock the sig. */ - if (mask & 1) - continue; - sigdelset(&G.blocked_set, sig); + install_sighandler(sig, pick_sighandler(sig)); } - sigprocmask(SIG_SETMASK, &G.blocked_set, NULL); } #else /* !BB_MMU */ @@ -5254,9 +5698,6 @@ static void re_execute_shell(char ***to_free, const char *s, * _inside_ group (just before echo 1), it works. * * I conclude it means we don't need to pass active traps here. - * Even if we would use signal handlers instead of signal masking - * in order to implement trap handling, - * exec syscall below resets signals to SIG_DFL for us. */ *pp++ = (char *) "-c"; *pp++ = (char *) s; @@ -5273,7 +5714,9 @@ static void re_execute_shell(char ***to_free, const char *s, do_exec: debug_printf_exec("re_execute_shell pid:%d cmd:'%s'\n", getpid(), s); - sigprocmask(SIG_SETMASK, &G.inherited_set, NULL); + /* Don't propagate SIG_IGN to the child */ + if (SPECIAL_JOBSTOP_SIGS != 0) + switch_off_special_sigs(G.special_sig_mask & SPECIAL_JOBSTOP_SIGS); execve(bb_busybox_exec_path, argv, pp); /* Fallback. Useful for init=/bin/hush usage etc */ if (argv[0][0] == '/') @@ -5304,9 +5747,29 @@ static void parse_and_run_stream(struct in_str *inp, int end_trigger) while (1) { struct pipe *pipe_list; +#if ENABLE_HUSH_INTERACTIVE + if (end_trigger == ';') + inp->promptmode = 0; /* PS1 */ +#endif pipe_list = parse_stream(NULL, inp, end_trigger); - if (!pipe_list) { /* EOF */ - if (empty) + if (!pipe_list || pipe_list == ERR_PTR) { /* EOF/error */ + /* If we are in "big" script + * (not in `cmd` or something similar)... + */ + if (pipe_list == ERR_PTR && end_trigger == ';') { + /* Discard cached input (rest of line) */ + int ch = inp->last_char; + while (ch != EOF && ch != '\n') { + //bb_error_msg("Discarded:'%c'", ch); + ch = i_getch(inp); + } + /* Force prompt */ + inp->p = NULL; + /* This stream isn't empty */ + empty = 0; + continue; + } + if (!pipe_list && empty) G.last_exitcode = 0; break; } @@ -5314,6 +5777,10 @@ static void parse_and_run_stream(struct in_str *inp, int end_trigger) debug_printf_exec("parse_and_run_stream: run_and_free_list\n"); run_and_free_list(pipe_list); empty = 0; +#if ENABLE_HUSH_FUNCTIONS + if (G.flag_return_in_progress == 1) + break; +#endif } } @@ -5486,7 +5953,7 @@ static void setup_heredoc(struct redir_struct *redir) expanded = NULL; if (!(redir->rd_dup & HEREDOC_QUOTED)) { - expanded = expand_pseudo_dquoted(heredoc); + expanded = encode_then_expand_string(heredoc, /*process_bkslash:*/ 1, /*unbackslash:*/ 1); if (expanded) heredoc = expanded; } @@ -5586,7 +6053,7 @@ static int setup_redirects(struct command *prog, int squirrel[]) continue; } mode = redir_table[redir->rd_type].mode; - p = expand_string_to_string(redir->rd_filename); + p = expand_string_to_string(redir->rd_filename, /*unbackslash:*/ 1); openfd = open_or_warn(p, mode); free(p); if (openfd < 0) { @@ -5880,10 +6347,13 @@ static void exec_builtin(char ***to_free, char **argv) { #if BB_MMU - int rcode = x->b_function(argv); + int rcode; + fflush_all(); + rcode = x->b_function(argv); fflush_all(); _exit(rcode); #else + fflush_all(); /* On NOMMU, we must never block! * Example: { sleep 99 | read line; } & echo Ok */ @@ -5900,7 +6370,9 @@ static void execvp_or_die(char **argv) NORETURN; static void execvp_or_die(char **argv) { debug_printf_exec("execing '%s'\n", argv[0]); - sigprocmask(SIG_SETMASK, &G.inherited_set, NULL); + /* Don't propagate SIG_IGN to the child */ + if (SPECIAL_JOBSTOP_SIGS != 0) + switch_off_special_sigs(G.special_sig_mask & SPECIAL_JOBSTOP_SIGS); execvp(argv[0], argv); bb_perror_msg("can't execute '%s'", argv[0]); _exit(127); /* bash compat */ @@ -6032,7 +6504,9 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, # endif /* Re-exec ourselves */ debug_printf_exec("re-execing applet '%s'\n", argv[0]); - sigprocmask(SIG_SETMASK, &G.inherited_set, NULL); + /* Don't propagate SIG_IGN to the child */ + if (SPECIAL_JOBSTOP_SIGS != 0) + switch_off_special_sigs(G.special_sig_mask & SPECIAL_JOBSTOP_SIGS); execv(bb_busybox_exec_path, argv); /* If they called chroot or otherwise made the binary no longer * executable, fall through */ @@ -6264,48 +6738,58 @@ static int checkjobs(struct pipe *fg_pipe) #endif /* Were we asked to wait for fg pipe? */ if (fg_pipe) { - for (i = 0; i < fg_pipe->num_cmds; i++) { + i = fg_pipe->num_cmds; + while (--i >= 0) { debug_printf_jobs("check pid %d\n", fg_pipe->cmds[i].pid); if (fg_pipe->cmds[i].pid != childpid) continue; if (dead) { + int ex; fg_pipe->cmds[i].pid = 0; fg_pipe->alive_cmds--; - if (i == fg_pipe->num_cmds - 1) { - /* last process gives overall exitstatus */ - rcode = WEXITSTATUS(status); - /* bash prints killer signal's name for *last* - * process in pipe (prints just newline for SIGINT). - * Mimic this. Example: "sleep 5" + (^\ or kill -QUIT) - */ - if (WIFSIGNALED(status)) { - int sig = WTERMSIG(status); - printf("%s\n", sig == SIGINT ? "" : get_signame(sig)); - /* TODO: MIPS has 128 sigs (1..128), what if sig==128 here? - * Maybe we need to use sig | 128? */ - rcode = sig + 128; - } - IF_HAS_KEYWORDS(if (fg_pipe->pi_inverted) rcode = !rcode;) + ex = WEXITSTATUS(status); + /* bash prints killer signal's name for *last* + * process in pipe (prints just newline for SIGINT/SIGPIPE). + * Mimic this. Example: "sleep 5" + (^\ or kill -QUIT) + */ + if (WIFSIGNALED(status)) { + int sig = WTERMSIG(status); + if (i == fg_pipe->num_cmds-1) + /* TODO: use strsignal() instead for bash compat? but that's bloat... */ + printf("%s\n", sig == SIGINT || sig == SIGPIPE ? "" : get_signame(sig)); + /* TODO: if (WCOREDUMP(status)) + " (core dumped)"; */ + /* TODO: MIPS has 128 sigs (1..128), what if sig==128 here? + * Maybe we need to use sig | 128? */ + ex = sig + 128; } + fg_pipe->cmds[i].cmd_exitcode = ex; } else { - fg_pipe->cmds[i].is_stopped = 1; fg_pipe->stopped_cmds++; } 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 <= 0) { + if (fg_pipe->alive_cmds == fg_pipe->stopped_cmds) { /* All processes in fg pipe have exited or stopped */ + 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;) /* 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" */ if (G_interactive_fd) { #if ENABLE_HUSH_JOB - if (fg_pipe->alive_cmds) + if (fg_pipe->alive_cmds != 0) insert_bg_job(fg_pipe); #endif return rcode; } - if (!fg_pipe->alive_cmds) + if (fg_pipe->alive_cmds == 0) return rcode; } /* There are still running processes in the fg pipe */ @@ -6340,7 +6824,6 @@ static int checkjobs(struct pipe *fg_pipe) } } else { /* child stopped */ - pi->cmds[i].is_stopped = 1; pi->stopped_cmds++; } #endif @@ -6381,7 +6864,7 @@ static int checkjobs_and_fg_shell(struct pipe *fg_pipe) * cmd ; ... { list } ; ... * cmd && ... { list } && ... * cmd || ... { list } || ... - * If it is, then we can run cmd as a builtin, NOFORK [do we do this?], + * If it is, then we can run cmd as a builtin, NOFORK, * or (if SH_STANDALONE) an applet, and we can run the { list } * with run_list. If it isn't one of these, we fork and exec cmd. * @@ -6391,7 +6874,7 @@ static int checkjobs_and_fg_shell(struct pipe *fg_pipe) * subshell: ( list ) [&] */ #if !ENABLE_HUSH_MODE_X -#define redirect_and_varexp_helper(new_env_p, old_vars_p, command, squirrel, char argv_expanded) \ +#define redirect_and_varexp_helper(new_env_p, old_vars_p, command, squirrel, argv_expanded) \ redirect_and_varexp_helper(new_env_p, old_vars_p, command, squirrel) #endif static int redirect_and_varexp_helper(char ***new_env_p, @@ -6431,6 +6914,13 @@ static NOINLINE int run_pipe(struct pipe *pi) debug_printf_exec("run_pipe start: members:%d\n", pi->num_cmds); debug_enter(); + /* Testcase: set -- q w e; (IFS='' echo "$*"; IFS=''; echo "$*"); echo "$*" + * Result should be 3 lines: q w e, qwe, q w e + */ + G.ifs = get_local_var_value("IFS"); + if (!G.ifs) + G.ifs = defifs; + IF_HUSH_JOB(pi->pgrp = -1;) pi->stopped_cmds = 0; command = &pi->cmds[0]; @@ -6530,7 +7020,7 @@ static NOINLINE int run_pipe(struct pipe *pi) if (G_x_mode) bb_putchar_stderr('+'); while (*argv) { - char *p = expand_string_to_string(*argv); + char *p = expand_string_to_string(*argv, /*unbackslash:*/ 1); if (G_x_mode) fprintf(stderr, " %s", p); debug_printf_exec("set shell var:'%s'->'%s'\n", @@ -6556,13 +7046,12 @@ static NOINLINE int run_pipe(struct pipe *pi) } /* Expand the rest into (possibly) many strings each */ - if (0) {} #if ENABLE_HUSH_BASH_COMPAT - else if (command->cmd_type == CMD_SINGLEWORD_NOGLOB) { + if (command->cmd_type == CMD_SINGLEWORD_NOGLOB) { argv_expanded = expand_strvec_to_strvec_singleword_noglob(argv + command->assignment_cnt); - } + } else #endif - else { + { argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt); } @@ -6592,6 +7081,7 @@ static NOINLINE int run_pipe(struct pipe *pi) if (!funcp) { debug_printf_exec(": builtin '%s' '%s'...\n", x->b_cmd, argv_expanded[1]); + fflush_all(); rcode = x->b_function(argv_expanded) & 0xff; fflush_all(); } @@ -6624,7 +7114,7 @@ static NOINLINE int run_pipe(struct pipe *pi) return rcode; } - if (ENABLE_FEATURE_SH_STANDALONE) { + if (ENABLE_FEATURE_SH_NOFORK) { int n = find_applet_by_name(argv_expanded[0]); if (n >= 0 && APPLET_IS_NOFORK(n)) { rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, squirrel, argv_expanded); @@ -6713,9 +7203,6 @@ static NOINLINE int run_pipe(struct pipe *pi) if (setup_redirects(command, NULL)) _exit(1); - /* Restore default handlers just prior to exec */ - /*signal(SIGCHLD, SIG_DFL); - so far we don't have any handlers */ - /* Stores to nommu_save list of env vars putenv'ed * (NOMMU, on MMU we don't need that) */ /* cast away volatility... */ @@ -6769,94 +7256,6 @@ static NOINLINE int run_pipe(struct pipe *pi) return -1; } -#ifndef debug_print_tree -static void debug_print_tree(struct pipe *pi, int lvl) -{ - static const char *const PIPE[] = { - [PIPE_SEQ] = "SEQ", - [PIPE_AND] = "AND", - [PIPE_OR ] = "OR" , - [PIPE_BG ] = "BG" , - }; - static const char *RES[] = { - [RES_NONE ] = "NONE" , -# if ENABLE_HUSH_IF - [RES_IF ] = "IF" , - [RES_THEN ] = "THEN" , - [RES_ELIF ] = "ELIF" , - [RES_ELSE ] = "ELSE" , - [RES_FI ] = "FI" , -# endif -# if ENABLE_HUSH_LOOPS - [RES_FOR ] = "FOR" , - [RES_WHILE] = "WHILE", - [RES_UNTIL] = "UNTIL", - [RES_DO ] = "DO" , - [RES_DONE ] = "DONE" , -# endif -# if ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE - [RES_IN ] = "IN" , -# endif -# if ENABLE_HUSH_CASE - [RES_CASE ] = "CASE" , - [RES_CASE_IN ] = "CASE_IN" , - [RES_MATCH] = "MATCH", - [RES_CASE_BODY] = "CASE_BODY", - [RES_ESAC ] = "ESAC" , -# endif - [RES_XXXX ] = "XXXX" , - [RES_SNTX ] = "SNTX" , - }; - static const char *const CMDTYPE[] = { - "{}", - "()", - "[noglob]", -# if ENABLE_HUSH_FUNCTIONS - "func()", -# endif - }; - - int pin, prn; - - pin = 0; - while (pi) { - fprintf(stderr, "%*spipe %d res_word=%s followup=%d %s\n", lvl*2, "", - pin, RES[pi->res_word], pi->followup, PIPE[pi->followup]); - prn = 0; - while (prn < pi->num_cmds) { - struct command *command = &pi->cmds[prn]; - char **argv = command->argv; - - fprintf(stderr, "%*s cmd %d assignment_cnt:%d", - lvl*2, "", prn, - command->assignment_cnt); - if (command->group) { - fprintf(stderr, " group %s: (argv=%p)%s%s\n", - CMDTYPE[command->cmd_type], - argv -# if !BB_MMU - , " group_as_string:", command->group_as_string -# else - , "", "" -# endif - ); - debug_print_tree(command->group, lvl+1); - prn++; - continue; - } - if (argv) while (*argv) { - fprintf(stderr, " '%s'", *argv); - argv++; - } - fprintf(stderr, "\n"); - prn++; - } - pi = pi->next; - pin++; - } -} -#endif /* debug_print_tree */ - /* NB: called by pseudo_exec, and therefore must not modify any * global data until exec/_exit (we can be a child after vfork!) */ static int run_list(struct pipe *pi) @@ -6877,7 +7276,7 @@ static int run_list(struct pipe *pi) enum { cond_code = 0 }; #endif #if HAS_KEYWORDS - smallint rword; /* enum reserved_style */ + smallint rword; /* RES_foo */ smallint last_rword; /* ditto */ #endif @@ -6886,27 +7285,30 @@ static int run_list(struct pipe *pi) #if ENABLE_HUSH_LOOPS /* Check syntax for "for" */ - for (struct pipe *cpipe = pi; cpipe; cpipe = cpipe->next) { - if (cpipe->res_word != RES_FOR && cpipe->res_word != RES_IN) - continue; - /* current word is FOR or IN (BOLD in comments below) */ - if (cpipe->next == NULL) { - syntax_error("malformed for"); - debug_leave(); - debug_printf_exec("run_list lvl %d return 1\n", G.run_list_level); - return 1; - } - /* "FOR v; do ..." and "for v IN a b; do..." are ok */ - if (cpipe->next->res_word == RES_DO) - continue; - /* next word is not "do". It must be "in" then ("FOR v in ...") */ - if (cpipe->res_word == RES_IN /* "for v IN a b; not_do..."? */ - || cpipe->next->res_word != RES_IN /* FOR v not_do_and_not_in..."? */ - ) { - syntax_error("malformed for"); - debug_leave(); - debug_printf_exec("run_list lvl %d return 1\n", G.run_list_level); - return 1; + { + struct pipe *cpipe; + for (cpipe = pi; cpipe; cpipe = cpipe->next) { + if (cpipe->res_word != RES_FOR && cpipe->res_word != RES_IN) + continue; + /* current word is FOR or IN (BOLD in comments below) */ + if (cpipe->next == NULL) { + syntax_error("malformed for"); + debug_leave(); + debug_printf_exec("run_list lvl %d return 1\n", G.run_list_level); + return 1; + } + /* "FOR v; do ..." and "for v IN a b; do..." are ok */ + if (cpipe->next->res_word == RES_DO) + continue; + /* next word is not "do". It must be "in" then ("FOR v in ...") */ + if (cpipe->res_word == RES_IN /* "for v IN a b; not_do..."? */ + || cpipe->next->res_word != RES_IN /* FOR v not_do_and_not_in..."? */ + ) { + syntax_error("malformed for"); + debug_leave(); + debug_printf_exec("run_list lvl %d return 1\n", G.run_list_level); + return 1; + } } } #endif @@ -7034,7 +7436,7 @@ static int run_list(struct pipe *pi) /* all prev words didn't match, does this one match? */ argv = pi->cmds->argv; while (*argv) { - char *pattern = expand_string_to_string(*argv); + char *pattern = expand_string_to_string(*argv, /*unbackslash:*/ 1); /* TODO: which FNM_xxx flags to use? */ cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0); free(pattern); @@ -7078,7 +7480,7 @@ static int run_list(struct pipe *pi) * and we don't need to wait for anything. */ G.last_exitcode = rcode; debug_printf_exec(": builtin/func exitcode %d\n", rcode); - check_and_run_traps(0); + check_and_run_traps(); #if ENABLE_HUSH_LOOPS /* Was it "break" or "continue"? */ if (G.flag_break_continue) { @@ -7110,7 +7512,7 @@ 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(0); + check_and_run_traps(); #if ENABLE_HUSH_JOB if (G.run_list_level == 1) insert_bg_job(pi); @@ -7125,13 +7527,13 @@ static int run_list(struct pipe *pi) /* 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(0); + check_and_run_traps(); } else #endif { /* This one just waits for completion */ rcode = checkjobs(pi); debug_printf_exec(": checkjobs exitcode %d\n", rcode); - check_and_run_traps(0); + check_and_run_traps(); } G.last_exitcode = rcode; } @@ -7144,7 +7546,10 @@ static int run_list(struct pipe *pi) #endif #if ENABLE_HUSH_LOOPS /* Beware of "while false; true; do ..."! */ - if (pi->next && pi->next->res_word == RES_DO) { + if (pi->next + && (pi->next->res_word == RES_DO || pi->next->res_word == RES_DONE) + /* check for RES_DONE is needed for "while ...; do \n done" case */ + ) { if (rword == RES_WHILE) { if (rcode) { /* "while false; do...done" - exitcode 0 */ @@ -7189,7 +7594,7 @@ static int run_and_free_list(struct pipe *pi) { int rcode = 0; debug_printf_exec("run_and_free_list entered\n"); - if (!G.n_mode) { + if (!G.o_opt[OPT_O_NOEXEC]) { debug_printf_exec(": run_list: 1st pipe with %d cmds\n", pi->num_cmds); rcode = run_list(pi); } @@ -7202,98 +7607,119 @@ static int run_and_free_list(struct pipe *pi) } +static void install_sighandlers(unsigned mask) +{ + sighandler_t old_handler; + unsigned sig = 0; + while ((mask >>= 1) != 0) { + sig++; + if (!(mask & 1)) + continue; + old_handler = install_sighandler(sig, pick_sighandler(sig)); + /* POSIX allows shell to re-enable SIGCHLD + * even if it was SIG_IGN on entry. + * Therefore we skip IGN check for it: + */ + if (sig == SIGCHLD) + continue; + if (old_handler == SIG_IGN) { + /* oops... restore back to IGN, and record this fact */ + install_sighandler(sig, old_handler); + if (!G.traps) + G.traps = xzalloc(sizeof(G.traps[0]) * NSIG); + free(G.traps[sig]); + G.traps[sig] = xzalloc(1); /* == xstrdup(""); */ + } + } +} + /* Called a few times only (or even once if "sh -c") */ -static void init_sigmasks(void) +static void install_special_sighandlers(void) { - unsigned sig; unsigned mask; - sigset_t old_blocked_set; - if (!G.inherited_set_is_saved) { - sigprocmask(SIG_SETMASK, NULL, &G.blocked_set); - G.inherited_set = G.blocked_set; - } - old_blocked_set = G.blocked_set; - - mask = (1 << SIGQUIT); + /* Which signals are shell-special? */ + mask = (1 << SIGQUIT) | (1 << SIGCHLD); if (G_interactive_fd) { - mask = (1 << SIGQUIT) | SPECIAL_INTERACTIVE_SIGS; + mask |= SPECIAL_INTERACTIVE_SIGS; if (G_saved_tty_pgrp) /* we have ctty, job control sigs work */ - mask |= SPECIAL_JOB_SIGS; + mask |= SPECIAL_JOBSTOP_SIGS; } - G.non_DFL_mask = mask; - - sig = 0; - while (mask) { - if (mask & 1) - sigaddset(&G.blocked_set, sig); - mask >>= 1; - sig++; + /* Careful, do not re-install handlers we already installed */ + if (G.special_sig_mask != mask) { + unsigned diff = mask & ~G.special_sig_mask; + G.special_sig_mask = mask; + install_sighandlers(diff); } - sigdelset(&G.blocked_set, SIGCHLD); - - if (memcmp(&old_blocked_set, &G.blocked_set, sizeof(old_blocked_set)) != 0) - sigprocmask(SIG_SETMASK, &G.blocked_set, NULL); - - /* POSIX allows shell to re-enable SIGCHLD - * even if it was SIG_IGN on entry */ -#if ENABLE_HUSH_FAST - G.count_SIGCHLD++; /* ensure it is != G.handled_SIGCHLD */ - if (!G.inherited_set_is_saved) - signal(SIGCHLD, SIGCHLD_handler); -#else - if (!G.inherited_set_is_saved) - signal(SIGCHLD, SIG_DFL); -#endif - - G.inherited_set_is_saved = 1; } #if ENABLE_HUSH_JOB /* helper */ -static void maybe_set_to_sigexit(int sig) -{ - void (*handler)(int); - /* non_DFL_mask'ed signals are, well, masked, - * no need to set handler for them. - */ - if (!((G.non_DFL_mask >> sig) & 1)) { - handler = signal(sig, sigexit); - if (handler == SIG_IGN) /* oops... restore back to IGN! */ - signal(sig, handler); - } -} /* Set handlers to restore tty pgrp and exit */ -static void set_fatal_handlers(void) -{ - /* We _must_ restore tty pgrp on fatal signals */ - if (HUSH_DEBUG) { - maybe_set_to_sigexit(SIGILL ); - maybe_set_to_sigexit(SIGFPE ); - maybe_set_to_sigexit(SIGBUS ); - maybe_set_to_sigexit(SIGSEGV); - maybe_set_to_sigexit(SIGTRAP); - } /* else: hush is perfect. what SEGV? */ - maybe_set_to_sigexit(SIGABRT); +static void install_fatal_sighandlers(void) +{ + unsigned mask; + + /* We will restore tty pgrp on these signals */ + mask = 0 + + (1 << SIGILL ) * HUSH_DEBUG + + (1 << SIGFPE ) * HUSH_DEBUG + + (1 << SIGBUS ) * HUSH_DEBUG + + (1 << SIGSEGV) * HUSH_DEBUG + + (1 << SIGTRAP) * HUSH_DEBUG + + (1 << SIGABRT) /* bash 3.2 seems to handle these just like 'fatal' ones */ - maybe_set_to_sigexit(SIGPIPE); - maybe_set_to_sigexit(SIGALRM); - /* if we are interactive, SIGHUP, SIGTERM and SIGINT are masked. + + (1 << SIGPIPE) + + (1 << SIGALRM) + /* if we are interactive, SIGHUP, SIGTERM and SIGINT are special sigs. * if we aren't interactive... but in this case - * we never want to restore pgrp on exit, and this fn is not called */ - /*maybe_set_to_sigexit(SIGHUP );*/ - /*maybe_set_to_sigexit(SIGTERM);*/ - /*maybe_set_to_sigexit(SIGINT );*/ + * we never want to restore pgrp on exit, and this fn is not called + */ + /*+ (1 << SIGHUP )*/ + /*+ (1 << SIGTERM)*/ + /*+ (1 << SIGINT )*/ + ; + G_fatal_sig_mask = mask; + + install_sighandlers(mask); } #endif -static int set_mode(const char cstate, const char mode) +static int set_mode(int state, char mode, const char *o_opt) { - int state = (cstate == '-' ? 1 : 0); + int idx; switch (mode) { - case 'n': G.n_mode = state; break; - case 'x': IF_HUSH_MODE_X(G_x_mode = state;) break; - default: return EXIT_FAILURE; + case 'n': + G.o_opt[OPT_O_NOEXEC] = state; + break; + case 'x': + IF_HUSH_MODE_X(G_x_mode = state;) + break; + case 'o': + if (!o_opt) { + /* "set -+o" without parameter. + * in bash, set -o produces this output: + * pipefail off + * and set +o: + * set +o pipefail + * We always use the second form. + */ + const char *p = o_opt_strings; + idx = 0; + while (*p) { + printf("set %co %s\n", (G.o_opt[idx] ? '-' : '+'), p); + idx++; + p += strlen(p) + 1; + } + break; + } + idx = index_in_strings(o_opt_strings, o_opt); + if (idx >= 0) { + G.o_opt[idx] = state; + break; + } + default: + return EXIT_FAILURE; } return EXIT_SUCCESS; } @@ -7301,28 +7727,37 @@ static int set_mode(const char cstate, const char mode) int hush_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int hush_main(int argc, char **argv) { + enum { + OPT_login = (1 << 0), + }; + unsigned flags; int opt; unsigned builtin_argc; char **e; struct variable *cur_var; + struct variable *shell_ver; INIT_G(); - if (EXIT_SUCCESS) /* if EXIT_SUCCESS == 0, it is already done */ + if (EXIT_SUCCESS != 0) /* if EXIT_SUCCESS == 0, it is already done */ G.last_exitcode = EXIT_SUCCESS; +#if ENABLE_HUSH_FAST + G.count_SIGCHLD++; /* ensure it is != G.handled_SIGCHLD */ +#endif #if !BB_MMU G.argv0_for_re_execing = argv[0]; #endif /* Deal with HUSH_VERSION */ - G.shell_ver.flg_export = 1; - G.shell_ver.flg_read_only = 1; - /* Code which handles ${var/P/R} needs writable values for all variables, + shell_ver = xzalloc(sizeof(*shell_ver)); + shell_ver->flg_export = 1; + shell_ver->flg_read_only = 1; + /* Code which handles ${var...} needs writable values for all variables, * therefore we xstrdup: */ - G.shell_ver.varstr = xstrdup(hush_version_str), - G.top_var = &G.shell_ver; + shell_ver->varstr = xstrdup(hush_version_str); /* Create shell local variables from the values * currently living in the environment */ debug_printf_env("unsetenv '%s'\n", "HUSH_VERSION"); unsetenv("HUSH_VERSION"); /* in case it exists in initial env */ + G.top_var = shell_ver; cur_var = G.top_var; e = environ; if (e) while (*e) { @@ -7337,8 +7772,8 @@ int hush_main(int argc, char **argv) e++; } /* (Re)insert HUSH_VERSION into env (AFTER we scanned the env!) */ - debug_printf_env("putenv '%s'\n", G.shell_ver.varstr); - putenv(G.shell_ver.varstr); + debug_printf_env("putenv '%s'\n", shell_ver->varstr); + putenv(shell_ver->varstr); /* Export PWD */ set_pwd_var(/*exp:*/ 1); @@ -7382,8 +7817,7 @@ int hush_main(int argc, char **argv) #if ENABLE_FEATURE_EDITING G.line_input_state = new_line_input_t(FOR_SHELL); #endif - G.global_argc = argc; - G.global_argv = argv; + /* Initialize some more globals to non-zero values */ cmdedit_update_prompt(); @@ -7395,17 +7829,18 @@ int hush_main(int argc, char **argv) } /* Shell is non-interactive at first. We need to call - * init_sigmasks() if we are going to execute "sh