aboutsummaryrefslogtreecommitdiff
path: root/shell/hush.c
diff options
context:
space:
mode:
authorDenys Vlasenko <dvlasenk@redhat.com>2010-09-05 14:45:38 +0200
committerDenys Vlasenko <dvlasenk@redhat.com>2010-09-05 14:47:58 +0200
commit36f774a0cd2bf8dd72b192aab93831c5ac0c58f0 (patch)
treeafa0b3d247e4dd163b89b7b61f4e3e0f3143b102 /shell/hush.c
parent701e127f7d892909a58c6f3333e23588ccef9e22 (diff)
downloadbusybox-w32-36f774a0cd2bf8dd72b192aab93831c5ac0c58f0.tar.gz
busybox-w32-36f774a0cd2bf8dd72b192aab93831c5ac0c58f0.tar.bz2
busybox-w32-36f774a0cd2bf8dd72b192aab93831c5ac0c58f0.zip
hush: add support for ${var/pattern/repl}, conditional on bash compat
function old new delta expand_vars_to_list 2386 2833 +447 expand_string_to_string 69 110 +41 parse_dollar 681 721 +40 hush_main 963 945 -18 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 3/1 up/down: 528/-18) Total: 510 bytes Signed-off-by: Denys Vlasenko <dvlasenk@redhat.com>
Diffstat (limited to 'shell/hush.c')
-rw-r--r--shell/hush.c179
1 files changed, 150 insertions, 29 deletions
diff --git a/shell/hush.c b/shell/hush.c
index 4f80b7d83..9a08e90c9 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -50,7 +50,6 @@
50 * 50 *
51 * Bash compat TODO: 51 * Bash compat TODO:
52 * redirection of stdout+stderr: &> and >& 52 * redirection of stdout+stderr: &> and >&
53 * subst operator: ${var/[/]expr/expr}
54 * brace expansion: one/{two,three,four} 53 * brace expansion: one/{two,three,four}
55 * reserved words: function select 54 * reserved words: function select
56 * advanced test: [[ ]] 55 * advanced test: [[ ]]
@@ -330,6 +329,17 @@
330#define _SPECIAL_VARS_STR "_*@$!?#" 329#define _SPECIAL_VARS_STR "_*@$!?#"
331#define SPECIAL_VARS_STR ("_*@$!?#" + 1) 330#define SPECIAL_VARS_STR ("_*@$!?#" + 1)
332#define NUMERIC_SPECVARS_STR ("_*@$!?#" + 3) 331#define NUMERIC_SPECVARS_STR ("_*@$!?#" + 3)
332#if ENABLE_HUSH_BASH_COMPAT
333/* Support / and // replace ops */
334/* Note that // is stored as \ in "encoded" string representation */
335# define VAR_ENCODED_SUBST_OPS "\\/%#:-=+?"
336# define VAR_SUBST_OPS ("\\/%#:-=+?" + 1)
337# define MINUS_PLUS_EQUAL_QUESTION ("\\/%#:-=+?" + 5)
338#else
339# define VAR_ENCODED_SUBST_OPS "%#:-=+?"
340# define VAR_SUBST_OPS "%#:-=+?"
341# define MINUS_PLUS_EQUAL_QUESTION ("%#:-=+?" + 3)
342#endif
333 343
334#define SPECIAL_VAR_SYMBOL 3 344#define SPECIAL_VAR_SYMBOL 3
335 345
@@ -2600,6 +2610,60 @@ static arith_t expand_and_evaluate_arith(const char *arg, int *errcode_p)
2600} 2610}
2601#endif 2611#endif
2602 2612
2613#if ENABLE_HUSH_BASH_COMPAT
2614/* ${var/[/]pattern[/repl]} helpers */
2615static char *strstr_pattern(char *val, const char *pattern, int *size)
2616{
2617 while (1) {
2618 char *end = scan_and_match(val, pattern, SCAN_MOVE_FROM_RIGHT + SCAN_MATCH_LEFT_HALF);
2619 debug_printf_varexp("val:'%s' pattern:'%s' end:'%s'\n", val, pattern, end);
2620 if (end) {
2621 *size = end - val;
2622 return val;
2623 }
2624 if (*val == '\0')
2625 return NULL;
2626 /* Optimization: if "*pat" did not match the start of "string",
2627 * we know that "tring", "ring" etc will not match too:
2628 */
2629 if (pattern[0] == '*')
2630 return NULL;
2631 val++;
2632 }
2633}
2634static char *replace_pattern(char *val, const char *pattern, const char *repl, char exp_op)
2635{
2636 char *result = NULL;
2637 unsigned res_len = 0;
2638 unsigned repl_len = strlen(repl);
2639
2640 while (1) {
2641 int size;
2642 char *s = strstr_pattern(val, pattern, &size);
2643 if (!s)
2644 break;
2645
2646 result = xrealloc(result, res_len + (s - val) + repl_len + 1);
2647 memcpy(result + res_len, val, s - val);
2648 res_len += s - val;
2649 strcpy(result + res_len, repl);
2650 res_len += repl_len;
2651 debug_printf_varexp("val:'%s' s:'%s' result:'%s'\n", val, s, result);
2652
2653 val = s + size;
2654 if (exp_op == '/')
2655 break;
2656 }
2657 if (val[0] && result) {
2658 result = xrealloc(result, res_len + strlen(val) + 1);
2659 strcpy(result + res_len, val);
2660 debug_printf_varexp("val:'%s' result:'%s'\n", val, result);
2661 }
2662 debug_printf_varexp("result:'%s'\n", result);
2663 return result;
2664}
2665#endif
2666
2603/* Expand all variable references in given string, adding words to list[] 2667/* Expand all variable references in given string, adding words to list[]
2604 * at n, n+1,... positions. Return updated n (so that list[n] is next one 2668 * at n, n+1,... positions. Return updated n (so that list[n] is next one
2605 * to be filled). This routine is extremely tricky: has to deal with 2669 * to be filled). This routine is extremely tricky: has to deal with
@@ -2750,7 +2814,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
2750 2814
2751 var = arg; 2815 var = arg;
2752 *p = '\0'; 2816 *p = '\0';
2753 exp_saveptr = arg[1] ? strchr("%#:-=+?", arg[1]) : NULL; 2817 exp_saveptr = arg[1] ? strchr(VAR_ENCODED_SUBST_OPS, arg[1]) : NULL;
2754 first_char = arg[0] = first_ch & 0x7f; 2818 first_char = arg[0] = first_ch & 0x7f;
2755 exp_op = 0; 2819 exp_op = 0;
2756 2820
@@ -2767,7 +2831,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
2767 exp_saveptr = var + 1; 2831 exp_saveptr = var + 1;
2768 } else { 2832 } else {
2769 /* ${?}, ${var}, ${var:0}, ${var[:]%0} etc */ 2833 /* ${?}, ${var}, ${var:0}, ${var[:]%0} etc */
2770 exp_saveptr = var+1 + strcspn(var+1, "%#:-=+?"); 2834 exp_saveptr = var+1 + strcspn(var+1, VAR_ENCODED_SUBST_OPS);
2771 } 2835 }
2772 exp_op = exp_save = *exp_saveptr; 2836 exp_op = exp_save = *exp_saveptr;
2773 if (exp_op) { 2837 if (exp_op) {
@@ -2775,7 +2839,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
2775 if (exp_op == ':') { 2839 if (exp_op == ':') {
2776 exp_op = *exp_word++; 2840 exp_op = *exp_word++;
2777 if (ENABLE_HUSH_BASH_COMPAT 2841 if (ENABLE_HUSH_BASH_COMPAT
2778 && (exp_op == '\0' || !strchr("%#:-=+?"+3, exp_op)) 2842 && (exp_op == '\0' || !strchr(MINUS_PLUS_EQUAL_QUESTION, exp_op))
2779 ) { 2843 ) {
2780 /* oops... it's ${var:N[:M]}, not ${var:?xxx} or some such */ 2844 /* oops... it's ${var:N[:M]}, not ${var:?xxx} or some such */
2781 exp_op = ':'; 2845 exp_op = ':';
@@ -2799,7 +2863,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
2799 val = utoa(G.root_pid); 2863 val = utoa(G.root_pid);
2800 break; 2864 break;
2801 case '!': /* bg pid */ 2865 case '!': /* bg pid */
2802 val = G.last_bg_pid ? utoa(G.last_bg_pid) : (char*)""; 2866 val = G.last_bg_pid ? utoa(G.last_bg_pid) : "";
2803 break; 2867 break;
2804 case '?': /* exitcode */ 2868 case '?': /* exitcode */
2805 val = utoa(G.last_exitcode); 2869 val = utoa(G.last_exitcode);
@@ -2843,13 +2907,47 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
2843 // exp_op, to_be_freed, exp_word, loc); 2907 // exp_op, to_be_freed, exp_word, loc);
2844 free(exp_exp_word); 2908 free(exp_exp_word);
2845 if (loc) { /* match was found */ 2909 if (loc) { /* match was found */
2846 if (scan_flags & SCAN_MATCH_LEFT_HALF) /* # or ## */ 2910 if (scan_flags & SCAN_MATCH_LEFT_HALF) /* #[#] */
2847 val = loc; 2911 val = loc;
2848 else /* % or %% */ 2912 else /* %[%] */
2849 *loc = '\0'; 2913 *loc = '\0';
2850 } 2914 }
2851 } 2915 }
2852 } else if (exp_op == ':') { 2916 }
2917#if ENABLE_HUSH_BASH_COMPAT
2918 else if (exp_op == '/' || exp_op == '\\') {
2919 /* Empty variable always gives nothing: */
2920 // "v=''; echo ${v/*/w}" prints ""
2921 if (val && val[0]) {
2922 /* It's ${var/[/]pattern[/repl]} thing */
2923 char *pattern, *repl, *t;
2924 pattern = expand_pseudo_dquoted(exp_word);
2925 if (!pattern)
2926 pattern = xstrdup(exp_word);
2927 debug_printf_varexp("pattern:'%s'->'%s'\n", exp_word, pattern);
2928 *p++ = SPECIAL_VAR_SYMBOL;
2929 exp_word = p;
2930 p = strchr(p, SPECIAL_VAR_SYMBOL);
2931 *p = '\0';
2932 repl = expand_pseudo_dquoted(exp_word);
2933 debug_printf_varexp("repl:'%s'->'%s'\n", exp_word, repl);
2934 /* HACK ALERT. We depend here on the fact that
2935 * G.global_argv and results of utoa and get_local_var_value
2936 * are actually in writable memory:
2937 * replace_pattern momentarily stores NULs there. */
2938 t = (char*)val;
2939 to_be_freed = replace_pattern(t,
2940 pattern,
2941 (repl ? repl : exp_word),
2942 exp_op);
2943 if (to_be_freed) /* at least one replace happened */
2944 val = to_be_freed;
2945 free(pattern);
2946 free(repl);
2947 }
2948 }
2949#endif
2950 else if (exp_op == ':') {
2853#if ENABLE_HUSH_BASH_COMPAT && ENABLE_SH_MATH_SUPPORT 2951#if ENABLE_HUSH_BASH_COMPAT && ENABLE_SH_MATH_SUPPORT
2854 /* It's ${var:N[:M]} bashism. 2952 /* It's ${var:N[:M]} bashism.
2855 * Note that in encoded form it has TWO parts: 2953 * Note that in encoded form it has TWO parts:
@@ -3084,6 +3182,16 @@ static char *expand_string_to_string(const char *str)
3084{ 3182{
3085 char *argv[2], **list; 3183 char *argv[2], **list;
3086 3184
3185 /* This is generally an optimization, but it also
3186 * handles "", which otherwise trips over !list[0] check below.
3187 * (is this ever happens that we actually get str="" here?)
3188 */
3189 if (!strchr(str, SPECIAL_VAR_SYMBOL) && !strchr(str, '\\')) {
3190 //TODO: Can use on strings with \ too, just unbackslash() them?
3191 debug_printf_expand("string_to_string(fast)='%s'\n", str);
3192 return xstrdup(str);
3193 }
3194
3087 argv[0] = (char*)str; 3195 argv[0] = (char*)str;
3088 argv[1] = NULL; 3196 argv[1] = NULL;
3089 list = expand_variables(argv, EXPVAR_FLAG_ESCAPE_VARS | EXPVAR_FLAG_SINGLEWORD); 3197 list = expand_variables(argv, EXPVAR_FLAG_ESCAPE_VARS | EXPVAR_FLAG_SINGLEWORD);
@@ -3271,7 +3379,7 @@ static void re_execute_shell(char ***to_free, const char *s,
3271 *pp++ = (char *) G.argv0_for_re_execing; 3379 *pp++ = (char *) G.argv0_for_re_execing;
3272 *pp++ = param_buf; 3380 *pp++ = param_buf;
3273 for (cur = G.top_var; cur; cur = cur->next) { 3381 for (cur = G.top_var; cur; cur = cur->next) {
3274 if (cur->varstr == hush_version_str) 3382 if (strcmp(cur->varstr, hush_version_str) == 0)
3275 continue; 3383 continue;
3276 if (cur->flg_read_only) { 3384 if (cur->flg_read_only) {
3277 *pp++ = (char *) "-R"; 3385 *pp++ = (char *) "-R";
@@ -6170,8 +6278,8 @@ static void add_till_backquote(o_string *dest, struct in_str *input)
6170 * 6278 *
6171 * Also adapted to eat ${var%...} and $((...)) constructs, since ... part 6279 * Also adapted to eat ${var%...} and $((...)) constructs, since ... part
6172 * can contain arbitrary constructs, just like $(cmd). 6280 * can contain arbitrary constructs, just like $(cmd).
6173 * In bash compat mode, it needs to also be able to stop on '}' or ':' 6281 * In bash compat mode, it needs to also be able to stop on ':' or '/'
6174 * for ${var:N[:M]} parsing. 6282 * for ${var:N[:M]} and ${var/P[/R]} parsing.
6175 */ 6283 */
6176#define DOUBLE_CLOSE_CHAR_FLAG 0x80 6284#define DOUBLE_CLOSE_CHAR_FLAG 0x80
6177static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsigned end_ch) 6285static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsigned end_ch)
@@ -6323,19 +6431,30 @@ static int parse_dollar(o_string *as_string,
6323 /* handle parameter expansions 6431 /* handle parameter expansions
6324 * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02 6432 * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02
6325 */ 6433 */
6326 if (!strchr("%#:-=+?", ch)) /* ${var<bad_char>... */ 6434 if (!strchr(VAR_SUBST_OPS, ch)) /* ${var<bad_char>... */
6327 goto bad_dollar_syntax; 6435 goto bad_dollar_syntax;
6328 o_addchr(dest, ch);
6329 6436
6330 /* Eat everything until closing '}' (or ':') */ 6437 /* Eat everything until closing '}' (or ':') */
6331 end_ch = '}'; 6438 end_ch = '}';
6332 if (ENABLE_HUSH_BASH_COMPAT 6439 if (ENABLE_HUSH_BASH_COMPAT
6333 && ch == ':' 6440 && ch == ':'
6334 && !strchr("%#:-=+?"+3, i_peek(input)) 6441 && !strchr(MINUS_PLUS_EQUAL_QUESTION, i_peek(input))
6335 ) { 6442 ) {
6336 /* It's ${var:N[:M]} thing */ 6443 /* It's ${var:N[:M]} thing */
6337 end_ch = '}' * 0x100 + ':'; 6444 end_ch = '}' * 0x100 + ':';
6338 } 6445 }
6446 if (ENABLE_HUSH_BASH_COMPAT
6447 && ch == '/'
6448 ) {
6449 /* It's ${var/[/]pattern[/repl]} thing */
6450 if (i_peek(input) == '/') { /* ${var//pattern[/repl]}? */
6451 i_getch(input);
6452 nommu_addchr(as_string, '/');
6453 ch = '\\';
6454 }
6455 end_ch = '}' * 0x100 + '/';
6456 }
6457 o_addchr(dest, ch);
6339 again: 6458 again:
6340 if (!BB_MMU) 6459 if (!BB_MMU)
6341 pos = dest->length; 6460 pos = dest->length;
@@ -6352,14 +6471,18 @@ static int parse_dollar(o_string *as_string,
6352 if (ENABLE_HUSH_BASH_COMPAT && (end_ch & 0xff00)) { 6471 if (ENABLE_HUSH_BASH_COMPAT && (end_ch & 0xff00)) {
6353 /* close the first block: */ 6472 /* close the first block: */
6354 o_addchr(dest, SPECIAL_VAR_SYMBOL); 6473 o_addchr(dest, SPECIAL_VAR_SYMBOL);
6355 /* while parsing N from ${var:N[:M]}... */ 6474 /* while parsing N from ${var:N[:M]}
6475 * or pattern from ${var/[/]pattern[/repl]} */
6356 if ((end_ch & 0xff) == last_ch) { 6476 if ((end_ch & 0xff) == last_ch) {
6357 /* ...got ':' - parse the rest */ 6477 /* got ':' or '/'- parse the rest */
6358 end_ch = '}'; 6478 end_ch = '}';
6359 goto again; 6479 goto again;
6360 } 6480 }
6361 /* ...got '}', not ':' - it's ${var:N}! emulate :999999999 */ 6481 /* got '}' */
6362 o_addstr(dest, "999999999"); 6482 if (end_ch == '}' * 0x100 + ':') {
6483 /* it's ${var:N} - emulate :999999999 */
6484 o_addstr(dest, "999999999");
6485 } /* else: it's ${var/[/]pattern} */
6363 } 6486 }
6364 break; 6487 break;
6365 } 6488 }
@@ -7186,13 +7309,6 @@ static int set_mode(const char cstate, const char mode)
7186int hush_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 7309int hush_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
7187int hush_main(int argc, char **argv) 7310int hush_main(int argc, char **argv)
7188{ 7311{
7189 static const struct variable const_shell_ver = {
7190 .next = NULL,
7191 .varstr = (char*)hush_version_str,
7192 .max_len = 1, /* 0 can provoke free(name) */
7193 .flg_export = 1,
7194 .flg_read_only = 1,
7195 };
7196 int opt; 7312 int opt;
7197 unsigned builtin_argc; 7313 unsigned builtin_argc;
7198 char **e; 7314 char **e;
@@ -7205,10 +7321,18 @@ int hush_main(int argc, char **argv)
7205 G.argv0_for_re_execing = argv[0]; 7321 G.argv0_for_re_execing = argv[0];
7206#endif 7322#endif
7207 /* Deal with HUSH_VERSION */ 7323 /* Deal with HUSH_VERSION */
7208 G.shell_ver = const_shell_ver; /* copying struct here */ 7324 G.shell_ver.flg_export = 1;
7325 G.shell_ver.flg_read_only = 1;
7326 /* Code which handles ${var/P/R} needs writable values for all variables,
7327 * therefore we xstrdup: */
7328 G.shell_ver.varstr = xstrdup(hush_version_str),
7209 G.top_var = &G.shell_ver; 7329 G.top_var = &G.shell_ver;
7210 debug_printf_env("unsetenv '%s'\n", "HUSH_VERSION"); 7330 debug_printf_env("unsetenv '%s'\n", "HUSH_VERSION");
7211 unsetenv("HUSH_VERSION"); /* in case it exists in initial env */ 7331 unsetenv("HUSH_VERSION"); /* in case it exists in initial env */
7332 /* reinstate HUSH_VERSION in environment */
7333 debug_printf_env("putenv '%s'\n", G.shell_ver.varstr);
7334 putenv(G.shell_ver.varstr);
7335
7212 /* Initialize our shell local variables with the values 7336 /* Initialize our shell local variables with the values
7213 * currently living in the environment */ 7337 * currently living in the environment */
7214 cur_var = G.top_var; 7338 cur_var = G.top_var;
@@ -7224,9 +7348,6 @@ int hush_main(int argc, char **argv)
7224 } 7348 }
7225 e++; 7349 e++;
7226 } 7350 }
7227 /* reinstate HUSH_VERSION */
7228 debug_printf_env("putenv '%s'\n", hush_version_str);
7229 putenv((char *)hush_version_str);
7230 7351
7231 /* Export PWD */ 7352 /* Export PWD */
7232 set_pwd_var(/*exp:*/ 1); 7353 set_pwd_var(/*exp:*/ 1);