X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=shell%2Fhush.c;h=32b90876fde26c265a53b61af9678d9ca8661173;hb=a6ad397ea92cd9c53973243728d3e52640fe63ec;hp=2d333d73103a4901ce57119601b0d035df1c4fb0;hpb=4ebc76c8a23367eaec29931b77e10e3ee890dd7d;p=oweals%2Fbusybox.git diff --git a/shell/hush.c b/shell/hush.c index 2d333d731..32b90876f 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -37,29 +37,32 @@ * handle the recursion implicit in the various substitutions, especially * across continuation lines. * - * POSIX syntax not implemented: + * TODOs: + * grep for "TODO" and fix (some of them are easy) + * special variables (done: PWD, PPID, RANDOM) + * tilde expansion * aliases - * <(list) and >(list) Process Substitution - * Tilde Expansion + * follow IFS rules more precisely, including update semantics + * builtins mandated by standards we don't support: + * [un]alias, command, fc, getopts, newgrp, readonly, times + * make complex ${var%...} constructs support optional + * make here documents optional * - * Bash stuff (optionally enabled): - * &> and >& redirection of stdout+stderr - * Brace Expansion - * reserved words: [[ ]] function select - * substrings ${var:1:5} + * 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) + * =~: regex operator * let EXPR [EXPR...] - * Each EXPR is an arithmetic expression (ARITHMETIC EVALUATION) - * If the last arg evaluates to 0, let returns 1; 0 otherwise. - * NB: let `echo 'a=a + 1'` - error (IOW: multi-word expansion is used) + * Each EXPR is an arithmetic expression (ARITHMETIC EVALUATION) + * If the last arg evaluates to 0, let returns 1; 0 otherwise. + * NB: let `echo 'a=a + 1'` - error (IOW: multi-word expansion is used) * ((EXPR)) - * The EXPR is evaluated according to ARITHMETIC EVALUATION. - * This is exactly equivalent to let "expression". - * - * TODOs: - * grep for "TODO" and fix (some of them are easy) - * builtins: ulimit - * special variables (done: PWD) - * follow IFS rules more precisely, including update semantics + * 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 * and therefore expansion of them should be "one-word" expansion: * $ export i=`echo 'a b'` # export has one arg: "i=a b" @@ -84,6 +87,10 @@ #if ENABLE_HUSH_CASE # include #endif + +#include "shell_common.h" +#include "builtin_read.h" +#include "builtin_ulimit.h" #include "math.h" #include "match.h" #if ENABLE_HUSH_RANDOM_SUPPORT @@ -122,14 +129,18 @@ # define USE_FOR_MMU(...) #endif -#if defined SINGLE_APPLET_MAIN +#define SKIP_definitions 1 +#include "applet_tables.h" +#undef SKIP_definitions +#if NUM_APPLETS == 1 /* STANDALONE does not make sense, and won't compile */ # undef CONFIG_FEATURE_SH_STANDALONE # undef ENABLE_FEATURE_SH_STANDALONE # undef IF_FEATURE_SH_STANDALONE +# undef IF_NOT_FEATURE_SH_STANDALONE +# define ENABLE_FEATURE_SH_STANDALONE 0 # define IF_FEATURE_SH_STANDALONE(...) # define IF_NOT_FEATURE_SH_STANDALONE(...) __VA_ARGS__ -# define ENABLE_FEATURE_SH_STANDALONE 0 #endif #if !ENABLE_HUSH_INTERACTIVE @@ -454,9 +465,9 @@ struct function { char *name; struct command *parent_cmd; struct pipe *body; -#if !BB_MMU +# if !BB_MMU char *body_as_string; -#endif +# endif }; #endif @@ -520,6 +531,7 @@ struct globals { 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; @@ -556,7 +568,7 @@ struct globals { unsigned long memleak_value; int debug_indent; #endif - char user_input_buf[ENABLE_FEATURE_EDITING ? BUFSIZ : 2]; + char user_input_buf[ENABLE_FEATURE_EDITING ? CONFIG_FEATURE_EDITING_MAX_LEN : 2]; }; #define G (*ptr_to_globals) /* Not #defining name to G.name - this quickly gets unwieldy @@ -617,10 +629,10 @@ static int builtin_return(char **argv) FAST_FUNC; * For example, 'unset foo | whatever' will parse and run, but foo will * still be set at the end. */ struct built_in_command { - const char *cmd; - int (*function)(char **argv) FAST_FUNC; + const char *b_cmd; + int (*b_function)(char **argv) FAST_FUNC; #if ENABLE_HUSH_HELP - const char *descr; + const char *b_descr; # define BLTIN(cmd, func, help) { cmd, func, help } #else # define BLTIN(cmd, func, help) { cmd, func } @@ -665,9 +677,12 @@ static const struct built_in_command bltins1[] = { #endif BLTIN("set" , builtin_set , "Set/unset positional parameters"), BLTIN("shift" , builtin_shift , "Shift positional parameters"), +#if ENABLE_HUSH_BASH_COMPAT + BLTIN("source" , builtin_source , "Run commands in a file"), +#endif BLTIN("trap" , builtin_trap , "Trap signals"), - BLTIN("type" , builtin_type , "Write a description of command type"), -// BLTIN("ulimit" , builtin_ulimit , "Control resource limits"), + BLTIN("type" , builtin_type , "Show command type"), + BLTIN("ulimit" , shell_builtin_ulimit , "Control resource limits"), BLTIN("umask" , builtin_umask , "Set file creation mask"), BLTIN("unset" , builtin_unset , "Unset variables"), BLTIN("wait" , builtin_wait , "Wait for process"), @@ -889,28 +904,6 @@ static void cmdedit_update_prompt(void); /* Utility functions */ -static int glob_needed(const char *s) -{ - while (*s) { - if (*s == '\\') - s++; - if (*s == '*' || *s == '[' || *s == '?') - return 1; - s++; - } - return 0; -} - -static int is_well_formed_var_name(const char *s, char terminator) -{ - if (!s || !(isalpha(*s) || *s == '_')) - return 0; - s++; - while (isalnum(*s) || *s == '_') - s++; - return *s == terminator; -} - /* Replace each \x with x in place, return ptr past NUL. */ static char *unbackslash(char *src) { @@ -1062,7 +1055,8 @@ static void restore_G_args(save_arg_t *sv, char **argv) * * Trap handlers will execute even within trap handlers. (right?) * - * User trap handlers are forgotten when subshell ("(cmd)") is entered. + * User trap handlers are forgotten when subshell ("(cmd)") is entered, + * except for handlers set to '' (empty string). * * If job control is off, backgrounded commands ("cmd &") * have SIGINT, SIGQUIT set to SIG_IGN. @@ -1118,7 +1112,7 @@ static void restore_G_args(save_arg_t *sv, char **argv) * after [v]fork, if we plan to be a shell: * unblock signals with special interactive handling * (child shell is not interactive), - * unset all traps (note: regardless of child shell's type - {}, (), etc) + * 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. * Restore blocked signal set to one inherited by shell just prior to exec. @@ -1130,7 +1124,7 @@ static void restore_G_args(save_arg_t *sv, char **argv) * Note 2 (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. + * are set to '' (ignore) are NOT reset to defaults. We do the same. */ enum { SPECIAL_INTERACTIVE_SIGS = 0 @@ -1206,7 +1200,7 @@ static void hush_exit(int exitcode) } #if ENABLE_HUSH_JOB - fflush(NULL); /* flush all streams */ + fflush_all(); sigexit(- (exitcode & 0xff)); #else exit(exitcode); @@ -1317,7 +1311,7 @@ static struct variable *get_local_var(const char *name) return NULL; } -static const char *get_local_var_value(const char *name) +static const char* FAST_FUNC get_local_var_value(const char *name) { struct variable **pp = get_ptr_to_local_var(name); if (pp) @@ -1520,7 +1514,7 @@ static void unset_vars(char **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 *endofname(const char *name) +static char* FAST_FUNC endofname(const char *name) { char *p; @@ -1533,14 +1527,13 @@ static char *endofname(const char *name) } return p; } +#endif -static void arith_set_local_var(const char *name, const char *val, int flags) +static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val) { - /* arith code doesnt malloc space, so do it for it */ char *var = xasprintf("%s=%s", name, val); - set_local_var(var, flags, /*lvl:*/ 0, /*ro:*/ 0); + set_local_var(var, /*flags:*/ 0, /*lvl:*/ 0, /*ro:*/ 0); } -#endif /* @@ -1666,7 +1659,7 @@ static void get_user_input(struct in_str *i) 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, BUFSIZ-1, G.line_input_state); + r = read_line_input(prompt_str, G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1, G.line_input_state); /* catch *SIGINT* etc (^C is handled by read_line_input) */ check_and_run_traps(0); } while (r == 0 || G.flag_SIGINT); /* repeat if ^C or SIGINT */ @@ -1679,7 +1672,7 @@ static void get_user_input(struct in_str *i) do { G.flag_SIGINT = 0; fputs(prompt_str, stdout); - fflush(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) @@ -1856,13 +1849,31 @@ static void o_addblock_duplicate_backslash(o_string *o, const char *str, int len } } +#undef HUSH_BRACE_EXP +/* + * HUSH_BRACE_EXP 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), + * but NOT brace expansion! Thus, there should be TWO independent + * quoting mechanisms on $v expansion side: one protects + * $v from brace expansion, and other additionally protects "$v" against globbing. + * We have only second one. + */ + +#ifdef HUSH_BRACE_EXP +# define MAYBE_BRACES "{}" +#else +# define MAYBE_BRACES "" +#endif + /* My analysis of quoting semantics tells me that state information * is associated with a destination, not a source. */ static void o_addqchr(o_string *o, int ch) { int sz = 1; - char *found = strchr("*?[\\", ch); + char *found = strchr("*?[\\" MAYBE_BRACES, ch); if (found) sz++; o_grow_by(o, sz); @@ -1878,7 +1889,7 @@ 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("*?[\\", ch)) { + if (o->o_escape && strchr("*?[\\" MAYBE_BRACES, ch)) { sz++; o->data[o->length] = '\\'; o->length++; @@ -1898,7 +1909,7 @@ static void o_addQstr(o_string *o, const char *str, int len) while (len) { char ch; int sz; - int ordinary_cnt = strcspn(str, "*?[\\"); + int ordinary_cnt = strcspn(str, "*?[\\" MAYBE_BRACES); if (ordinary_cnt > len) /* paranoia */ ordinary_cnt = len; o_addblock(o, str, ordinary_cnt); @@ -1909,7 +1920,7 @@ static void o_addQstr(o_string *o, const char *str, int len) ch = *str++; sz = 1; - if (ch) { /* it is necessarily one of "*?[\\" */ + if (ch) { /* it is necessarily one of "*?[\\" MAYBE_BRACES */ sz++; o->data[o->length] = '\\'; o->length++; @@ -2003,8 +2014,223 @@ static int o_get_last_ptr(o_string *o, int n) return ((int)(ptrdiff_t)list[n-1]) + string_start; } -/* o_glob performs globbing on last list[], saving each result - * as a new list[]. */ +#ifdef HUSH_BRACE_EXP +/* 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 + * existing files. Need to re-implement it. + */ + +/* Helper */ +static int glob_needed(const char *s) +{ + while (*s) { + if (*s == '\\') { + if (!s[1]) + return 0; + s += 2; + continue; + } + if (*s == '*' || *s == '[' || *s == '?' || *s == '{') + return 1; + s++; + } + return 0; +} +/* Return pointer to next closing brace or to comma */ +static const char *next_brace_sub(const char *cp) +{ + unsigned depth = 0; + cp++; + while (*cp != '\0') { + if (*cp == '\\') { + if (*++cp == '\0') + break; + cp++; + continue; + } + /*{*/ if ((*cp == '}' && depth-- == 0) || (*cp == ',' && depth == 0)) + break; + if (*cp++ == '{') /*}*/ + depth++; + } + + return *cp != '\0' ? cp : NULL; +} +/* Recursive brace globber. Note: may garble pattern[]. */ +static int glob_brace(char *pattern, o_string *o, int n) +{ + char *new_pattern_buf; + const char *begin; + const char *next; + const char *rest; + const char *p; + size_t rest_len; + + debug_printf_glob("glob_brace('%s')\n", pattern); + + begin = pattern; + while (1) { + if (*begin == '\0') + goto simple_glob; + if (*begin == '{') /*}*/ { + /* Find the first sub-pattern and at the same time + * find the rest after the closing brace */ + next = next_brace_sub(begin); + if (next == NULL) { + /* An illegal expression */ + goto simple_glob; + } + /*{*/ if (*next == '}') { + /* "{abc}" with no commas - illegal + * brace expr, disregard and skip it */ + begin = next + 1; + continue; + } + break; + } + if (*begin == '\\' && begin[1] != '\0') + begin++; + begin++; + } + debug_printf_glob("begin:%s\n", begin); + debug_printf_glob("next:%s\n", next); + + /* Now find the end of the whole brace expression */ + rest = next; + /*{*/ while (*rest != '}') { + rest = next_brace_sub(rest); + if (rest == NULL) { + /* An illegal expression */ + goto simple_glob; + } + debug_printf_glob("rest:%s\n", rest); + } + rest_len = strlen(++rest) + 1; + + /* We are sure the brace expression is well-formed */ + + /* Allocate working buffer large enough for our work */ + new_pattern_buf = xmalloc(strlen(pattern)); + + /* We have a brace expression. BEGIN points to the opening {, + * NEXT points past the terminator of the first element, and REST + * points past the final }. We will accumulate result names from + * recursive runs for each brace alternative in the buffer using + * GLOB_APPEND. */ + + p = begin + 1; + while (1) { + /* Construct the new glob expression */ + memcpy( + mempcpy( + mempcpy(new_pattern_buf, + /* We know the prefix for all sub-patterns */ + pattern, begin - pattern), + p, next - p), + rest, rest_len); + + /* Note: glob_brace() may garble new_pattern_buf[]. + * That's why we re-copy prefix every time (1st memcpy above). + */ + n = glob_brace(new_pattern_buf, o, n); + /*{*/ if (*next == '}') { + /* We saw the last entry */ + break; + } + p = next + 1; + next = next_brace_sub(next); + } + free(new_pattern_buf); + return n; + + simple_glob: + { + int gr; + glob_t globdata; + + memset(&globdata, 0, sizeof(globdata)); + gr = glob(pattern, 0, NULL, &globdata); + debug_printf_glob("glob('%s'):%d\n", pattern, gr); + if (gr != 0) { + if (gr == GLOB_NOMATCH) { + globfree(&globdata); + /* NB: garbles parameter */ + unbackslash(pattern); + o_addstr_with_NUL(o, pattern); + debug_printf_glob("glob pattern '%s' is literal\n", pattern); + return o_save_ptr_helper(o, n); + } + if (gr == GLOB_NOSPACE) + bb_error_msg_and_die(bb_msg_memory_exhausted); + /* GLOB_ABORTED? Only happens with GLOB_ERR flag, + * but we didn't specify it. Paranoia again. */ + bb_error_msg_and_die("glob error %d on '%s'", gr, pattern); + } + if (globdata.gl_pathv && globdata.gl_pathv[0]) { + char **argv = globdata.gl_pathv; + while (1) { + o_addstr_with_NUL(o, *argv); + n = o_save_ptr_helper(o, n); + argv++; + if (!*argv) + break; + } + } + globfree(&globdata); + } + return n; +} +/* Performs globbing on last list[], + * saving each result as a new list[]. + */ +static int o_glob(o_string *o, int n) +{ + char *pattern, *copy; + + debug_printf_glob("start o_glob: n:%d o->data:%p\n", n, o->data); + if (!o->data) + return o_save_ptr_helper(o, n); + pattern = o->data + o_get_last_ptr(o, n); + debug_printf_glob("glob pattern '%s'\n", pattern); + if (!glob_needed(pattern)) { + /* unbackslash last string in o in place, fix length */ + o->length = unbackslash(pattern) - o->data; + debug_printf_glob("glob pattern '%s' is literal\n", pattern); + return o_save_ptr_helper(o, n); + } + + copy = xstrdup(pattern); + /* "forget" pattern in o */ + o->length = pattern - o->data; + n = glob_brace(copy, o, n); + free(copy); + if (DEBUG_GLOB) + debug_print_list("o_glob returning", o, n); + return n; +} + +#else + +/* Helper */ +static int glob_needed(const char *s) +{ + while (*s) { + if (*s == '\\') { + if (!s[1]) + return 0; + s += 2; + continue; + } + if (*s == '*' || *s == '[' || *s == '?') + return 1; + s++; + } + return 0; +} +/* Performs globbing on last list[], + * saving each result as a new list[]. + */ static int o_glob(o_string *o, int n) { glob_t globdata; @@ -2018,27 +2244,35 @@ static int o_glob(o_string *o, int n) debug_printf_glob("glob pattern '%s'\n", pattern); if (!glob_needed(pattern)) { literal: + /* unbackslash last string in o in place, fix length */ o->length = unbackslash(pattern) - o->data; debug_printf_glob("glob pattern '%s' is literal\n", pattern); return o_save_ptr_helper(o, n); } memset(&globdata, 0, sizeof(globdata)); + /* Can't use GLOB_NOCHECK: it does not unescape the string. + * If we glob "*.\*" and don't find anything, we need + * to fall back to using literal "*.*", but GLOB_NOCHECK + * will return "*.\*"! + */ gr = glob(pattern, 0, NULL, &globdata); debug_printf_glob("glob('%s'):%d\n", pattern, gr); - if (gr == GLOB_NOSPACE) - bb_error_msg_and_die("out of memory during glob"); - if (gr == GLOB_NOMATCH) { - globfree(&globdata); - goto literal; - } - if (gr != 0) { /* GLOB_ABORTED ? */ - /* TODO: testcase for bad glob pattern behavior */ - bb_error_msg("glob(3) error %d on '%s'", gr, pattern); + if (gr != 0) { + if (gr == GLOB_NOMATCH) { + globfree(&globdata); + goto literal; + } + if (gr == GLOB_NOSPACE) + bb_error_msg_and_die(bb_msg_memory_exhausted); + /* GLOB_ABORTED? Only happens with GLOB_ERR flag, + * but we didn't specify it. Paranoia again. */ + bb_error_msg_and_die("glob error %d on '%s'", gr, pattern); } if (globdata.gl_pathv && globdata.gl_pathv[0]) { char **argv = globdata.gl_pathv; - o->length = pattern - o->data; /* "forget" pattern */ + /* "forget" pattern in o */ + o->length = pattern - o->data; while (1) { o_addstr_with_NUL(o, *argv); n = o_save_ptr_helper(o, n); @@ -2053,6 +2287,8 @@ static int o_glob(o_string *o, int n) return n; } +#endif + /* If o->o_glob == 1, glob the string so far remembered. * Otherwise, just finish current list[] and start new */ static int o_save_ptr(o_string *o, int n) @@ -2169,6 +2405,23 @@ static char *expand_pseudo_dquoted(const char *str) return exp_str; } +#if ENABLE_SH_MATH_SUPPORT +static arith_t expand_and_evaluate_arith(const char *arg, int *errcode_p) +{ + arith_eval_hooks_t hooks; + 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); + free(exp_str); + return res; +} +#endif + /* Expand all variable references in given string, adding words to list[] * at n, n+1,... positions. Return updated n (so that list[n] is next one * to be filled). This routine is extremely tricky: has to deal with @@ -2185,7 +2438,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char ored_ch = 0; - debug_printf_expand("expand_vars_to_list: arg '%s'\n", arg); + debug_printf_expand("expand_vars_to_list: arg:'%s' or_mask:%x\n", arg, or_mask); 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); @@ -2193,7 +2446,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char while ((p = strchr(arg, SPECIAL_VAR_SYMBOL)) != NULL) { char first_ch; int i; - char *dyn_val = NULL; + char *to_be_freed = NULL; const char *val = NULL; #if ENABLE_HUSH_TICK o_string subst_result = NULL_O_STRING; @@ -2287,28 +2540,20 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char * expanded result may need to be globbed * and $IFS-splitted */ debug_printf_subst("SUBST '%s' first_ch %x\n", arg, first_ch); - process_command_subs(&subst_result, arg); - debug_printf_subst("SUBST RES '%s'\n", subst_result.data); + G.last_exitcode = process_command_subs(&subst_result, arg); + debug_printf_subst("SUBST RES:%d '%s'\n", G.last_exitcode, subst_result.data); val = subst_result.data; goto store_val; #endif #if ENABLE_SH_MATH_SUPPORT case '+': { /* +cmd */ - arith_eval_hooks_t hooks; arith_t res; int errcode; - char *exp_str; arg++; /* skip '+' */ *p = '\0'; /* replace trailing */ debug_printf_subst("ARITH '%s' first_ch %x\n", arg, first_ch); - - exp_str = expand_pseudo_dquoted(arg); - hooks.lookupvar = get_local_var_value; - hooks.setvar = arith_set_local_var; - hooks.endofname = endofname; - res = arith(exp_str ? exp_str : arg, &errcode, &hooks); - free(exp_str); + res = expand_and_evaluate_arith(arg, &errcode); if (errcode < 0) { const char *msg = "error in arithmetic"; @@ -2333,35 +2578,32 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char #endif default: /* varname */ case_default: { - bool exp_len = false; - bool exp_null = false; char *var = arg; + char exp_len; /* '#' if it's ${#var} */ + char exp_op; char exp_save = exp_save; /* for compiler */ - char exp_op = exp_op; /* for compiler */ + char *exp_saveptr = exp_saveptr; /* points to expansion operator */ char *exp_word = exp_word; /* for compiler */ - size_t exp_off = 0; *p = '\0'; arg[0] = first_ch & 0x7f; /* prepare for expansions */ - if (var[0] == '#') { + exp_op = 0; + exp_len = var[0]; + if (exp_len == '#') { /* handle length expansion ${#var} */ - exp_len = true; - ++var; + var++; } else { /* maybe handle parameter expansion */ - exp_off = strcspn(var, ":-=+?%#"); - if (!var[exp_off]) - exp_off = 0; - if (exp_off) { - exp_save = var[exp_off]; - exp_null = exp_save == ':'; - exp_word = var + exp_off; - if (exp_null) - ++exp_word; + exp_saveptr = var + strcspn(var, "%#:-=+?"); + exp_save = *exp_saveptr; + if (exp_save) { + exp_word = exp_saveptr; + if (exp_save == ':') + exp_word++; exp_op = *exp_word++; - var[exp_off] = '\0'; + *exp_saveptr = '\0'; } } @@ -2376,43 +2618,118 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char val = get_local_var_value(var); /* handle any expansions */ - if (exp_len) { - debug_printf_expand("expand: length of '%s' = ", val); + if (exp_len == '#') { + debug_printf_expand("expand: length(%s)=", val); val = utoa(val ? strlen(val) : 0); debug_printf_expand("%s\n", val); - } else if (exp_off) { + } else if (exp_op) { if (exp_op == '%' || exp_op == '#') { + /* Standard-mandated substring removal ops: + * ${parameter%word} - remove smallest suffix pattern + * ${parameter%%word} - remove largest suffix pattern + * ${parameter#word} - remove smallest prefix pattern + * ${parameter##word} - remove largest prefix pattern + * + * Word is expanded to produce a glob pattern. + * Then var's value is matched to it and matching part removed. + */ if (val) { - /* we need to do a pattern match */ bool match_at_left; char *loc; scan_t scan = pick_scan(exp_op, *exp_word, &match_at_left); if (exp_op == *exp_word) /* ## or %% */ - ++exp_word; - val = dyn_val = xstrdup(val); - loc = scan(dyn_val, exp_word, match_at_left); - if (match_at_left) /* # or ## */ - val = loc; - else if (loc) /* % or %% and match was found */ - *loc = '\0'; + exp_word++; + val = to_be_freed = xstrdup(val); + { + char *exp_exp_word = expand_pseudo_dquoted(exp_word); + if (exp_exp_word) + exp_word = exp_exp_word; + loc = scan(to_be_freed, exp_word, match_at_left); + //bb_error_msg("op:%c str:'%s' pat:'%s' res:'%s'", + // exp_op, to_be_freed, exp_word, loc); + free(exp_exp_word); + } + if (loc) { /* match was found */ + if (match_at_left) /* # or ## */ + val = loc; + else /* % or %% */ + *loc = '\0'; + } } - } else { - /* we need to do an expansion */ - int exp_test = (!val || (exp_null && !val[0])); + } else if (!strchr("%#:-=+?"+3, exp_op)) { +#if ENABLE_HUSH_BASH_COMPAT + /* exp_op is ':' and next char isn't a subst operator. + * Assuming it's ${var:[N][:M]} bashism. + * TODO: N, M can be expressions similar to $((EXPR)): 2+2, 2+var etc + */ + char *end; + unsigned len = INT_MAX; + unsigned beg = 0; + end = --exp_word; + if (*exp_word != ':') /* not ${var::...} */ + beg = bb_strtou(exp_word, &end, 0); + //bb_error_msg("beg:'%s'=%u end:'%s'", exp_word, beg, end); + if (*end == ':') { + if (end[1] != '\0') /* not ${var:NUM:} */ + len = bb_strtou(end + 1, &end, 0); + else { + len = 0; + end++; + } + //bb_error_msg("len:%u end:'%s'", len, end); + } + if (*end == '\0') { + //bb_error_msg("from val:'%s'", val); + if (len == 0 || !val || beg >= strlen(val)) + val = ""; + else + val = to_be_freed = xstrndup(val + beg, len); + //bb_error_msg("val:'%s'", val); + } else +#endif + { + die_if_script("malformed ${%s...}", var); + val = ""; + } + } else { /* one of "-=+?" */ + /* Standard-mandated substitution ops: + * ${var?word} - indicate error if unset + * If var is unset, word (or a message indicating it is unset + * if word is null) is written to standard error + * and the shell exits with a non-zero exit status. + * Otherwise, the value of var is substituted. + * ${var-word} - use default value + * If var is unset, word is substituted. + * ${var=word} - assign and use default value + * If var is unset, word is assigned to var. + * In all cases, final value of var is substituted. + * ${var+word} - use alternative value + * If var is unset, null is substituted. + * Otherwise, word is substituted. + * + * Word is subjected to tilde expansion, parameter expansion, + * command substitution, and arithmetic expansion. + * If word is not needed, it is not expanded. + * + * Colon forms (${var:-word}, ${var:=word} etc) do the same, + * but also treat null var as if it is unset. + */ + int use_word = (!val || ((exp_save == ':') && !val[0])); if (exp_op == '+') - exp_test = !exp_test; + use_word = !use_word; debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op, - exp_null ? "true" : "false", exp_test); - if (exp_test) { + (exp_save == ':') ? "true" : "false", use_word); + if (use_word) { + to_be_freed = expand_pseudo_dquoted(exp_word); + if (to_be_freed) + exp_word = to_be_freed; if (exp_op == '?') { -//TODO: how interactive bash aborts expansion mid-command? - /* ${var?[error_msg_if_unset]} */ - /* ${var:?[error_msg_if_unset_or_null]} */ /* mimic bash message */ die_if_script("%s: %s", var, exp_word[0] ? exp_word : "parameter null or not set" ); +//TODO: how interactive bash aborts expansion mid-command? } else { val = exp_word; } @@ -2431,8 +2748,8 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char } } - var[exp_off] = exp_save; - } + *exp_saveptr = exp_save; + } /* if (exp_op) */ arg[0] = first_ch; #if ENABLE_HUSH_TICK @@ -2457,7 +2774,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char if (val) { o_addQstr(output, val, strlen(val)); } - free(dyn_val); + free(to_be_freed); /* Do the check to avoid writing to a const string */ if (*p != SPECIAL_VAR_SYMBOL) *p = SPECIAL_VAR_SYMBOL; @@ -2686,14 +3003,17 @@ static void re_execute_shell(char ***to_free, const char *s, char *g_argv0, char **g_argv, char **builtin_argv) { - char param_buf[sizeof("-$%x:%x:%x:%x:%x") + sizeof(unsigned) * 2]; +#define NOMMU_HACK_FMT ("-$%x:%x:%x:%x:%x:%llx" IF_HUSH_LOOPS(":%x")) + /* delims + 2 * (number of bytes in printed hex numbers) */ + char param_buf[sizeof(NOMMU_HACK_FMT) + 2 * (sizeof(int)*6 + sizeof(long long)*1)]; char *heredoc_argv[4]; struct variable *cur; -#if ENABLE_HUSH_FUNCTIONS +# if ENABLE_HUSH_FUNCTIONS struct function *funcp; -#endif +# endif char **argv, **pp; unsigned cnt; + unsigned long long empty_trap_mask; if (!g_argv0) { /* heredoc */ argv = heredoc_argv; @@ -2710,15 +3030,26 @@ static void re_execute_shell(char ***to_free, const char *s, if (pp) while (*pp++) cnt++; - sprintf(param_buf, "-$%x:%x:%x:%x:%x" IF_HUSH_LOOPS(":%x") + empty_trap_mask = 0; + if (G.traps) { + int sig; + for (sig = 1; sig < NSIG; sig++) { + if (G.traps[sig] && !G.traps[sig][0]) + empty_trap_mask |= 1LL << sig; + } + } + + sprintf(param_buf, NOMMU_HACK_FMT , (unsigned) G.root_pid , (unsigned) G.root_ppid , (unsigned) G.last_bg_pid , (unsigned) G.last_exitcode , cnt + , empty_trap_mask IF_HUSH_LOOPS(, G.depth_of_loop) ); - /* 1:hush 2:-$::: +#undef NOMMU_HACK_FMT + /* 1:hush 2:-$::: * 3:-c 4: 5: 6:NULL */ cnt += 6; @@ -2726,10 +3057,10 @@ static void re_execute_shell(char ***to_free, const char *s, if (!cur->flg_export || cur->flg_read_only) cnt += 2; } -#if ENABLE_HUSH_FUNCTIONS +# if ENABLE_HUSH_FUNCTIONS for (funcp = G.top_func; funcp; funcp = funcp->next) cnt += 3; -#endif +# endif pp = g_argv; while (*pp++) cnt++; @@ -2747,13 +3078,13 @@ static void re_execute_shell(char ***to_free, const char *s, *pp++ = cur->varstr; } } -#if ENABLE_HUSH_FUNCTIONS +# if ENABLE_HUSH_FUNCTIONS for (funcp = G.top_func; funcp; funcp = funcp->next) { *pp++ = (char *) "-F"; *pp++ = funcp->name; *pp++ = funcp->body_as_string; } -#endif +# endif /* We can pass activated traps here. Say, -Tnn:trap_string * * However, POSIX says that subshells reset signals with traps @@ -2771,7 +3102,9 @@ 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. - * exec syscall below resets them to SIG_DFL for us. + * 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; @@ -3095,7 +3428,7 @@ static const struct built_in_command* find_builtin_helper(const char *name, const struct built_in_command *end) { while (x != end) { - if (strcmp(name, x->cmd) != 0) { + if (strcmp(name, x->b_cmd) != 0) { x++; continue; } @@ -3220,7 +3553,7 @@ static void exec_function(char ***to_free, G.global_argc = n; /* On MMU, funcp->body is always non-NULL */ n = run_list(funcp->body); - fflush(NULL); + fflush_all(); _exit(n); # else re_execute_shell(to_free, @@ -3242,9 +3575,9 @@ static int run_function(const struct function *funcp, char **argv) /* "we are in function, ok to use return" */ sv_flg = G.flag_return_in_progress; G.flag_return_in_progress = -1; -#if ENABLE_HUSH_LOCAL +# if ENABLE_HUSH_LOCAL G.func_nest_level++; -#endif +# endif /* On MMU, funcp->body is always non-NULL */ # if !BB_MMU @@ -3258,7 +3591,7 @@ static int run_function(const struct function *funcp, char **argv) rc = run_list(funcp->body); } -#if ENABLE_HUSH_LOCAL +# if ENABLE_HUSH_LOCAL { struct variable *var; struct variable **var_pp; @@ -3281,7 +3614,7 @@ static int run_function(const struct function *funcp, char **argv) } G.func_nest_level--; } -#endif +# endif G.flag_return_in_progress = sv_flg; restore_G_args(&sv, argv); @@ -3291,13 +3624,13 @@ static int run_function(const struct function *funcp, char **argv) #endif /* ENABLE_HUSH_FUNCTIONS */ -# if BB_MMU +#if BB_MMU #define exec_builtin(to_free, x, argv) \ exec_builtin(x, argv) -# else +#else #define exec_builtin(to_free, x, argv) \ exec_builtin(to_free, argv) -# endif +#endif static void exec_builtin(char ***to_free, const struct built_in_command *x, char **argv) NORETURN; @@ -3305,11 +3638,11 @@ static void exec_builtin(char ***to_free, const struct built_in_command *x, char **argv) { -# if BB_MMU - int rcode = x->function(argv); - fflush(NULL); +#if BB_MMU + int rcode = x->b_function(argv); + fflush_all(); _exit(rcode); -# else +#else /* On NOMMU, we must never block! * Example: { sleep 99 | read line; } & echo Ok */ @@ -3318,10 +3651,20 @@ static void exec_builtin(char ***to_free, G.global_argv[0], G.global_argv + 1, argv); -# endif +#endif } +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); + execvp(argv[0], argv); + bb_perror_msg("can't execute '%s'", argv[0]); + _exit(127); /* bash compat */ +} + #if BB_MMU #define pseudo_exec_argv(nommu_save, argv, assignment_cnt, argv_expanded) \ pseudo_exec_argv(argv, assignment_cnt, argv_expanded) @@ -3337,7 +3680,7 @@ static void exec_builtin(char ***to_free, static void pseudo_exec_argv(nommu_save_t *nommu_save, char **argv, int assignment_cnt, char **argv_expanded) NORETURN; -static void pseudo_exec_argv(nommu_save_t *nommu_save, +static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, char **argv, int assignment_cnt, char **argv_expanded) { @@ -3421,11 +3764,7 @@ static void pseudo_exec_argv(nommu_save_t *nommu_save, #if ENABLE_FEATURE_SH_STANDALONE || BB_MMU skip: #endif - debug_printf_exec("execing '%s'\n", argv[0]); - sigprocmask(SIG_SETMASK, &G.inherited_set, NULL); - execvp(argv[0], argv); - bb_perror_msg("can't execute '%s'", argv[0]); - _exit(EXIT_FAILURE); + execvp_or_die(argv); } /* Called after [v]fork() in run_pipe @@ -3659,9 +3998,7 @@ static int checkjobs(struct pipe* fg_pipe) fg_pipe->alive_cmds--; if (i == fg_pipe->num_cmds - 1) { /* last process gives overall exitstatus */ - /* Note: is WIFSIGNALED, WEXITSTATUS = sig + 128 */ rcode = WEXITSTATUS(status); - IF_HAS_KEYWORDS(if (fg_pipe->pi_inverted) rcode = !rcode;) /* bash prints killer signal's name for *last* * process in pipe (prints just newline for SIGINT). * Mimic this. Example: "sleep 5" + (^\ or kill -QUIT) @@ -3669,7 +4006,11 @@ static int checkjobs(struct pipe* fg_pipe) 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;) } } else { fg_pipe->cmds[i].is_stopped = 1; @@ -3858,7 +4199,8 @@ static NOINLINE int run_pipe(struct pipe *pi) if (argv[command->assignment_cnt] == NULL) { /* Assignments, but no command */ - /* Ensure redirects take effect. Try "a=t >file" */ + /* Ensure redirects take effect (that is, create files). + * Try "a=t >file": */ rcode = setup_redirects(command, squirrel); restore_redirects(squirrel); /* Set shell variables */ @@ -3869,6 +4211,11 @@ static NOINLINE int run_pipe(struct pipe *pi) set_local_var(p, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0); argv++; } + /* Redirect error sets $? to 1. Othervise, + * if evaluating assignment value set $?, retain it. + * Try "false; q=`exit 2`; echo $?" - should print 2: */ + if (rcode == 0) + rcode = G.last_exitcode; /* Do we need to flag set_local_var() errors? * "assignment to readonly var" and "putenv error" */ @@ -3895,6 +4242,13 @@ static NOINLINE int run_pipe(struct pipe *pi) argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt); } + /* if someone gives us an empty string: `cmd with empty output` */ + if (!argv_expanded[0]) { + free(argv_expanded); + debug_leave(); + return G.last_exitcode; + } + x = find_builtin(argv_expanded[0]); #if ENABLE_HUSH_FUNCTIONS funcp = NULL; @@ -3903,7 +4257,7 @@ static NOINLINE int run_pipe(struct pipe *pi) #endif if (x || funcp) { if (!funcp) { - if (x->function == builtin_exec && argv_expanded[1] == NULL) { + if (x->b_function == builtin_exec && argv_expanded[1] == NULL) { debug_printf("exec with redirects only\n"); rcode = setup_redirects(command, NULL); goto clean_up_and_ret1; @@ -3919,9 +4273,9 @@ static NOINLINE int run_pipe(struct pipe *pi) old_vars = set_vars_and_save_old(new_env); if (!funcp) { debug_printf_exec(": builtin '%s' '%s'...\n", - x->cmd, argv_expanded[1]); - rcode = x->function(argv_expanded) & 0xff; - fflush(NULL); + x->b_cmd, argv_expanded[1]); + rcode = x->b_function(argv_expanded) & 0xff; + fflush_all(); } #if ENABLE_HUSH_FUNCTIONS else { @@ -4109,30 +4463,30 @@ static void debug_print_tree(struct pipe *pi, int lvl) }; static const char *RES[] = { [RES_NONE ] = "NONE" , -#if ENABLE_HUSH_IF +# if ENABLE_HUSH_IF [RES_IF ] = "IF" , [RES_THEN ] = "THEN" , [RES_ELIF ] = "ELIF" , [RES_ELSE ] = "ELSE" , [RES_FI ] = "FI" , -#endif -#if ENABLE_HUSH_LOOPS +# 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 +# endif +# if ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE [RES_IN ] = "IN" , -#endif -#if ENABLE_HUSH_CASE +# 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 +# endif [RES_XXXX ] = "XXXX" , [RES_SNTX ] = "SNTX" , }; @@ -4140,9 +4494,9 @@ static void debug_print_tree(struct pipe *pi, int lvl) "{}", "()", "[noglob]", -#if ENABLE_HUSH_FUNCTIONS +# if ENABLE_HUSH_FUNCTIONS "func()", -#endif +# endif }; int pin, prn; @@ -4160,9 +4514,15 @@ static void debug_print_tree(struct pipe *pi, int lvl) lvl*2, "", prn, command->assignment_cnt); if (command->group) { - fprintf(stderr, " group %s: (argv=%p)\n", + fprintf(stderr, " group %s: (argv=%p)%s%s\n", CMDTYPE[command->cmd_type], - argv); + argv +#if !BB_MMU + , " group_as_string:", command->group_as_string +#else + , "", "" +#endif + ); debug_print_tree(command->group, lvl+1); prn++; continue; @@ -4178,7 +4538,7 @@ static void debug_print_tree(struct pipe *pi, int lvl) pin++; } } -#endif +#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!) */ @@ -4652,25 +5012,25 @@ struct reserved_combo { }; enum { FLAG_END = (1 << RES_NONE ), -#if ENABLE_HUSH_IF +# if ENABLE_HUSH_IF FLAG_IF = (1 << RES_IF ), FLAG_THEN = (1 << RES_THEN ), FLAG_ELIF = (1 << RES_ELIF ), FLAG_ELSE = (1 << RES_ELSE ), FLAG_FI = (1 << RES_FI ), -#endif -#if ENABLE_HUSH_LOOPS +# endif +# if ENABLE_HUSH_LOOPS FLAG_FOR = (1 << RES_FOR ), FLAG_WHILE = (1 << RES_WHILE), FLAG_UNTIL = (1 << RES_UNTIL), FLAG_DO = (1 << RES_DO ), FLAG_DONE = (1 << RES_DONE ), FLAG_IN = (1 << RES_IN ), -#endif -#if ENABLE_HUSH_CASE +# endif +# if ENABLE_HUSH_CASE FLAG_MATCH = (1 << RES_MATCH), FLAG_ESAC = (1 << RES_ESAC ), -#endif +# endif FLAG_START = (1 << RES_XXXX ), }; @@ -4682,26 +5042,26 @@ static const struct reserved_combo* match_reserved_word(o_string *word) * FLAG_START means the word must start a new compound list. */ static const struct reserved_combo reserved_list[] = { -#if ENABLE_HUSH_IF +# 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 }, -#endif -#if ENABLE_HUSH_LOOPS +# 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 }, -#endif -#if ENABLE_HUSH_CASE +# endif +# if ENABLE_HUSH_CASE { "case", RES_CASE, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_START }, { "esac", RES_ESAC, NOT_ASSIGNMENT , FLAG_END }, -#endif +# endif }; const struct reserved_combo *r; @@ -4715,11 +5075,11 @@ static const struct reserved_combo* match_reserved_word(o_string *word) */ static int reserved_word(o_string *word, struct parse_context *ctx) { -#if ENABLE_HUSH_CASE +# if ENABLE_HUSH_CASE static const struct reserved_combo reserved_match = { "", RES_MATCH, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_ESAC }; -#endif +# endif const struct reserved_combo *r; if (word->o_quoted) @@ -4729,12 +5089,12 @@ static int reserved_word(o_string *word, struct parse_context *ctx) return 0; debug_printf("found reserved word %s, res %d\n", r->literal, r->res); -#if ENABLE_HUSH_CASE +# if ENABLE_HUSH_CASE if (r->res == RES_IN && ctx->ctx_res_w == RES_CASE_IN) { /* "case word IN ..." - IN part starts first MATCH part */ r = &reserved_match; } else -#endif +# endif if (r->flag == 0) { /* '!' */ if (ctx->ctx_inverted) { /* bash doesn't accept '! ! true' */ syntax_error("! ! command"); @@ -4775,19 +5135,19 @@ static int reserved_word(o_string *word, struct parse_context *ctx) old = ctx->stack; old->command->group = ctx->list_head; old->command->cmd_type = CMD_NORMAL; -#if !BB_MMU +# if !BB_MMU o_addstr(&old->as_string, ctx->as_string.data); o_free_unsafe(&ctx->as_string); old->command->group_as_string = xstrdup(old->as_string.data); debug_printf_parse("pop, remembering as:'%s'\n", old->command->group_as_string); -#endif +# endif *ctx = *old; /* physical copy */ free(old); } return 1; } -#endif +#endif /* HAS_KEYWORDS */ /* Word is complete, look at it and update parsing context. * Normal return is 0. Syntax errors return 1. @@ -5190,13 +5550,13 @@ static int fetch_heredocs(int heredoc_cnt, struct parse_context *ctx, struct in_ #if ENABLE_HUSH_TICK -static FILE *generate_stream_from_string(const char *s) +static FILE *generate_stream_from_string(const char *s, pid_t *pid_p) { - FILE *pf; - int pid, channel[2]; -#if !BB_MMU - char **to_free; -#endif + pid_t pid; + int channel[2]; +# if !BB_MMU + char **to_free = NULL; +# endif xpipe(channel); pid = BB_MMU ? fork() : vfork(); @@ -5260,11 +5620,11 @@ static FILE *generate_stream_from_string(const char *s) builtin_trap((char**)argv); exit(0); /* not _exit() - we need to fflush */ } -#if BB_MMU +# if BB_MMU reset_traps_to_defaults(); parse_and_run_string(s); _exit(G.last_exitcode); -#else +# else /* We re-execute after vfork on NOMMU. This makes this script safe: * yes "0123456789012345678901234567890" | dd bs=32 count=64k >BIG * huge=`cat BIG` # was blocking here forever @@ -5275,37 +5635,36 @@ static FILE *generate_stream_from_string(const char *s) G.global_argv[0], G.global_argv + 1, NULL); -#endif +# endif } /* parent */ -#if ENABLE_HUSH_FAST + *pid_p = pid; +# if ENABLE_HUSH_FAST G.count_SIGCHLD++; //bb_error_msg("[%d] fork in generate_stream_from_string: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); -#endif +# endif enable_restore_tty_pgrp_on_exit(); -#if !BB_MMU +# if !BB_MMU free(to_free); -#endif +# endif close(channel[1]); - pf = fdopen(channel[0], "r"); - return pf; + close_on_exec_on(channel[0]); + return xfdopen_for_read(channel[0]); } /* Return code is exit status of the process that is run. */ static int process_command_subs(o_string *dest, const char *s) { - FILE *pf; + FILE *fp; struct in_str pipe_str; - int ch, eol_cnt; + pid_t pid; + int status, ch, eol_cnt; - pf = generate_stream_from_string(s); - if (pf == NULL) - return 1; - close_on_exec_on(fileno(pf)); + fp = generate_stream_from_string(s, &pid); /* Now send results of command back into original context */ - setup_file_in_str(&pipe_str, pf); + setup_file_in_str(&pipe_str, fp); eol_cnt = 0; while ((ch = i_getch(&pipe_str)) != EOF) { if (ch == '\n') { @@ -5319,19 +5678,21 @@ static int process_command_subs(o_string *dest, const char *s) o_addQchr(dest, ch); } - debug_printf("done reading from pipe, pclose()ing\n"); - /* Note: we got EOF, and we just close the read end of the pipe. - * We do not wait for the `cmd` child to terminate. bash and ash do. - * Try these: - * echo `echo Hi; exec 1>&-; sleep 2` - bash waits 2 sec - * `false`; echo $? - bash outputs "1" - */ - fclose(pf); - debug_printf("closed FILE from child. return 0\n"); - return 0; + debug_printf("done reading from `cmd` pipe, closing it\n"); + fclose(fp); + /* We need to extract exitcode. Test case + * "true; echo `sleep 1; false` $?" + * should print 1 */ + safe_waitpid(pid, &status, 0); + debug_printf("child exited. returning its exitcode:%d\n", WEXITSTATUS(status)); + return WEXITSTATUS(status); } -#endif +#endif /* ENABLE_HUSH_TICK */ +#if !ENABLE_HUSH_FUNCTIONS +#define parse_group(dest, ctx, input, ch) \ + parse_group(ctx, input, ch) +#endif static int parse_group(o_string *dest, struct parse_context *ctx, struct in_str *input, int ch) { @@ -5375,6 +5736,8 @@ static int parse_group(o_string *dest, struct parse_context *ctx, goto skip; } #endif + +#if 0 /* Prevented by caller */ if (command->argv /* word [word]{... */ || dest->length /* word{... */ || dest->o_quoted /* ""{... */ @@ -5384,6 +5747,7 @@ static int parse_group(o_string *dest, struct parse_context *ctx, "syntax error, groups and arglists don't mix\n"); return 1; } +#endif #if ENABLE_HUSH_FUNCTIONS skip: @@ -5524,29 +5888,37 @@ static void add_till_backquote(o_string *dest, struct in_str *input) * echo $(echo '(TEST)' BEST) (TEST) BEST * echo $(echo 'TEST)' BEST) TEST) BEST * echo $(echo \(\(TEST\) BEST) ((TEST) BEST + * + * Also adapted to eat ${var%...} constructs, since ... part + * can contain arbitrary constructs, just like $(cmd). */ -static void add_till_closing_paren(o_string *dest, struct in_str *input, bool dbl) +#define DOUBLE_CLOSE_CHAR_FLAG 0x80 +static void add_till_closing_paren(o_string *dest, struct in_str *input, char end_ch) { - int count = 0; + char dbl = end_ch & DOUBLE_CLOSE_CHAR_FLAG; + end_ch &= (DOUBLE_CLOSE_CHAR_FLAG-1); while (1) { int ch = i_getch(input); if (ch == EOF) { - syntax_error_unterm_ch(')'); + syntax_error_unterm_ch(end_ch); /*xfunc_die(); - redundant */ } - if (ch == '(') - count++; - if (ch == ')') { - if (--count < 0) { - if (!dbl) - break; - if (i_peek(input) == ')') { - i_getch(input); - break; - } + if (ch == end_ch) { + if (!dbl) + break; + /* we look for closing )) of $((EXPR)) */ + if (i_peek(input) == end_ch) { + i_getch(input); /* eat second ')' */ + break; } } o_addchr(dest, ch); + if (ch == '(' || ch == '{') { + ch = (ch == '(' ? ')' : '}'); + add_till_closing_paren(dest, input, ch); + o_addchr(dest, ch); + continue; + } if (ch == '\'') { add_till_single_quote(dest, input); o_addchr(dest, ch); @@ -5557,6 +5929,11 @@ static void add_till_closing_paren(o_string *dest, struct in_str *input, bool db o_addchr(dest, ch); continue; } + if (ch == '`') { + add_till_backquote(dest, input); + o_addchr(dest, ch); + continue; + } if (ch == '\\') { /* \x. Copy verbatim. Important for \(, \) */ ch = i_getch(input); @@ -5617,88 +5994,56 @@ static int handle_dollar(o_string *as_string, case '@': /* args */ goto make_one_char_var; case '{': { - bool first_char, all_digits; - int expansion; + o_addchr(dest, SPECIAL_VAR_SYMBOL); - ch = i_getch(input); + ch = i_getch(input); /* eat '{' */ nommu_addchr(as_string, ch); - o_addchr(dest, SPECIAL_VAR_SYMBOL); - /* TODO: maybe someone will try to escape the '}' */ - expansion = 0; - first_char = true; - all_digits = false; + 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("$!?#*@_", ch) && !isalnum(ch)) { /* not one of those */ + bad_dollar_syntax: + syntax_error_unterm_str("${name}"); + debug_printf_parse("handle_dollar return 1: unterminated ${name}\n"); + return 1; + } + ch |= quote_mask; + + /* It's possible to just call add_till_closing_paren() at this point. + * However, this regresses some of our testsuite cases + * which check invalid constructs like ${%}. + * Oh well... let's check that the var name part is fine... */ + while (1) { + o_addchr(dest, ch); + debug_printf_parse(": '%c'\n", ch); + ch = i_getch(input); nommu_addchr(as_string, ch); - if (ch == '}') { + if (ch == '}') break; - } - if (first_char) { - if (ch == '#') { - /* ${#var}: length of var contents */ - goto char_ok; - } - if (isdigit(ch)) { - all_digits = true; - goto char_ok; - } - /* They're being verbose and doing ${?} */ - if (i_peek(input) == '}' && strchr("$!?#*@_", ch)) - goto char_ok; - } - - if (expansion < 2 - && ( (all_digits && !isdigit(ch)) - || (!all_digits && !isalnum(ch) && ch != '_') - ) - ) { + if (!isalnum(ch) && ch != '_') { /* handle parameter expansions * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02 */ - if (first_char) - goto case_default; - switch (ch) { - case ':': /* null modifier */ - if (expansion == 0) { - debug_printf_parse(": null modifier\n"); - ++expansion; - break; - } - goto case_default; - case '#': /* remove prefix */ - case '%': /* remove suffix */ - if (expansion == 0) { - debug_printf_parse(": remove suffix/prefix\n"); - expansion = 2; - break; - } - goto case_default; - case '-': /* default value */ - case '=': /* assign default */ - case '+': /* alternative */ - case '?': /* error indicate */ - debug_printf_parse(": parameter expansion\n"); - expansion = 2; - break; - default: - case_default: - syntax_error_unterm_str("${name}"); - debug_printf_parse("handle_dollar return 1: unterminated ${name}\n"); - return 1; - } + if (!strchr("%#:-=+?", ch)) /* ${var... */ + goto bad_dollar_syntax; + /* Eat everything until closing '}' */ + o_addchr(dest, ch); +//TODO: add nommu_addchr hack here + add_till_closing_paren(dest, input, '}'); + break; } - char_ok: - debug_printf_parse(": '%c'\n", ch); - o_addchr(dest, ch | quote_mask); - quote_mask = 0; - first_char = false; - } /* while (1) */ + } o_addchr(dest, SPECIAL_VAR_SYMBOL); break; } -#if (ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_TICK) +#if ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_TICK case '(': { # if !BB_MMU int pos; @@ -5714,7 +6059,7 @@ static int handle_dollar(o_string *as_string, # if !BB_MMU pos = dest->length; # endif - add_till_closing_paren(dest, input, true); + add_till_closing_paren(dest, input, ')' | DOUBLE_CLOSE_CHAR_FLAG); # if !BB_MMU if (as_string) { o_addstr(as_string, dest->data + pos); @@ -5732,11 +6077,11 @@ static int handle_dollar(o_string *as_string, # if !BB_MMU pos = dest->length; # endif - add_till_closing_paren(dest, input, false); + add_till_closing_paren(dest, input, ')'); # if !BB_MMU if (as_string) { o_addstr(as_string, dest->data + pos); - o_addchr(as_string, '`'); + o_addchr(as_string, ')'); } # endif o_addchr(dest, SPECIAL_VAR_SYMBOL); @@ -5796,7 +6141,7 @@ static int parse_stream_dquoted(o_string *as_string, if (ch != '\n') { next = i_peek(input); } - debug_printf_parse(": ch=%c (%d) escape=%d\n", + debug_printf_parse("\" ch=%c (%d) escape=%d\n", ch, ch, dest->o_escape); if (ch == '\\') { if (next == EOF) { @@ -5876,9 +6221,14 @@ static struct pipe *parse_stream(char **pstring, end_trigger ? end_trigger : 'X'); debug_enter(); + /* If very first arg is "" or '', dest.data may end up NULL. + * Preventing this: */ + o_addchr(&dest, '\0'); + dest.length = 0; + G.ifs = get_local_var_value("IFS"); if (G.ifs == NULL) - G.ifs = " \t\n"; + G.ifs = defifs; reset: #if ENABLE_HUSH_INTERACTIVE @@ -5948,10 +6298,29 @@ static struct pipe *parse_stream(char **pstring, return pi; } nommu_addchr(&ctx.as_string, ch); + + next = '\0'; + if (ch != '\n') + next = i_peek(input); + + is_special = "{}<>;&|()#'" /* special outside of "str" */ + "\\$\"" IF_HUSH_TICK("`"); /* always special */ + /* Are { and } special here? */ + if (ctx.command->argv /* word [word]{... - non-special */ + || dest.length /* word{... - non-special */ + || dest.o_quoted /* ""{... - non-special */ + || (next != ';' /* }; - special */ + && next != ')' /* }) - special */ + && next != '&' /* }& and }&& ... - special */ + && next != '|' /* }|| ... - special */ + && !strchr(G.ifs, 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_special = strchr("<>;&|(){}#'" /* special outside of "str" */ - "\\$\"" IF_HUSH_TICK("`") /* always special */ - , ch); if (!is_special && !is_ifs) { /* ordinary char */ ordinary_char: @@ -6062,11 +6431,6 @@ static struct pipe *parse_stream(char **pstring, if (is_ifs) continue; - next = '\0'; - if (ch != '\n') { - next = i_peek(input); - } - /* Catch <, > before deciding whether this word is * an assignment. a=1 2>z b=2: b=2 is still assignment */ switch (ch) { @@ -6158,12 +6522,13 @@ static struct pipe *parse_stream(char **pstring, /* Example: echo Hello \2>file * we need to know that word 2 is quoted */ dest.o_quoted = 1; - } else { + } #if !BB_MMU + else { /* It's "\". Remove trailing '\' from ctx.as_string */ ctx.as_string.data[--ctx.as_string.length] = '\0'; -#endif } +#endif break; case '$': if (handle_dollar(&ctx.as_string, &dest, input) != 0) { @@ -6270,6 +6635,9 @@ static struct pipe *parse_stream(char **pstring, * with redirect_opt_num(), but bash doesn't do it. * "echo foo 2| cat" yields "foo 2". */ done_command(&ctx); +#if !BB_MMU + o_reset_to_empty_unquoted(&ctx.as_string); +#endif } goto new_cmd; case '(': @@ -6365,15 +6733,26 @@ static struct pipe *parse_stream(char **pstring, */ static void parse_and_run_stream(struct in_str *inp, int end_trigger) { + /* Why we need empty flag? + * An obscure corner case "false; ``; echo $?": + * empty command in `` should still set $? to 0. + * But we can't just set $? to 0 at the start, + * this breaks "false; echo `echo $?`" case. + */ + bool empty = 1; while (1) { struct pipe *pipe_list; pipe_list = parse_stream(NULL, inp, end_trigger); - if (!pipe_list) /* EOF */ + if (!pipe_list) { /* EOF */ + if (empty) + G.last_exitcode = 0; break; + } debug_print_tree(pipe_list, 0); debug_printf_exec("parse_and_run_stream: run_and_free_list\n"); run_and_free_list(pipe_list); + empty = 0; } } @@ -6392,10 +6771,17 @@ static void parse_and_run_file(FILE *f) } /* Called a few times only (or even once if "sh -c") */ -static void block_signals(int second_time) +static void init_sigmasks(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); if (G_interactive_fd) { @@ -6405,8 +6791,6 @@ static void block_signals(int second_time) } G.non_DFL_mask = mask; - if (!second_time) - sigprocmask(SIG_SETMASK, NULL, &G.blocked_set); sig = 0; while (mask) { if (mask & 1) @@ -6416,18 +6800,21 @@ static void block_signals(int second_time) } sigdelset(&G.blocked_set, SIGCHLD); - sigprocmask(SIG_SETMASK, &G.blocked_set, - second_time ? NULL : &G.inherited_set); + 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 (!second_time) + if (!G.inherited_set_is_saved) signal(SIGCHLD, SIGCHLD_handler); #else - if (!second_time) + if (!G.inherited_set_is_saved) signal(SIGCHLD, SIG_DFL); #endif + + G.inherited_set_is_saved = 1; } #if ENABLE_HUSH_JOB @@ -6489,14 +6876,13 @@ int hush_main(int argc, char **argv) .flg_export = 1, .flg_read_only = 1, }; - int signal_mask_is_inited = 0; int opt; unsigned builtin_argc; char **e; struct variable *cur_var; INIT_G(); - if (EXIT_SUCCESS) /* if EXIT_SUCCESS == 0, is already done */ + if (EXIT_SUCCESS) /* if EXIT_SUCCESS == 0, it is already done */ G.last_exitcode = EXIT_SUCCESS; #if !BB_MMU G.argv0_for_re_execing = argv[0]; @@ -6580,10 +6966,9 @@ int hush_main(int argc, char **argv) } /* Shell is non-interactive at first. We need to call - * block_signals(0) if we are going to execute "sh