hush: delete hush-bugs/glob_and_vars.tests for real
[oweals/busybox.git] / shell / hush.c
index a84bb92dc98e61f01beb14d878d7da80c345457e..7c5a442747ccacd0471a86dc96c149e7b78a47f0 100644 (file)
@@ -178,11 +178,8 @@ void xxfree(void *ptr)
 #endif
 
 
-#define SPECIAL_VAR_SYMBOL   3
-
+#define SPECIAL_VAR_SYMBOL       3
 #define PARSEFLAG_EXIT_FROM_LOOP 1
-#define PARSEFLAG_SEMICOLON      (1 << 1)  /* symbol ';' is special for parser */
-#define PARSEFLAG_REPARSING      (1 << 2)  /* >= 2nd pass */
 
 typedef enum {
        REDIRECT_INPUT     = 1,
@@ -242,11 +239,9 @@ struct p_context {
        struct pipe *pipe;
        struct redir_struct *pending_redirect;
        smallint res_w;
-       smallint parse_type;        /* bitmask of PARSEFLAG_xxx, defines type of parser: ";$" common or special symbol */
        smallint ctx_inverted;      /* "! cmd | cmd" */
        int old_flag;               /* bitmask of FLAG_xxx, for figuring out valid reserved words */
        struct p_context *stack;
-       /* How about quoting status? */
 };
 
 struct redir_struct {
@@ -486,10 +481,6 @@ static int run_list(struct pipe *pi);
 static void pseudo_exec_argv(char **ptrs2free, char **argv) ATTRIBUTE_NORETURN;
 static void pseudo_exec(char **ptrs2free, struct child_prog *child) ATTRIBUTE_NORETURN;
 static int run_pipe(struct pipe *pi);
-/*   extended glob support: */
-static char **globhack(const char *src, char **strings);
-static int glob_needed(const char *s);
-static int xglob(o_string *dest, char ***pglob);
 /*   variable assignment: */
 static int is_assignment(const char *s);
 /*   data structure manipulation: */
@@ -585,7 +576,6 @@ static void free_strings(char **strings)
        }
 }
 
-
 #if !BB_MMU
 #define EXTRA_PTRS 5 /* 1 for NULL, 1 for args, 3 for paranoid reasons */
 static char **alloc_ptrs(char **argv)
@@ -597,6 +587,17 @@ static char **alloc_ptrs(char **argv)
 }
 #endif
 
+#ifdef DEBUG_EXPAND
+static void debug_print_strings(const char *prefix, char **vv)
+{
+       fprintf(stderr, "%s:\n", prefix);
+       while (*vv)
+               fprintf(stderr, " '%s'\n", *vv++);
+}
+#else
+#define debug_print_strings(prefix, vv) ((void)0)
+#endif
+
 
 /* Function prototypes for builtins */
 static int builtin_cd(char **argv);
@@ -845,8 +846,7 @@ static int builtin_eval(char **argv)
 
        if (argv[1]) {
                char *str = expand_strvec_to_string(argv + 1);
-               parse_and_run_string(str, PARSEFLAG_EXIT_FROM_LOOP |
-                                       PARSEFLAG_SEMICOLON);
+               parse_and_run_string(str, PARSEFLAG_EXIT_FROM_LOOP);
                free(str);
                rcode = last_return_code;
        }
@@ -893,7 +893,6 @@ static int builtin_exit(char **argv)
        //puts("exit"); /* bash does it */
 // TODO: warn if we have background jobs: "There are stopped jobs"
 // On second consecutive 'exit', exit anyway.
-
        if (argv[1] == NULL)
                hush_exit(last_return_code);
        /* mimic bash: exit 123abc == exit 255 + error msg */
@@ -1139,12 +1138,6 @@ static int builtin_unset(char **argv)
        return EXIT_SUCCESS;
 }
 
-//static int builtin_not_written(char **argv)
-//{
-//     printf("builtin_%s not written\n", argv[0]);
-//     return EXIT_FAILURE;
-//}
-
 /*
  * o_string support
  */
@@ -1635,12 +1628,6 @@ static void pseudo_exec(char **ptrs2free, struct child_prog *child)
                bb_error_msg_and_die("nested lists are not supported on NOMMU");
 #else
                int rcode;
-
-#if ENABLE_HUSH_INTERACTIVE
-// run_list_level now takes care of it?
-//             debug_printf_exec("pseudo_exec: setting interactive_fd=0\n");
-//             interactive_fd = 0;    /* crucial!!!! */
-#endif
                debug_printf_exec("pseudo_exec: run_list\n");
                rcode = run_list(child->group);
                /* OK to leak memory by not calling free_pipe_list,
@@ -2263,7 +2250,6 @@ static int run_list(struct pipe *pi)
 #endif /* JOB */
 
        for (; pi; pi = flag_restore ? rpipe : pi->next) {
-//why?         int save_num_progs;
                rword = pi->res_word;
 #if ENABLE_HUSH_LOOPS
                if (rword == RES_WHILE || rword == RES_UNTIL || rword == RES_FOR) {
@@ -2301,7 +2287,9 @@ static int run_list(struct pipe *pi)
                                if (!pi->next->progs->argv)
                                        continue;
                                /* create list of variable values */
+                               debug_print_strings("for_list made from", pi->next->progs->argv);
                                for_list = expand_strvec_to_strvec(pi->next->progs->argv);
+                               debug_print_strings("for_list", for_list);
                                for_lcur = for_list;
                                for_varname = pi->progs->argv[0];
                                pi->progs->argv[0] = NULL;
@@ -2336,7 +2324,6 @@ static int run_list(struct pipe *pi)
 #endif
                if (pi->num_progs == 0)
                        continue;
-//why?         save_num_progs = pi->num_progs;
                debug_printf_exec(": run_pipe with %d members\n", pi->num_progs);
                rcode = run_pipe(pi);
                if (rcode != -1) {
@@ -2367,7 +2354,6 @@ static int run_list(struct pipe *pi)
                }
                debug_printf_exec(": setting last_return_code=%d\n", rcode);
                last_return_code = rcode;
-//why?         pi->num_progs = save_num_progs;
 #if ENABLE_HUSH_IF
                if (rword == RES_IF || rword == RES_ELIF)
                        next_if_code = rcode;  /* can be overwritten a number of times */
@@ -2488,17 +2474,10 @@ static int run_and_free_list(struct pipe *pi)
        return rcode;
 }
 
-/* Whoever decided to muck with glob internal data is AN IDIOT! */
-/* uclibc happily changed the way it works (and it has rights to do so!),
-   all hell broke loose (SEGVs) */
-
-/* The API for glob is arguably broken.  This routine pushes a non-matching
- * string into the output structure, removing non-backslashed backslashes.
- * If someone can prove me wrong, by performing this function within the
- * original glob(3) api, feel free to rewrite this routine into oblivion.
+/* Remove non-backslashed backslashes and add to "strings" vector.
  * XXX broken if the last character is '\\', check that before calling.
  */
-static char **globhack(const char *src, char **strings)
+static char **add_unq_string_to_strings(char **strings, const char *src)
 {
        int cnt;
        const char *s;
@@ -2530,33 +2509,20 @@ static int glob_needed(const char *s)
        return 0;
 }
 
-static int xglob(o_string *dest, char ***pglob)
+static int xglob(char ***pglob, const char *pattern)
 {
-       /* short-circuit for null word */
-       /* we can code this better when the debug_printf's are gone */
-       if (dest->length == 0) {
-               if (dest->nonnull) {
-                       /* bash man page calls this an "explicit" null */
-                       *pglob = globhack(dest->data, *pglob);
-               }
-               return 0;
-       }
-
-       if (glob_needed(dest->data)) {
+       if (glob_needed(pattern)) {
                glob_t globdata;
                int gr;
 
                memset(&globdata, 0, sizeof(globdata));
-               gr = glob(dest->data, 0, NULL, &globdata);
+               gr = glob(pattern, 0, NULL, &globdata);
                debug_printf("glob returned %d\n", gr);
                if (gr == GLOB_NOSPACE)
                        bb_error_msg_and_die("out of memory during glob");
                if (gr == GLOB_NOMATCH) {
-                       debug_printf("globhack returned %d\n", gr);
-                       /* quote removal, or more accurately, backslash removal */
-                       *pglob = globhack(dest->data, *pglob);
                        globfree(&globdata);
-                       return 0;
+                       goto literal;
                }
                if (gr != 0) { /* GLOB_ABORTED ? */
                        bb_error_msg("glob(3) error %d", gr);
@@ -2567,7 +2533,10 @@ static int xglob(o_string *dest, char ***pglob)
                return gr;
        }
 
-       *pglob = globhack(dest->data, *pglob);
+ literal:
+       /* quote removal, or more accurately, backslash removal */
+       *pglob = add_unq_string_to_strings(*pglob, pattern);
+       debug_print_strings("after xglob", *pglob);
        return 0;
 }
 
@@ -2760,7 +2729,7 @@ static char **expand_variables(char **argv, char or_mask)
                n = expand_vars_to_list(&output, n, *v++, or_mask);
        o_debug_list("expand_variables", &output, n);
 
-       /* output.data (malloced) gets returned in "list" */
+       /* output.data (malloced in one block) gets returned in "list" */
        list = o_finalize_list(&output, n);
 
 #ifdef DEBUG_EXPAND
@@ -2777,9 +2746,27 @@ static char **expand_variables(char **argv, char or_mask)
 
 static char **expand_strvec_to_strvec(char **argv)
 {
-       return expand_variables(argv, 0);
+       char **exp;
+       char **res = NULL;
+
+       debug_print_strings("expand_strvec_to_strvec: pre expand", argv);
+       exp = argv = expand_variables(argv, 0);
+       debug_print_strings("expand_strvec_to_strvec: post expand", argv);
+       while (*argv) {
+               int r = xglob(&res, *argv);
+               if (r)
+                       bb_error_msg("xglob returned %d on '%s'", r, *argv);
+//TODO: testcase for bad glob pattern behavior
+               argv++;
+       }
+       free(exp);
+       debug_print_strings("expand_strvec_to_strvec: res", res);
+       return res;
 }
 
+/* used for expansion of right hand of assignments */
+/* NB: should NOT do globbing! "export v=/bin/c*; env | grep ^v=" outputs
+ * "v=/bin/c*" */
 static char *expand_string_to_string(const char *str)
 {
        char *argv[2], **list;
@@ -2796,6 +2783,7 @@ static char *expand_string_to_string(const char *str)
        return (char*)list;
 }
 
+/* used for eval */
 static char* expand_strvec_to_string(char **argv)
 {
        char **list;
@@ -3001,9 +2989,7 @@ static struct pipe *new_pipe(void)
 
 static void initialize_context(struct p_context *ctx)
 {
-       smallint sv = ctx->parse_type;
        memset(ctx, 0, sizeof(*ctx));
-       ctx->parse_type = sv;
        ctx->pipe = ctx->list_head = new_pipe();
        /* Create the memory for child, roughly:
         * ctx->pipe->progs = new struct child_prog;
@@ -3135,7 +3121,6 @@ static int done_word(o_string *word, struct p_context *ctx)
 {
        struct child_prog *child = ctx->child;
        char ***glob_target;
-       int gr;
 
        debug_printf_parse("done_word entered: '%s' %p\n", word->data, child);
        if (word->length == 0 && !word->nonnull) {
@@ -3150,7 +3135,7 @@ static int done_word(o_string *word, struct p_context *ctx)
                        debug_printf_parse("done_word return 1: syntax error, groups and arglists don't mix\n");
                        return 1;
                }
-               if (!child->argv && (ctx->parse_type & PARSEFLAG_SEMICOLON)) {
+               if (!child->argv) {
                        debug_printf_parse(": checking '%s' for reserved-ness\n", word->data);
                        if (reserved_word(word, ctx)) {
                                o_reset(word);
@@ -3160,12 +3145,10 @@ static int done_word(o_string *word, struct p_context *ctx)
                }
                glob_target = &child->argv;
        }
-//BUG! globbing should be done after variable expansion!
-//See glob_and_vars testcase
-       gr = xglob(word, glob_target);
-       if (gr != 0) {
-               debug_printf_parse("done_word return 1: xglob returned %d\n", gr);
-               return 1;
+
+       if (word->length || word->nonnull) {
+               *glob_target = add_string_to_strings(*glob_target, xstrdup(word->data));
+               debug_print_strings("glob_target appended", *glob_target);
        }
 
        o_reset(word);
@@ -3400,6 +3383,11 @@ static int process_command_subs(o_string *dest,
                        o_addQchr(dest, '\n');
                        eol_cnt--;
                }
+               /* Even unquoted `echo '\'` results in two backslashes
+                * (which are converted into one by globbing later) */
+               if (!dest->o_quote && ch == '\\') {
+                       o_addchr(dest, ch);
+               }
                o_addQchr(dest, ch);
        }
 
@@ -3437,11 +3425,11 @@ static int parse_group(o_string *dest, struct p_context *ctx,
                child->subshell = 1;
        }
        rcode = parse_stream(dest, &sub, input, endch);
-//vda: err chk?
-       done_word(dest, &sub); /* finish off the final word in the subcontext */
-       done_pipe(&sub, PIPE_SEQ);  /* and the final command there, too */
-       child->group = sub.list_head;
-
+       if (rcode == 0) {
+               done_word(dest, &sub); /* finish off the final word in the subcontext */
+               done_pipe(&sub, PIPE_SEQ);  /* and the final command there, too */
+               child->group = sub.list_head;
+       }
        debug_printf_parse("parse_group return %d\n", rcode);
        return rcode;
        /* child remains "open", available for possible redirects */
@@ -3469,7 +3457,7 @@ static void add_till_single_quote(o_string *dest, struct in_str *input)
                        break;
                if (ch == '\'')
                        break;
-               o_addqchr(dest, ch);
+               o_addchr(dest, ch);
        }
 }
 /* "...\"...`..`...." - do we need to handle "...$(..)..." too? */
@@ -3480,18 +3468,18 @@ static void add_till_double_quote(o_string *dest, struct in_str *input)
                if (ch == '"')
                        break;
                if (ch == '\\') {  /* \x. Copy both chars. */
-                       o_addqchr(dest, ch);
+                       o_addchr(dest, ch);
                        ch = i_getch(input);
                }
                if (ch == EOF)
                        break;
-               o_addqchr(dest, ch);
+               o_addchr(dest, ch);
                if (ch == '`') {
                        add_till_backquote(dest, input);
-                       o_addqchr(dest, ch);
+                       o_addchr(dest, ch);
                        continue;
                }
-//             if (ch == '$') ...
+               //if (ch == '$') ...
        }
 }
 /* Process `cmd` - copy contents until "`" is seen. Complicated by
@@ -3517,12 +3505,12 @@ static void add_till_backquote(o_string *dest, struct in_str *input)
                if (ch == '\\') {  /* \x. Copy both chars unless it is \` */
                        int ch2 = i_getch(input);
                        if (ch2 != '`' && ch2 != '$' && ch2 != '\\')
-                               o_addqchr(dest, ch);
+                               o_addchr(dest, ch);
                        ch = ch2;
                }
                if (ch == EOF)
                        break;
-               o_addqchr(dest, ch);
+               o_addchr(dest, ch);
        }
 }
 /* Process $(cmd) - copy contents until ")" is seen. Complicated by
@@ -3549,22 +3537,22 @@ static void add_till_closing_curly_brace(o_string *dest, struct in_str *input)
                if (ch == ')')
                        if (--count < 0)
                                break;
-               o_addqchr(dest, ch);
+               o_addchr(dest, ch);
                if (ch == '\'') {
                        add_till_single_quote(dest, input);
-                       o_addqchr(dest, ch);
+                       o_addchr(dest, ch);
                        continue;
                }
                if (ch == '"') {
                        add_till_double_quote(dest, input);
-                       o_addqchr(dest, ch);
+                       o_addchr(dest, ch);
                        continue;
                }
                if (ch == '\\') { /* \x. Copy verbatim. Important for  \(, \) */
                        ch = i_getch(input);
                        if (ch == EOF)
                                break;
-                       o_addqchr(dest, ch);
+                       o_addchr(dest, ch);
                        continue;
                }
        }
@@ -3700,11 +3688,19 @@ static int parse_stream(o_string *dest, struct p_context *ctx,
                                done_pipe(ctx, PIPE_SEQ);
                        }
                }
-               if ((end_trigger && strchr(end_trigger, ch))
-                && !dest->o_quote && ctx->res_w == RES_NONE
-               ) {
-                       debug_printf_parse("parse_stream return 0: end_trigger char found\n");
-                       return 0;
+               if (end_trigger) {
+                       if (!dest->o_quote && strchr(end_trigger, ch)) {
+                               /* Special case: (...word) makes last word terminate,
+                                * as if ';' is seen */
+                               if (ch == ')') {
+                                       done_word(dest, ctx);
+                                       done_pipe(ctx, PIPE_SEQ);
+                               }
+                               if (ctx->res_w == RES_NONE) {
+                                       debug_printf_parse("parse_stream return 0: end_trigger char found\n");
+                                       return 0;
+                               }
+                       }
                }
                if (m == CHAR_IFS)
                        continue;
@@ -3757,15 +3753,15 @@ static int parse_stream(o_string *dest, struct p_context *ctx,
                        dest->nonnull = 1;
                        while (1) {
                                ch = i_getch(input);
-                               if (ch == EOF || ch == '\'')
+                               if (ch == EOF) {
+                                       syntax("unterminated '");
+                                       debug_printf_parse("parse_stream return 1: unterminated '\n");
+                                       return 1;
+                               }
+                               if (ch == '\'')
                                        break;
                                o_addqchr(dest, ch);
                        }
-                       if (ch == EOF) {
-                               syntax("unterminated '");
-                               debug_printf_parse("parse_stream return 1: unterminated '\n");
-                               return 1;
-                       }
                        break;
                case '"':
                        dest->nonnull = 1;
@@ -3861,7 +3857,7 @@ static int parse_stream(o_string *dest, struct p_context *ctx,
                        if (ENABLE_HUSH_DEBUG)
                                bb_error_msg_and_die("BUG: unexpected %c\n", ch);
                }
-       }
+       } /* while (1) */
        /* Complain if quote?  No, maybe we just finished a command substitution
         * that was quoted.  Example:
         * $ echo "`cat foo` plus more"
@@ -3908,19 +3904,13 @@ static void update_charmap(void)
  * from builtin_source() and builtin_eval() */
 static int parse_and_run_stream(struct in_str *inp, int parse_flag)
 {
-//TODO: PARSEFLAG_SEMICOLON bit is always set in parse_flag. fishy
-//TODO: PARSEFLAG_REPARSING bit is never set (grep for it). wow
        struct p_context ctx;
        o_string temp = NULL_O_STRING;
        int rcode;
+
        do {
-// It always has PARSEFLAG_SEMICOLON, can we remove all checks for this bit?
-// After that, the whole parse_type fiels is not needed.
-               ctx.parse_type = parse_flag;
                initialize_context(&ctx);
                update_charmap();
-               if (!(parse_flag & PARSEFLAG_SEMICOLON) || (parse_flag & PARSEFLAG_REPARSING))
-                       set_in_charmap(";$&|", CHAR_ORDINARY);
 #if ENABLE_HUSH_INTERACTIVE
                inp->promptmode = 0; /* PS1 */
 #endif
@@ -3938,23 +3928,26 @@ static int parse_and_run_stream(struct in_str *inp, int parse_flag)
                        debug_printf_exec("parse_stream_outer: run_and_free_list\n");
                        run_and_free_list(ctx.list_head);
                } else {
+                       /* We arrive here also if rcode == 1 (error in parse_stream) */
                        if (ctx.old_flag != 0) {
                                free(ctx.stack);
                                o_reset(&temp);
                        }
                        temp.nonnull = 0;
                        temp.o_quote = 0;
-                       inp->p = NULL;
                        free_pipe_list(ctx.list_head, /* indent: */ 0);
+                       /* Discard all unprocessed line input, force prompt on */
+                       inp->p = NULL;
+                       inp->promptme = 1;
                }
                o_free(&temp);
-       } while (rcode != -1 && !(parse_flag & PARSEFLAG_EXIT_FROM_LOOP));   /* loop on syntax errors, return on EOF */
+               /* loop on syntax errors, return on EOF: */
+       } while (rcode != -1 && !(parse_flag & PARSEFLAG_EXIT_FROM_LOOP));
        return 0;
 }
 
 static int parse_and_run_string(const char *s, int parse_flag)
 {
-//TODO: PARSEFLAG_SEMICOLON bit is always set in parse_flag. fishy
        struct in_str input;
        setup_string_in_str(&input, s);
        return parse_and_run_stream(&input, parse_flag);
@@ -3965,7 +3958,7 @@ static int parse_and_run_file(FILE *f)
        int rcode;
        struct in_str input;
        setup_file_in_str(&input, f);
-       rcode = parse_and_run_stream(&input, PARSEFLAG_SEMICOLON);
+       rcode = parse_and_run_stream(&input, 0 /* parse_flag */);
        return rcode;
 }
 
@@ -4078,7 +4071,7 @@ int hush_main(int argc, char **argv)
                case 'c':
                        global_argv = argv + optind;
                        global_argc = argc - optind;
-                       opt = parse_and_run_string(optarg, PARSEFLAG_SEMICOLON);
+                       opt = parse_and_run_string(optarg, 0 /* parse_flag */);
                        goto final_return;
                case 'i':
                        /* Well, we cannot just declare interactiveness,