X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=shell%2Fhush.c;h=32b90876fde26c265a53b61af9678d9ca8661173;hb=a6ad397ea92cd9c53973243728d3e52640fe63ec;hp=0a25967a1a64c62855f76118d41873ab09d2af6f;hpb=17323a6245597f16321e6bf99536ae9bb89c79cf;p=oweals%2Fbusybox.git diff --git a/shell/hush.c b/shell/hush.c index 0a25967a1..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" @@ -87,6 +90,7 @@ #include "shell_common.h" #include "builtin_read.h" +#include "builtin_ulimit.h" #include "math.h" #include "match.h" #if ENABLE_HUSH_RANDOM_SUPPORT @@ -125,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 @@ -669,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"), @@ -2394,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 @@ -2418,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; @@ -2513,27 +2541,19 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char * and $IFS-splitted */ debug_printf_subst("SUBST '%s' first_ch %x\n", arg, first_ch); G.last_exitcode = process_command_subs(&subst_result, arg); - debug_printf_subst("SUBST RES '%s'\n", subst_result.data); + 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 = set_local_var_from_halves; - 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"; @@ -2558,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'; } } @@ -2601,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; } @@ -2656,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 @@ -2682,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; @@ -4107,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 */ @@ -4118,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" */ @@ -4175,7 +4273,7 @@ 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]); + x->b_cmd, argv_expanded[1]); rcode = x->b_function(argv_expanded) & 0xff; fflush_all(); } @@ -5790,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); @@ -5823,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); @@ -5883,84 +5994,52 @@ 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; } @@ -5980,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); @@ -5998,7 +6077,7 @@ 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); @@ -6227,10 +6306,15 @@ static struct pipe *parse_stream(char **pstring, is_special = "{}<>;&|()#'" /* special outside of "str" */ "\\$\"" IF_HUSH_TICK("`"); /* always special */ /* Are { and } special here? */ - if (ctx.command->argv /* word [word]{... */ - || dest.length /* word{... */ - || dest.o_quoted /* ""{... */ - || (next != ';' && next != ')' && !strchr(G.ifs, next)) /* {word */ + 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; @@ -6798,7 +6882,7 @@ int hush_main(int argc, char **argv) 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]; @@ -7242,11 +7326,20 @@ static int FAST_FUNC builtin_printf(char **argv) } #endif +static char **skip_dash_dash(char **argv) +{ + argv++; + if (argv[0] && argv[0][0] == '-' && argv[0][1] == '-' && argv[0][2] == '\0') + argv++; + return argv; +} + static int FAST_FUNC builtin_eval(char **argv) { int rcode = EXIT_SUCCESS; - if (*++argv) { + argv = skip_dash_dash(argv); + if (*argv) { char *str = expand_strvec_to_string(argv); /* bash: * eval "echo Hi; done" ("done" is syntax error): @@ -7261,7 +7354,10 @@ static int FAST_FUNC builtin_eval(char **argv) static int FAST_FUNC builtin_cd(char **argv) { - const char *newdir = argv[1]; + const char *newdir; + + argv = skip_dash_dash(argv); + newdir = argv[0]; if (newdir == NULL) { /* bash does nothing (exitcode 0) if HOME is ""; if it's unset, * bash says "bash: cd: HOME not set" and does nothing @@ -7285,7 +7381,8 @@ static int FAST_FUNC builtin_cd(char **argv) static int FAST_FUNC builtin_exec(char **argv) { - if (*++argv == NULL) + argv = skip_dash_dash(argv); + if (argv[0] == NULL) return EXIT_SUCCESS; /* bash does this */ /* Careful: we can end up here after [v]fork. Do not restore @@ -7318,12 +7415,13 @@ static int FAST_FUNC builtin_exit(char **argv) */ /* note: EXIT trap is run by hush_exit */ - if (*++argv == NULL) + argv = skip_dash_dash(argv); + if (argv[0] == NULL) hush_exit(G.last_exitcode); /* mimic bash: exit 123abc == exit 255 + error msg */ xfunc_error_retval = 255; /* bash: exit -2 == exit 254, no error msg */ - hush_exit(xatoi(*argv) & 0xff); + hush_exit(xatoi(argv[0]) & 0xff); } static void print_escaped(const char *s) @@ -7652,7 +7750,7 @@ static int FAST_FUNC builtin_help(char **argv UNUSED_PARAM) "------------------\n"); for (x = bltins1; x != &bltins1[ARRAY_SIZE(bltins1)]; x++) { if (x->b_descr) - printf("%s\t%s\n", x->b_cmd, x->b_descr); + printf("%-10s%s\n", x->b_cmd, x->b_descr); } bb_putchar('\n'); return EXIT_SUCCESS; @@ -7835,8 +7933,9 @@ static int FAST_FUNC builtin_set(char **argv) static int FAST_FUNC builtin_shift(char **argv) { int n = 1; - if (argv[1]) { - n = atoi(argv[1]); + argv = skip_dash_dash(argv); + if (argv[0]) { + n = atoi(argv[0]); } if (n >= 0 && n < G.global_argc) { if (G.global_args_malloced) { @@ -7854,21 +7953,27 @@ static int FAST_FUNC builtin_shift(char **argv) static int FAST_FUNC builtin_source(char **argv) { - char *arg_path; + char *arg_path, *filename; FILE *input; save_arg_t sv; #if ENABLE_HUSH_FUNCTIONS smallint sv_flg; #endif - if (*++argv == NULL) - return EXIT_FAILURE; - - if (strchr(*argv, '/') == NULL && (arg_path = find_in_path(*argv)) != NULL) { - input = fopen_for_read(arg_path); - free(arg_path); - } else - input = fopen_or_warn(*argv, "r"); + argv = skip_dash_dash(argv); + filename = argv[0]; + if (!filename) { + /* bash says: "bash: .: filename argument required" */ + return 2; /* bash compat */ + } + arg_path = NULL; + if (!strchr(filename, '/')) { + arg_path = find_in_path(filename); + if (arg_path) + filename = arg_path; + } + input = fopen_or_warn(filename, "r"); + free(arg_path); if (!input) { /* bb_perror_msg("%s", *argv); - done by fopen_or_warn */ return EXIT_FAILURE; @@ -7899,11 +8004,12 @@ static int FAST_FUNC builtin_umask(char **argv) mode_t mask; mask = umask(0); - if (argv[1]) { + argv = skip_dash_dash(argv); + if (argv[0]) { mode_t old_mask = mask; mask ^= 0777; - rc = bb_parse_mode(argv[1], &mask); + rc = bb_parse_mode(argv[0], &mask); mask ^= 0777; if (rc == 0) { mask = old_mask; @@ -7911,7 +8017,7 @@ static int FAST_FUNC builtin_umask(char **argv) * bash: umask: 'q': invalid symbolic mode operator * bash: umask: 999: octal number out of range */ - bb_error_msg("%s: '%s' invalid mode", argv[0], argv[1]); + bb_error_msg("%s: invalid mode '%s'", "umask", argv[0]); } } else { rc = 1; @@ -7967,7 +8073,8 @@ static int FAST_FUNC builtin_wait(char **argv) int ret = EXIT_SUCCESS; int status, sig; - if (*++argv == NULL) { + argv = skip_dash_dash(argv); + if (argv[0] == NULL) { /* Don't care about wait results */ /* Note 1: must wait until there are no more children */ /* Note 2: must be interruptible */