find: support -HLP
[oweals/busybox.git] / shell / hush.c
index 1082738a2ddcb43f843203dd83a1fe07afbfefc2..1a2603e3b7fa5a017f639c8a136d0b8912bfbf12 100644 (file)
 # define PIPE_BUF 4096  /* amount of buffering in a pipe */
 #endif
 
-/* Not every libc has sighandler_t. Fix it */
-typedef void (*hush_sighandler_t)(int);
-#define sighandler_t hush_sighandler_t
-
 //config:config HUSH
 //config:      bool "hush"
 //config:      default y
@@ -324,6 +320,8 @@ typedef void (*hush_sighandler_t)(int);
 # define ENABLE_FEATURE_EDITING 0
 # undef ENABLE_FEATURE_EDITING_FANCY_PROMPT
 # define ENABLE_FEATURE_EDITING_FANCY_PROMPT 0
+# undef ENABLE_FEATURE_EDITING_SAVE_ON_EXIT
+# define ENABLE_FEATURE_EDITING_SAVE_ON_EXIT 0
 #endif
 
 /* Do we support ANY keywords? */
@@ -524,7 +522,6 @@ typedef enum redir_type {
 struct command {
        pid_t pid;                  /* 0 if exited */
        int assignment_cnt;         /* how many argv[i] are assignments? */
-       smallint is_stopped;        /* is the command currently running? */
        smallint cmd_type;          /* CMD_xxx */
 #define CMD_NORMAL   0
 #define CMD_SUBSHELL 1
@@ -853,6 +850,9 @@ static int builtin_jobs(char **argv) FAST_FUNC;
 #if ENABLE_HUSH_HELP
 static int builtin_help(char **argv) FAST_FUNC;
 #endif
+#if MAX_HISTORY && ENABLE_FEATURE_EDITING
+static int builtin_history(char **argv) FAST_FUNC;
+#endif
 #if ENABLE_HUSH_LOCAL
 static int builtin_local(char **argv) FAST_FUNC;
 #endif
@@ -922,6 +922,9 @@ static const struct built_in_command bltins1[] = {
 #if ENABLE_HUSH_HELP
        BLTIN("help"     , builtin_help    , NULL),
 #endif
+#if MAX_HISTORY && ENABLE_FEATURE_EDITING
+       BLTIN("history"  , builtin_history , "Show command history"),
+#endif
 #if ENABLE_HUSH_JOB
        BLTIN("jobs"     , builtin_jobs    , "List jobs"),
 #endif
@@ -1541,6 +1544,10 @@ static sighandler_t pick_sighandler(unsigned sig)
 static void hush_exit(int exitcode) NORETURN;
 static void hush_exit(int exitcode)
 {
+#if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT
+       save_history(G.line_input_state);
+#endif
+
        fflush_all();
        if (G.exiting <= 0 && G.traps && G.traps[0] && G.traps[0][0]) {
                char *argv[3];
@@ -2281,7 +2288,7 @@ static void o_addqblock(o_string *o, const char *str, int len)
                        ordinary_cnt = len;
                o_addblock(o, str, ordinary_cnt);
                if (ordinary_cnt == len)
-                       return;
+                       return; /* NUL is already added by o_addblock */
                str += ordinary_cnt;
                len -= ordinary_cnt + 1; /* we are processing + 1 char below */
 
@@ -2295,8 +2302,8 @@ static void o_addqblock(o_string *o, const char *str, int len)
                o_grow_by(o, sz);
                o->data[o->length] = ch;
                o->length++;
-               o->data[o->length] = '\0';
        }
+       o->data[o->length] = '\0';
 }
 
 static void o_addQblock(o_string *o, const char *str, int len)
@@ -2385,6 +2392,7 @@ static int o_save_ptr_helper(o_string *o, int n)
                                n, string_len, string_start);
                o->has_empty_slot = 0;
        }
+       o->has_quoted_part = 0;
        list[n] = (char*)(uintptr_t)string_len;
        return n + 1;
 }
@@ -3264,14 +3272,6 @@ static int done_word(o_string *word, struct parse_context *ctx)
                        ) {
                                p += 3;
                        }
-                       if (p == word->data || p[0] != '\0') {
-                               /* saw no "$@", or not only "$@" but some
-                                * real text is there too */
-                               /* insert "empty variable" reference, this makes
-                                * e.g. "", $empty"" etc to not disappear */
-                               o_addchr(word, SPECIAL_VAR_SYMBOL);
-                               o_addchr(word, SPECIAL_VAR_SYMBOL);
-                       }
                }
                command->argv = add_string_to_strings(command->argv, xstrdup(word->data));
                debug_print_strings("word appended to argv", command->argv);
@@ -4225,7 +4225,7 @@ static struct pipe *parse_stream(char **pstring,
                        /* (this makes bare "&" cmd a no-op.
                         * bash says: "syntax error near unexpected token '&'") */
                        if (pi->num_cmds == 0
-                           IF_HAS_KEYWORDS( && pi->res_word == RES_NONE)
+                       IF_HAS_KEYWORDS(&& pi->res_word == RES_NONE)
                        ) {
                                free_pipe_list(pi);
                                pi = NULL;
@@ -4378,7 +4378,7 @@ static struct pipe *parse_stream(char **pstring,
                        debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]);
                        /* Do we sit outside of any if's, loops or case's? */
                        if (!HAS_KEYWORDS
-                        IF_HAS_KEYWORDS(|| (ctx.ctx_res_w == RES_NONE && ctx.old_flag == 0))
+                       IF_HAS_KEYWORDS(|| (ctx.ctx_res_w == RES_NONE && ctx.old_flag == 0))
                        ) {
                                o_free(&dest);
 #if !BB_MMU
@@ -4515,20 +4515,30 @@ static struct pipe *parse_stream(char **pstring,
                        break;
                case '\'':
                        dest.has_quoted_part = 1;
-                       while (1) {
-                               ch = i_getch(input);
-                               if (ch == EOF) {
-                                       syntax_error_unterm_ch('\'');
-                                       goto parse_error;
+                       if (next == '\'' && !ctx.pending_redirect) {
+ insert_empty_quoted_str_marker:
+                               nommu_addchr(&ctx.as_string, next);
+                               i_getch(input); /* eat second ' */
+                               o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+                               o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+                       } else {
+                               while (1) {
+                                       ch = i_getch(input);
+                                       if (ch == EOF) {
+                                               syntax_error_unterm_ch('\'');
+                                               goto parse_error;
+                                       }
+                                       nommu_addchr(&ctx.as_string, ch);
+                                       if (ch == '\'')
+                                               break;
+                                       o_addqchr(&dest, ch);
                                }
-                               nommu_addchr(&ctx.as_string, ch);
-                               if (ch == '\'')
-                                       break;
-                               o_addqchr(&dest, ch);
                        }
                        break;
                case '"':
                        dest.has_quoted_part = 1;
+                       if (next == '"' && !ctx.pending_redirect)
+                               goto insert_empty_quoted_str_marker;
                        if (dest.o_assignment == NOT_ASSIGNMENT)
                                dest.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS;
                        if (!encode_string(&ctx.as_string, &dest, input, '"', /*process_bkslash:*/ 1))
@@ -4750,12 +4760,22 @@ static void o_addblock_duplicate_backslash(o_string *o, const char *str, int len
 
 /* Store given string, finalizing the word and starting new one whenever
  * we encounter IFS char(s). This is used for expanding variable values.
- * End-of-string does NOT finalize word: think about 'echo -$VAR-' */
-static int expand_on_ifs(o_string *output, int n, const char *str)
+ * End-of-string does NOT finalize word: think about 'echo -$VAR-'.
+ * Return in *ended_with_ifs:
+ * 1 - ended with IFS char, else 0 (this includes case of empty str).
+ */
+static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const char *str)
 {
+       int last_is_ifs = 0;
+
        while (1) {
-               int word_len = strcspn(str, G.ifs);
+               int word_len;
+
+               if (!*str)  /* EOL - do not finalize word */
+                       break;
+               word_len = strcspn(str, G.ifs);
                if (word_len) {
+                       /* We have WORD_LEN leading non-IFS chars */
                        if (!(output->o_expflags & EXP_FLAG_GLOB)) {
                                o_addblock(output, str, word_len);
                        } else {
@@ -4768,15 +4788,36 @@ static int expand_on_ifs(o_string *output, int n, const char *str)
                                /*o_addblock(output, str, word_len); - WRONG: "v='\*'; echo Z$v" prints "Z*" instead of "Z\*" */
                                /*o_addqblock(output, str, word_len); - WRONG: "v='*'; echo Z$v" prints "Z*" instead of Z* files */
                        }
+                       last_is_ifs = 0;
                        str += word_len;
+                       if (!*str)  /* EOL - do not finalize word */
+                               break;
                }
+
+               /* We know str here points to at least one IFS char */
+               last_is_ifs = 1;
+               str += strspn(str, G.ifs); /* skip IFS chars */
                if (!*str)  /* EOL - do not finalize word */
                        break;
-               o_addchr(output, '\0');
-               debug_print_list("expand_on_ifs", output, n);
-               n = o_save_ptr(output, n);
-               str += strspn(str, G.ifs); /* skip ifs chars */
+
+               /* Start new word... but not always! */
+               /* Case "v=' a'; echo ''$v": we do need to finalize empty word: */
+               if (output->has_quoted_part
+               /* Case "v=' a'; echo $v":
+                * here nothing precedes the space in $v expansion,
+                * therefore we should not finish the word
+                * (IOW: if there *is* word to finalize, only then do it):
+                */
+                || (n > 0 && output->data[output->length - 1])
+               ) {
+                       o_addchr(output, '\0');
+                       debug_print_list("expand_on_ifs", output, n);
+                       n = o_save_ptr(output, n);
+               }
        }
+
+       if (ended_with_ifs)
+               *ended_with_ifs = last_is_ifs;
        debug_print_list("expand_on_ifs[1]", output, n);
        return n;
 }
@@ -5191,6 +5232,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
         * expansion of right-hand side of assignment == 1-element expand.
         */
        char cant_be_null = 0; /* only bit 0x80 matters */
+       int ended_in_ifs = 0;  /* did last unquoted expansion end with IFS chars? */
        char *p;
 
        debug_printf_expand("expand_vars_to_list: arg:'%s' singleword:%x\n", arg,
@@ -5209,6 +5251,13 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
 #if ENABLE_SH_MATH_SUPPORT
                char arith_buf[sizeof(arith_t)*3 + 2];
 #endif
+
+               if (ended_in_ifs) {
+                       o_addchr(output, '\0');
+                       n = o_save_ptr(output, n);
+                       ended_in_ifs = 0;
+               }
+
                o_addblock(output, arg, p - arg);
                debug_print_list("expand_vars_to_list[1]", output, n);
                arg = ++p;
@@ -5237,7 +5286,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
                        cant_be_null |= first_ch; /* do it for "$@" _now_, when we know it's not empty */
                        if (!(first_ch & 0x80)) { /* unquoted $* or $@ */
                                while (G.global_argv[i]) {
-                                       n = expand_on_ifs(output, n, G.global_argv[i]);
+                                       n = expand_on_ifs(NULL, output, n, G.global_argv[i]);
                                        debug_printf_expand("expand_vars_to_list: argv %d (last %d)\n", i, G.global_argc - 1);
                                        if (G.global_argv[i++][0] && G.global_argv[i]) {
                                                /* this argv[] is not empty and not last:
@@ -5270,11 +5319,13 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
                                        if (G.ifs[0])
                                                o_addchr(output, G.ifs[0]);
                                }
+                               output->has_quoted_part = 1;
                        }
                        break;
                }
                case SPECIAL_VAR_SYMBOL: /* <SPECIAL_VAR_SYMBOL><SPECIAL_VAR_SYMBOL> */
                        /* "Empty variable", used to make "" etc to not disappear */
+                       output->has_quoted_part = 1;
                        arg++;
                        cant_be_null = 0x80;
                        break;
@@ -5312,10 +5363,11 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
                                debug_printf_expand("unquoted '%s', output->o_escape:%d\n", val,
                                                !!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
                                if (val && val[0]) {
-                                       n = expand_on_ifs(output, n, val);
+                                       n = expand_on_ifs(&ended_in_ifs, output, n, val);
                                        val = NULL;
                                }
                        } else { /* quoted $VAR, val will be appended below */
+                               output->has_quoted_part = 1;
                                debug_printf_expand("quoted '%s', output->o_escape:%d\n", val,
                                                !!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
                        }
@@ -5340,6 +5392,10 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
        } /* end of "while (SPECIAL_VAR_SYMBOL is found) ..." */
 
        if (arg[0]) {
+               if (ended_in_ifs) {
+                       o_addchr(output, '\0');
+                       n = o_save_ptr(output, n);
+               }
                debug_print_list("expand_vars_to_list[a]", output, n);
                /* this part is literal, and it was already pre-quoted
                 * if needed (much earlier), do not use o_addQstr here! */
@@ -5470,12 +5526,6 @@ static char **expand_assignments(char **argv, int count)
 }
 
 
-#if BB_MMU
-/* never called */
-void re_execute_shell(char ***to_free, const char *s,
-               char *g_argv0, char **g_argv,
-               char **builtin_argv) NORETURN;
-
 static void switch_off_special_sigs(unsigned mask)
 {
        unsigned sig = 0;
@@ -5495,6 +5545,12 @@ static void switch_off_special_sigs(unsigned mask)
        }
 }
 
+#if BB_MMU
+/* never called */
+void re_execute_shell(char ***to_free, const char *s,
+               char *g_argv0, char **g_argv,
+               char **builtin_argv) NORETURN;
+
 static void reset_traps_to_defaults(void)
 {
        /* This function is always called in a child shell
@@ -6714,7 +6770,6 @@ static int checkjobs(struct pipe *fg_pipe)
                                        }
                                        fg_pipe->cmds[i].cmd_exitcode = ex;
                                } else {
-                                       fg_pipe->cmds[i].is_stopped = 1;
                                        fg_pipe->stopped_cmds++;
                                }
                                debug_printf_jobs("fg_pipe: alive_cmds %d stopped_cmds %d\n",
@@ -6775,7 +6830,6 @@ static int checkjobs(struct pipe *fg_pipe)
                        }
                } else {
                        /* child stopped */
-                       pi->cmds[i].is_stopped = 1;
                        pi->stopped_cmds++;
                }
 #endif
@@ -7306,7 +7360,7 @@ static int run_list(struct pipe *pi)
                                 * and we should not execute CMD */
                                debug_printf_exec("skipped cmd because of || or &&\n");
                                last_followup = pi->followup;
-                               continue;
+                               goto dont_check_jobs_but_continue;
                        }
                }
                last_followup = pi->followup;
@@ -7445,8 +7499,10 @@ static int run_list(struct pipe *pi)
                                                        G.flag_break_continue = 0;
                                                /* else: e.g. "continue 2" should *break* once, *then* continue */
                                        } /* else: "while... do... { we are here (innermost list is not a loop!) };...done" */
-                                       if (G.depth_break_continue != 0 || fbc == BC_BREAK)
-                                               goto check_jobs_and_break;
+                                       if (G.depth_break_continue != 0 || fbc == BC_BREAK) {
+                                               checkjobs(NULL);
+                                               break;
+                                       }
                                        /* "continue": simulate end of loop */
                                        rword = RES_DONE;
                                        continue;
@@ -7454,7 +7510,6 @@ static int run_list(struct pipe *pi)
 #endif
 #if ENABLE_HUSH_FUNCTIONS
                                if (G.flag_return_in_progress == 1) {
-                                       /* same as "goto check_jobs_and_break" */
                                        checkjobs(NULL);
                                        break;
                                }
@@ -7496,6 +7551,9 @@ static int run_list(struct pipe *pi)
                if (rword == RES_IF || rword == RES_ELIF)
                        cond_code = rcode;
 #endif
+ check_jobs_and_continue:
+               checkjobs(NULL);
+ dont_check_jobs_but_continue: ;
 #if ENABLE_HUSH_LOOPS
                /* Beware of "while false; true; do ..."! */
                if (pi->next
@@ -7507,22 +7565,17 @@ static int run_list(struct pipe *pi)
                                        /* "while false; do...done" - exitcode 0 */
                                        G.last_exitcode = rcode = EXIT_SUCCESS;
                                        debug_printf_exec(": while expr is false: breaking (exitcode:EXIT_SUCCESS)\n");
-                                       goto check_jobs_and_break;
+                                       break;
                                }
                        }
                        if (rword == RES_UNTIL) {
                                if (!rcode) {
                                        debug_printf_exec(": until expr is true: breaking\n");
- check_jobs_and_break:
-                                       checkjobs(NULL);
                                        break;
                                }
                        }
                }
 #endif
-
- check_jobs_and_continue:
-               checkjobs(NULL);
        } /* for (pi) */
 
 #if ENABLE_HUSH_JOB
@@ -7768,22 +7821,6 @@ int hush_main(int argc, char **argv)
 
 #if ENABLE_FEATURE_EDITING
        G.line_input_state = new_line_input_t(FOR_SHELL);
-# if defined MAX_HISTORY && MAX_HISTORY > 0 && ENABLE_HUSH_SAVEHISTORY
-       {
-               const char *hp = get_local_var_value("HISTFILE");
-               if (!hp) {
-                       hp = get_local_var_value("HOME");
-                       if (hp) {
-                               G.line_input_state->hist_file = concat_path_file(hp, ".hush_history");
-                               //set_local_var(xasprintf("HISTFILE=%s", ...));
-                       }
-               }
-# if ENABLE_FEATURE_SH_HISTFILESIZE
-               hp = get_local_var_value("HISTFILESIZE");
-               G.line_input_state->max_history = size_from_HISTFILESIZE(hp);
-# endif
-       }
-# endif
 #endif
 
        /* Initialize some more globals to non-zero values */
@@ -8055,6 +8092,27 @@ int hush_main(int argc, char **argv)
                /* -1 is special - makes xfuncs longjmp, not exit
                 * (we reset die_sleep = 0 whereever we [v]fork) */
                enable_restore_tty_pgrp_on_exit(); /* sets die_sleep = -1 */
+
+# if ENABLE_HUSH_SAVEHISTORY && MAX_HISTORY > 0
+               {
+                       const char *hp = get_local_var_value("HISTFILE");
+                       if (!hp) {
+                               hp = get_local_var_value("HOME");
+                               if (hp)
+                                       hp = concat_path_file(hp, ".hush_history");
+                       } else {
+                               hp = xstrdup(hp);
+                       }
+                       if (hp) {
+                               G.line_input_state->hist_file = hp;
+                               //set_local_var(xasprintf("HISTFILE=%s", ...));
+                       }
+#  if ENABLE_FEATURE_SH_HISTFILESIZE
+                       hp = get_local_var_value("HISTFILESIZE");
+                       G.line_input_state->max_history = size_from_HISTFILESIZE(hp);
+#  endif
+               }
+# endif
        } else {
                install_special_sighandlers();
        }
@@ -8228,7 +8286,7 @@ static int FAST_FUNC builtin_exit(char **argv)
         * (if there are _stopped_ jobs, running ones don't count)
         * # exit
         * exit
-        # EEE (then bash exits)
+        * EEE (then bash exits)
         *
         * TODO: we can use G.exiting = -1 as indicator "last cmd was exit"
         */
@@ -8538,7 +8596,6 @@ static int FAST_FUNC builtin_fg_bg(char **argv)
        debug_printf_jobs("reviving %d procs, pgrp %d\n", pi->num_cmds, pi->pgrp);
        for (i = 0; i < pi->num_cmds; i++) {
                debug_printf_jobs("reviving pid %d\n", pi->cmds[i].pid);
-               pi->cmds[i].is_stopped = 0;
        }
        pi->stopped_cmds = 0;
 
@@ -8576,6 +8633,14 @@ static int FAST_FUNC builtin_help(char **argv UNUSED_PARAM)
 }
 #endif
 
+#if MAX_HISTORY && ENABLE_FEATURE_EDITING
+static int FAST_FUNC builtin_history(char **argv UNUSED_PARAM)
+{
+       show_history(G.line_input_state);
+       return EXIT_SUCCESS;
+}
+#endif
+
 #if ENABLE_HUSH_JOB
 static int FAST_FUNC builtin_jobs(char **argv UNUSED_PARAM)
 {
@@ -8828,6 +8893,9 @@ static int FAST_FUNC builtin_source(char **argv)
        free(arg_path);
        if (!input) {
                /* bb_perror_msg("%s", *argv); - done by fopen_or_warn */
+               /* POSIX: non-interactive shell should abort here,
+                * not merely fail. So far no one complained :)
+                */
                return EXIT_FAILURE;
        }
        close_on_exec_on(fileno(input));
@@ -8837,12 +8905,14 @@ static int FAST_FUNC builtin_source(char **argv)
        /* "we are inside sourced file, ok to use return" */
        G.flag_return_in_progress = -1;
 #endif
-       save_and_replace_G_args(&sv, argv);
+       if (argv[1])
+               save_and_replace_G_args(&sv, argv);
 
        parse_and_run_file(input);
        fclose(input);
 
-       restore_G_args(&sv, argv);
+       if (argv[1])
+               restore_G_args(&sv, argv);
 #if ENABLE_HUSH_FUNCTIONS
        G.flag_return_in_progress = sv_flg;
 #endif