diff --git a/shell/hush.c b/shell/hush.c index 9aeb0f6a8..84dd9e13c 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -43,12 +43,13 @@ * Here Documents ( << word ) * Functions * Tilde Expansion - * fancy forms of Parameter Expansion: ${var:-val} + * Parameter Expansion for substring processing ${var#word} ${var%word} * * Bash stuff maybe optional enable: * &> and >& redirection of stdout+stderr * Brace expansion * reserved words: [[ ]] function select + * substrings ${var:1:5} * * Major bugs: * job handling woefully incomplete and buggy (improved --vda) @@ -589,18 +590,20 @@ static const struct built_in_command bltins[] = { #if 1 /* Normal */ -static void syntax(const char *msg) +static void maybe_die(const char *notice, const char *msg) { -#if ENABLE_HUSH_INTERACTIVE /* Was using fancy stuff: * (G.interactive_fd ? bb_error_msg : bb_error_msg_and_die)(...params...) * but it SEGVs. ?! Oh well... explicit temp ptr works around that */ - void FAST_FUNC (*fp)(const char *s, ...); + void FAST_FUNC (*fp)(const char *s, ...) = bb_error_msg_and_die; +#if ENABLE_HUSH_INTERACTIVE fp = (G.interactive_fd ? bb_error_msg : bb_error_msg_and_die); - fp(msg ? "%s: %s" : "syntax error", "syntax error", msg); -#else - bb_error_msg_and_die(msg ? "%s: %s" : "syntax error", "syntax error", msg); #endif + fp(msg ? "%s: %s" : notice, notice, msg); +} +static void syntax(const char *msg) +{ + maybe_die("syntax error", msg); } #else /* Debug */ @@ -920,7 +923,13 @@ static const char *lookup_param(const char *src) } /* str holds "NAME=VAL" and is expected to be malloced. - * We take ownership of it. */ + * We take ownership of it. + * flg_export is used by: + * 0: do not export + * 1: export + * -1: if NAME is set, leave export status alone + * if NAME is not set, do not export + */ static int set_local_var(char *str, int flg_export) { struct variable *cur; @@ -980,7 +989,7 @@ static int set_local_var(char *str, int flg_export) set_str_and_exp: cur->varstr = str; exp: - if (flg_export) + if (flg_export == 1) cur->flg_export = 1; if (cur->flg_export) { debug_printf_env("%s: putenv '%s'\n", __func__, cur->varstr); @@ -1548,6 +1557,9 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask) val = utoa(G.last_return_code); break; case '#': /* argc */ + if (arg[1] != SPECIAL_VAR_SYMBOL) + /* actually, it's a ${#var} */ + goto case_default; val = utoa(G.global_argc ? G.global_argc-1 : 0); break; case '*': @@ -1615,20 +1627,79 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask) } #endif default: /* varname */ + case_default: { + bool exp_len = false, exp_null = false; + char *var = arg, exp_save, exp_op, *exp_word; + size_t exp_off = 0; *p = '\0'; arg[0] = first_ch & 0x7f; - if (isdigit(arg[0])) { - i = xatoi_u(arg); + + /* prepare for expansions */ + if (var[0] == '#') { + /* handle length expansion ${#var} */ + exp_len = true; + ++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_op = *exp_word++; + var[exp_off] = '\0'; + } + } + + /* lookup the variable in question */ + if (isdigit(var[0])) { + i = xatoi_u(var); if (i < G.global_argc) val = G.global_argv[i]; /* else val remains NULL: $N with too big N */ } else - val = lookup_param(arg); + val = lookup_param(var); + + /* handle any expansions */ + if (exp_len) { + debug_printf_expand("expand: length of '%s' = ", val); + val = utoa(val ? strlen(val) : 0); + debug_printf_expand("%s\n", val); + } else if (exp_off) { + /* we need to do an expansion */ + int exp_test = (!val || (exp_null && !val[0])); + if (exp_op == '+') + exp_test = !exp_test; + debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op, + exp_null ? "true" : "false", exp_test); + if (exp_test) { + if (exp_op == '?') + maybe_die(var, *exp_word ? exp_word : "parameter null or not set"); + else + val = exp_word; + + if (exp_op == '=') { + if (isdigit(var[0]) || var[0] == '#') { + maybe_die(var, "special vars cannot assign in this way"); + val = NULL; + } else { + char *new_var = xmalloc(strlen(var) + strlen(val) + 2); + sprintf(new_var, "%s=%s", var, val); + set_local_var(new_var, -1); + } + } + } + var[exp_off] = exp_save; + } + arg[0] = first_ch; + #if ENABLE_HUSH_TICK store_val: #endif - *p = SPECIAL_VAR_SYMBOL; if (!(first_ch & 0x80)) { /* unquoted $VAR */ debug_printf_expand("unquoted '%s', output->o_quote:%d\n", val, output->o_quote); if (val) { @@ -1643,9 +1714,13 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask) debug_printf_expand("quoted '%s', output->o_quote:%d\n", val, output->o_quote); } } + } if (val) { o_addQstr(output, val, strlen(val)); } + /* Do the check to avoid writing to a const string */ + if (p && *p != SPECIAL_VAR_SYMBOL) + *p = SPECIAL_VAR_SYMBOL; #if ENABLE_HUSH_TICK o_free(&subst_result); @@ -3625,6 +3700,7 @@ static void add_till_closing_curly_brace(o_string *dest, struct in_str *input) /* Return code: 0 for OK, 1 for syntax error */ static int handle_dollar(o_string *dest, struct in_str *input) { + int expansion; int ch = i_peek(input); /* first character after the $ */ unsigned char quote_mask = dest->o_quote ? 0x80 : 0; @@ -3658,25 +3734,71 @@ static int handle_dollar(o_string *dest, struct in_str *input) case '*': /* args */ case '@': /* args */ goto make_one_char_var; - case '{': + case '{': { + bool first_char; + o_addchr(dest, SPECIAL_VAR_SYMBOL); i_getch(input); /* XXX maybe someone will try to escape the '}' */ + expansion = 0; + first_char = true; while (1) { ch = i_getch(input); if (ch == '}') break; - if (!isalnum(ch) && ch != '_') { - syntax("unterminated ${name}"); - debug_printf_parse("handle_dollar return 1: unterminated ${name}\n"); - return 1; + + if (ch == '#' && first_char) + /* ${#var}: length of var contents */; + + else if (expansion < 2 && !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; + +#if 0 /* not implemented yet :( */ + case '#': /* remove prefix */ + case '%': /* remove suffix */ + if (expansion == 0) { + debug_printf_parse(": remove suffix/prefix\n"); + expansion = 2; + break; + } + goto case_default; +#endif + + case '-': /* default value */ + case '=': /* assign default */ + case '+': /* alternative */ + case '?': /* error indicate */ + debug_printf_parse(": parameter expansion\n"); + expansion = 2; + break; + + default: + case_default: + syntax("unterminated ${name}"); + debug_printf_parse("handle_dollar return 1: unterminated ${name}\n"); + return 1; + } } debug_printf_parse(": '%c'\n", ch); o_addchr(dest, ch | quote_mask); quote_mask = 0; + first_char = false; } o_addchr(dest, SPECIAL_VAR_SYMBOL); break; + } #if ENABLE_HUSH_TICK case '(': { //int pos = dest->length;