od_bloaty: fix debug code
[oweals/busybox.git] / shell / hush.c
index df1b046abc982ad597ca47144dc88a7b7b78f55e..7b83c736c1ec8e77632be08d8226f9c58496ebd6 100644 (file)
  *      follow IFS rules more precisely, including update semantics
  *      tilde expansion
  *      aliases
- *      builtins mandated by standards we don't support:
- *          [un]alias, command, fc:
- *          command -v CMD: print "/path/to/CMD"
- *              prints "CMD" for builtins
- *              prints "alias ALIAS='EXPANSION'" for aliases
- *              prints nothing and sets $? to 1 if not found
- *          command -V CMD: print "CMD is /path/CMD|a shell builtin|etc"
- *          command [-p] CMD: run CMD, even if a function CMD also exists
- *              (can use this to override standalone shell as well)
- *              -p: use default $PATH
+ *      "command" missing features:
+ *          command -p CMD: run CMD using default $PATH
+ *              (can use this to override standalone shell as well?)
  *          command BLTIN: disables special-ness (e.g. errors do not abort)
+ *          command -V CMD1 CMD2 CMD3 (multiple args) (not in standard)
+ *      builtins mandated by standards we don't support:
+ *          [un]alias, fc:
  *          fc -l[nr] [BEG] [END]: list range of commands in history
  *          fc [-e EDITOR] [BEG] [END]: edit/rerun range of commands
  *          fc -s [PAT=REP] [CMD]: rerun CMD, replacing PAT with REP
 //config:      default y
 //config:      depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
 //config:
+//config:config HUSH_COMMAND
+//config:      bool "command builtin"
+//config:      default y
+//config:      depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
+//config:
 //config:config HUSH_TRAP
 //config:      bool "trap builtin"
 //config:      default y
 #define BASH_SUBSTR        ENABLE_HUSH_BASH_COMPAT
 #define BASH_SOURCE        ENABLE_HUSH_BASH_COMPAT
 #define BASH_HOSTNAME_VAR  ENABLE_HUSH_BASH_COMPAT
+#define BASH_LINENO_VAR    ENABLE_HUSH_BASH_COMPAT
 #define BASH_TEST2         (ENABLE_HUSH_BASH_COMPAT && ENABLE_HUSH_TEST)
 #define BASH_READ_D        ENABLE_HUSH_BASH_COMPAT
 
 # define MINUS_PLUS_EQUAL_QUESTION ("%#:-=+?" + 3)
 #endif
 
-#define SPECIAL_VAR_SYMBOL   3
+#define SPECIAL_VAR_SYMBOL_STR "\3"
+#define SPECIAL_VAR_SYMBOL       3
+/* The "variable" with name "\1" emits string "\3". Testcase: "echo ^C" */
+#define SPECIAL_VAR_QUOTED_SVS   1
 
 struct variable;
 
@@ -608,6 +613,9 @@ typedef enum redir_type {
 struct command {
        pid_t pid;                  /* 0 if exited */
        int assignment_cnt;         /* how many argv[i] are assignments? */
+#if BASH_LINENO_VAR
+       unsigned lineno;
+#endif
        smallint cmd_type;          /* CMD_xxx */
 #define CMD_NORMAL   0
 #define CMD_SUBSHELL 1
@@ -926,6 +934,10 @@ struct globals {
        unsigned count_SIGCHLD;
        unsigned handled_SIGCHLD;
        smallint we_have_children;
+#endif
+#if BASH_LINENO_VAR
+       unsigned lineno;
+       char *lineno_var;
 #endif
        struct FILE_list *FILE_list;
        /* Which signals have non-DFL handler (even with no traps set)?
@@ -1924,7 +1936,7 @@ static void hush_exit(int exitcode)
        if (G.exiting <= 0 && G_traps && G_traps[0] && G_traps[0][0]) {
                char *argv[3];
                /* argv[0] is unused */
-               argv[1] = G_traps[0];
+               argv[1] = xstrdup(G_traps[0]); /* copy, since EXIT trap handler may modify G_traps[0] */
                argv[2] = NULL;
                G.exiting = 1; /* prevent EXIT trap recursion */
                /* Note: G_traps[0] is not cleared!
@@ -2131,6 +2143,13 @@ static int set_local_var(char *str, unsigned flags)
        }
 
        name_len = eq_sign - str + 1; /* including '=' */
+#if BASH_LINENO_VAR
+       if (G.lineno_var) {
+               if (name_len == 7 && strncmp("LINENO", str, 6) == 0)
+                       G.lineno_var = NULL;
+       }
+#endif
+
        var_pp = &G.top_var;
        while ((cur = *var_pp) != NULL) {
                if (strncmp(cur->varstr, str, name_len) != 0) {
@@ -2252,10 +2271,16 @@ static int unset_local_var_len(const char *name, int name_len)
 
        if (!name)
                return EXIT_SUCCESS;
+
 #if ENABLE_HUSH_GETOPTS
        if (name_len == 6 && strncmp(name, "OPTIND", 6) == 0)
                G.getopt_count = 0;
 #endif
+#if BASH_LINENO_VAR
+       if (name_len == 6 && G.lineno_var && strncmp(name, "LINENO", 6) == 0)
+               G.lineno_var = NULL;
+#endif
+
        var_pp = &G.top_var;
        while ((cur = *var_pp) != NULL) {
                if (strncmp(cur->varstr, name, name_len) == 0 && cur->varstr[name_len] == '=') {
@@ -2574,6 +2599,10 @@ static int i_getch(struct in_str *i)
  out:
        debug_printf("file_get: got '%c' %d\n", ch, ch);
        i->last_char = ch;
+#if BASH_LINENO_VAR
+       if (ch == '\n')
+               G.lineno++;
+#endif
        return ch;
 }
 
@@ -3456,6 +3485,9 @@ static int done_command(struct parse_context *ctx)
        ctx->command = command = &pi->cmds[pi->num_cmds];
  clear_and_ret:
        memset(command, 0, sizeof(*command));
+#if BASH_LINENO_VAR
+       command->lineno = G.lineno;
+#endif
        return pi->num_cmds; /* used only for 0/nonzero check */
 }
 
@@ -3840,21 +3872,6 @@ static int done_word(o_string *word, struct parse_context *ctx)
                        word->o_assignment = MAYBE_ASSIGNMENT;
                }
                debug_printf_parse("word->o_assignment='%s'\n", assignment_flag[word->o_assignment]);
-
-               if (word->has_quoted_part
-                /* optimization: and if it's ("" or '') or ($v... or `cmd`...): */
-                && (word->data[0] == '\0' || word->data[0] == SPECIAL_VAR_SYMBOL)
-                /* (otherwise it's known to be not empty and is already safe) */
-               ) {
-                       /* exclude "$@" - it can expand to no word despite "" */
-                       char *p = word->data;
-                       while (p[0] == SPECIAL_VAR_SYMBOL
-                           && (p[1] & 0x7f) == '@'
-                           && p[2] == SPECIAL_VAR_SYMBOL
-                       ) {
-                               p += 3;
-                       }
-               }
                command->argv = add_string_to_strings(command->argv, xstrdup(word->data));
                debug_print_strings("word appended to argv", command->argv);
        }
@@ -4914,7 +4931,8 @@ static struct pipe *parse_stream(char **pstring,
                        next = i_peek(input);
 
                is_special = "{}<>;&|()#'" /* special outside of "str" */
-                               "\\$\"" IF_HUSH_TICK("`"); /* always special */
+                               "\\$\"" IF_HUSH_TICK("`") /* always special */
+                               SPECIAL_VAR_SYMBOL_STR;
                /* Are { and } special here? */
                if (ctx.command->argv /* word [word]{... - non-special */
                 || dest.length       /* word{... - non-special */
@@ -5186,8 +5204,14 @@ static struct pipe *parse_stream(char **pstring,
                /* Note: nommu_addchr(&ctx.as_string, ch) is already done */
 
                switch (ch) {
-               case '#': /* non-comment #: "echo a#b" etc */
-                       o_addQchr(&dest, ch);
+               case SPECIAL_VAR_SYMBOL:
+                       /* Convert raw ^C to corresponding special variable reference */
+                       o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+                       o_addchr(&dest, SPECIAL_VAR_QUOTED_SVS);
+                       /* fall through */
+               case '#':
+                       /* non-comment #: "echo a#b" etc */
+                       o_addchr(&dest, ch);
                        break;
                case '\\':
                        if (next == EOF) {
@@ -5229,6 +5253,11 @@ static struct pipe *parse_stream(char **pstring,
                                        nommu_addchr(&ctx.as_string, ch);
                                        if (ch == '\'')
                                                break;
+                                       if (ch == SPECIAL_VAR_SYMBOL) {
+                                               /* Convert raw ^C to corresponding special variable reference */
+                                               o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+                                               o_addchr(&dest, SPECIAL_VAR_QUOTED_SVS);
+                                       }
                                        o_addqchr(&dest, ch);
                                }
                        }
@@ -6041,6 +6070,11 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
                        arg++;
                        cant_be_null = 0x80;
                        break;
+               case SPECIAL_VAR_QUOTED_SVS:
+                       /* <SPECIAL_VAR_SYMBOL><SPECIAL_VAR_QUOTED_SVS><SPECIAL_VAR_SYMBOL> */
+                       arg++;
+                       val = SPECIAL_VAR_SYMBOL_STR;
+                       break;
 #if ENABLE_HUSH_TICK
                case '`': /* <SPECIAL_VAR_SYMBOL>`cmd<SPECIAL_VAR_SYMBOL> */
                        *p = '\0'; /* replace trailing <SPECIAL_VAR_SYMBOL> */
@@ -6199,7 +6233,7 @@ static char *expand_string_to_string(const char *str, int do_unbackslash)
        return (char*)list;
 }
 
-/* Used for "eval" builtin and case string */
+#if ENABLE_HUSH_CASE
 static char* expand_strvec_to_string(char **argv)
 {
        char **list;
@@ -6221,6 +6255,7 @@ static char* expand_strvec_to_string(char **argv)
        debug_printf_expand("strvec_to_string='%s'\n", (char*)list);
        return (char*)list;
 }
+#endif
 
 static char **expand_assignments(char **argv, int count)
 {
@@ -6513,8 +6548,17 @@ static void parse_and_run_string(const char *s)
 static void parse_and_run_file(FILE *f)
 {
        struct in_str input;
+#if BASH_LINENO_VAR
+       unsigned sv;
+
+       sv = G.lineno;
+       G.lineno = 1;
+#endif
        setup_file_in_str(&input, f);
        parse_and_run_stream(&input, ';');
+#if BASH_LINENO_VAR
+       G.lineno = sv;
+#endif
 }
 
 #if ENABLE_HUSH_TICK
@@ -7330,6 +7374,32 @@ static void dump_cmd_in_x_mode(char **argv)
 # define dump_cmd_in_x_mode(argv) ((void)0)
 #endif
 
+#if ENABLE_HUSH_COMMAND
+static void if_command_vV_print_and_exit(char opt_vV, char *cmd, const char *explanation)
+{
+       char *to_free;
+
+       if (!opt_vV)
+               return;
+
+       to_free = NULL;
+       if (!explanation) {
+               char *path = getenv("PATH");
+               explanation = to_free = find_executable(cmd, &path); /* path == NULL is ok */
+               if (!explanation)
+                       _exit(1); /* PROG was not found */
+               if (opt_vV != 'V')
+                       cmd = to_free; /* -v PROG prints "/path/to/PROG" */
+       }
+       printf((opt_vV == 'V') ? "%s is %s\n" : "%s\n", cmd, explanation);
+       free(to_free);
+       fflush_all();
+       _exit(0);
+}
+#else
+# define if_command_vV_print_and_exit(a,b,c) ((void)0)
+#endif
+
 #if BB_MMU
 #define pseudo_exec_argv(nommu_save, argv, assignment_cnt, argv_expanded) \
        pseudo_exec_argv(argv, assignment_cnt, argv_expanded)
@@ -7350,7 +7420,11 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
                char **argv, int assignment_cnt,
                char **argv_expanded)
 {
+       const struct built_in_command *x;
        char **new_env;
+#if ENABLE_HUSH_COMMAND
+       char opt_vV = 0;
+#endif
 
        new_env = expand_assignments(argv, assignment_cnt);
        dump_cmd_in_x_mode(new_env);
@@ -7399,21 +7473,58 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
        }
 #endif
 
+#if ENABLE_HUSH_COMMAND
+       /* "command BAR": run BAR without looking it up among functions
+        * "command -v BAR": print "BAR" or "/path/to/BAR"; or exit 1
+        * "command -V BAR": print "BAR is {a function,a shell builtin,/path/to/BAR}"
+        */
+       while (strcmp(argv[0], "command") == 0 && argv[1]) {
+               char *p;
+
+               argv++;
+               p = *argv;
+               if (p[0] != '-' || !p[1])
+                       continue; /* bash allows "command command command [-OPT] BAR" */
+
+               for (;;) {
+                       p++;
+                       switch (*p) {
+                       case '\0':
+                               argv++;
+                               p = *argv;
+                               if (p[0] != '-' || !p[1])
+                                       goto after_opts;
+                               continue; /* next arg is also -opts, process it too */
+                       case 'v':
+                       case 'V':
+                               opt_vV = *p;
+                               continue;
+                       default:
+                               bb_error_msg_and_die("%s: %s: invalid option", "command", argv[0]);
+                       }
+               }
+       }
+ after_opts:
+# if ENABLE_HUSH_FUNCTIONS
+       if (opt_vV && find_function(argv[0]))
+               if_command_vV_print_and_exit(opt_vV, argv[0], "a function");
+# endif
+#endif
+
        /* Check if the command matches any of the builtins.
         * Depending on context, this might be redundant.  But it's
         * easier to waste a few CPU cycles than it is to figure out
         * if this is one of those cases.
         */
-       {
-               /* On NOMMU, it is more expensive to re-execute shell
-                * just in order to run echo or test builtin.
-                * It's better to skip it here and run corresponding
-                * non-builtin later. */
-               const struct built_in_command *x;
-               x = BB_MMU ? find_builtin(argv[0]) : find_builtin1(argv[0]);
-               if (x) {
-                       exec_builtin(&nommu_save->argv_from_re_execing, x, argv);
-               }
+       /* Why "BB_MMU ? :" difference in logic? -
+        * On NOMMU, it is more expensive to re-execute shell
+        * just in order to run echo or test builtin.
+        * It's better to skip it here and run corresponding
+        * non-builtin later. */
+       x = BB_MMU ? find_builtin(argv[0]) : find_builtin1(argv[0]);
+       if (x) {
+               if_command_vV_print_and_exit(opt_vV, argv[0], "a shell builtin");
+               exec_builtin(&nommu_save->argv_from_re_execing, x, argv);
        }
 
 #if ENABLE_FEATURE_SH_STANDALONE
@@ -7421,6 +7532,7 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
        {
                int a = find_applet_by_name(argv[0]);
                if (a >= 0) {
+                       if_command_vV_print_and_exit(opt_vV, argv[0], "an applet");
 # if BB_MMU /* see above why on NOMMU it is not allowed */
                        if (APPLET_IS_NOEXEC(a)) {
                                /* Do not leak open fds from opened script files etc.
@@ -7450,6 +7562,7 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
 #if ENABLE_FEATURE_SH_STANDALONE || BB_MMU
  skip:
 #endif
+       if_command_vV_print_and_exit(opt_vV, argv[0], NULL);
        execvp_or_die(argv);
 }
 
@@ -7992,6 +8105,11 @@ static NOINLINE int run_pipe(struct pipe *pi)
                char **new_env = NULL;
                struct variable *old_vars = NULL;
 
+#if BASH_LINENO_VAR
+               if (G.lineno_var)
+                       strcpy(G.lineno_var + sizeof("LINENO=")-1, utoa(command->lineno));
+#endif
+
                if (argv[command->assignment_cnt] == NULL) {
                        /* Assignments, but no command */
                        /* Ensure redirects take effect (that is, create files).
@@ -8196,6 +8314,11 @@ static NOINLINE int run_pipe(struct pipe *pi)
                if (cmd_no < pi->num_cmds)
                        xpiped_pair(pipefds);
 
+#if BASH_LINENO_VAR
+               if (G.lineno_var)
+                       strcpy(G.lineno_var + sizeof("LINENO=")-1, utoa(command->lineno));
+#endif
+
                command->pid = BB_MMU ? fork() : vfork();
                if (!command->pid) { /* child */
 #if ENABLE_HUSH_JOB
@@ -8831,17 +8954,19 @@ int hush_main(int argc, char **argv)
 #if !BB_MMU
        G.argv0_for_re_execing = argv[0];
 #endif
+
        /* Deal with HUSH_VERSION */
+       debug_printf_env("unsetenv '%s'\n", "HUSH_VERSION");
+       unsetenv("HUSH_VERSION"); /* in case it exists in initial env */
        shell_ver = xzalloc(sizeof(*shell_ver));
        shell_ver->flg_export = 1;
        shell_ver->flg_read_only = 1;
        /* Code which handles ${var<op>...} needs writable values for all variables,
         * therefore we xstrdup: */
        shell_ver->varstr = xstrdup(hush_version_str);
+
        /* Create shell local variables from the values
         * currently living in the environment */
-       debug_printf_env("unsetenv '%s'\n", "HUSH_VERSION");
-       unsetenv("HUSH_VERSION"); /* in case it exists in initial env */
        G.top_var = shell_ver;
        cur_var = G.top_var;
        e = environ;
@@ -8907,6 +9032,14 @@ int hush_main(int argc, char **argv)
         */
 #endif
 
+#if BASH_LINENO_VAR
+       if (BASH_LINENO_VAR) {
+               char *p = xasprintf("LINENO=%*s", (int)(sizeof(int)*3), "");
+               set_local_var(p, /*flags*/ 0);
+               G.lineno_var = p; /* can't assign before set_local_var("LINENO=...") */
+       }
+#endif
+
 #if ENABLE_FEATURE_EDITING
        G.line_input_state = new_line_input_t(FOR_SHELL);
 #endif
@@ -9349,13 +9482,34 @@ static int FAST_FUNC builtin_eval(char **argv)
        int rcode = EXIT_SUCCESS;
 
        argv = skip_dash_dash(argv);
-       if (*argv) {
-               char *str = expand_strvec_to_string(argv);
+       if (argv[0]) {
+               char *str = NULL;
+
+               if (argv[1]) {
+                       /* "The eval utility shall construct a command by
+                        * concatenating arguments together, separating
+                        * each with a <space> character."
+                        */
+                       char *p;
+                       unsigned len = 0;
+                       char **pp = argv;
+                       do
+                               len += strlen(*pp) + 1;
+                       while (*++pp);
+                       str = p = xmalloc(len);
+                       pp = argv;
+                       do {
+                               p = stpcpy(p, *pp);
+                               *p++ = ' ';
+                       } while (*++pp);
+                       p[-1] = '\0';
+               }
+
                /* bash:
                 * eval "echo Hi; done" ("done" is syntax error):
                 * "echo Hi" will not execute too.
                 */
-               parse_and_run_string(str);
+               parse_and_run_string(str ? str : argv[0]);
                free(str);
                rcode = G.last_exitcode;
        }
@@ -9858,7 +10012,7 @@ static int FAST_FUNC builtin_set(char **argv)
 
        /* Nothing known, so abort */
  error:
-       bb_error_msg("set: %s: invalid option", arg);
+       bb_error_msg("%s: %s: invalid option", "set", arg);
        return EXIT_FAILURE;
 }
 #endif