hush: make builtins work in pipes (eval ... | ...) on NOMMU
authorDenys Vlasenko <vda.linux@googlemail.com>
Sat, 6 Jun 2009 18:59:55 +0000 (20:59 +0200)
committerDenys Vlasenko <vda.linux@googlemail.com>
Sat, 6 Jun 2009 18:59:55 +0000 (20:59 +0200)
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
shell/ash.c
shell/hush.c
shell/lash_unused.c
shell/msh.c

index 1e7429cb104c4e2bff7d97b92ee5f744683d3721..cc5802c30f06750c2989b85a66a12bcff66ed9eb 100644 (file)
@@ -12233,7 +12233,7 @@ helpcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
        unsigned col;
        unsigned i;
 
-       out1fmt("\n"
+       out1fmt(
                "Built-in commands:\n"
                "------------------\n");
        for (col = 0, i = 0; i < ARRAY_SIZE(builtintab); i++) {
index f34fdd402db344e39758bc11783dae85abe85152..e827660936cf6df515b9d506440dac97c9c871b5 100644 (file)
@@ -50,7 +50,7 @@
  *
  * TODOs:
  *      grep for "TODO" and fix (some of them are easy)
- *      builtins: ulimit, local
+ *      builtins: ulimit
  *      follow IFS rules more precisely, including update semantics
  *      export builtin should be special, its arguments are assignments
  *          and therefore expansion of them should be "one-word" expansion:
@@ -593,56 +593,58 @@ struct built_in_command {
 #endif
 };
 
-/* For now, echo and test are unconditionally enabled.
- * Maybe make it configurable? */
-static const struct built_in_command bltins[] = {
-       BLTIN("."       , builtin_source  , "Run commands in a file"),
-       BLTIN(":"       , builtin_true    , "No-op"),
-       BLTIN("["       , builtin_test    , "Test condition"),
+static const struct built_in_command bltins1[] = {
+       BLTIN("."        , builtin_source  , "Run commands in a file"),
+       BLTIN(":"        , builtin_true    , NULL),
 #if ENABLE_HUSH_JOB
-       BLTIN("bg"      , builtin_fg_bg   , "Resume a job in the background"),
+       BLTIN("bg"       , builtin_fg_bg   , "Resume a job in the background"),
 #endif
 #if ENABLE_HUSH_LOOPS
-       BLTIN("break"   , builtin_break   , "Exit from a loop"),
+       BLTIN("break"    , builtin_break   , "Exit from a loop"),
 #endif
-       BLTIN("cd"      , builtin_cd      , "Change directory"),
+       BLTIN("cd"       , builtin_cd      , "Change directory"),
 #if ENABLE_HUSH_LOOPS
-       BLTIN("continue", builtin_continue, "Start new loop iteration"),
+       BLTIN("continue" , builtin_continue, "Start new loop iteration"),
 #endif
-       BLTIN("echo"    , builtin_echo    , "Write to stdout"),
-       BLTIN("eval"    , builtin_eval    , "Construct and run shell command"),
-       BLTIN("exec"    , builtin_exec    , "Execute command, don't return to shell"),
-       BLTIN("exit"    , builtin_exit    , "Exit"),
-       BLTIN("export"  , builtin_export  , "Set environment variable"),
+       BLTIN("eval"     , builtin_eval    , "Construct and run shell command"),
+       BLTIN("exec"     , builtin_exec    , "Execute command, don't return to shell"),
+       BLTIN("exit"     , builtin_exit    , "Exit"),
+       BLTIN("export"   , builtin_export  , "Set environment variables"),
 #if ENABLE_HUSH_JOB
-       BLTIN("fg"      , builtin_fg_bg   , "Bring job into the foreground"),
+       BLTIN("fg"       , builtin_fg_bg   , "Bring job into the foreground"),
 #endif
 #if ENABLE_HUSH_HELP
-       BLTIN("help"    , builtin_help    , "List shell built-in commands"),
+       BLTIN("help"     , builtin_help    , NULL),
 #endif
 #if ENABLE_HUSH_JOB
-       BLTIN("jobs"    , builtin_jobs    , "List active jobs"),
+       BLTIN("jobs"     , builtin_jobs    , "List jobs"),
 #endif
 #if ENABLE_HUSH_LOCAL
-       BLTIN("local"   , builtin_local   , "Set local variable"),
+       BLTIN("local"    , builtin_local   , "Set local variables"),
 #endif
 #if HUSH_DEBUG
-       BLTIN("memleak" , builtin_memleak , "Debug tool"),
+       BLTIN("memleak"  , builtin_memleak , NULL),
 #endif
-       BLTIN("pwd"     , builtin_pwd     , "Print current directory"),
-       BLTIN("read"    , builtin_read    , "Input environment variable"),
+       BLTIN("read"     , builtin_read    , "Input into variable"),
 #if ENABLE_HUSH_FUNCTIONS
-       BLTIN("return"  , builtin_return  , "Return from a function"),
-#endif
-       BLTIN("set"     , builtin_set     , "Set/unset shell local variables"),
-       BLTIN("shift"   , builtin_shift   , "Shift positional parameters"),
-       BLTIN("test"    , builtin_test    , "Test condition"),
-       BLTIN("trap"    , builtin_trap    , "Trap signals"),
-       BLTIN("type"    , builtin_type    , "Write a description of command type"),
-//     BLTIN("ulimit"  , builtin_ulimit  , "Control resource limits"),
-       BLTIN("umask"   , builtin_umask   , "Set file creation mask"),
-       BLTIN("unset"   , builtin_unset   , "Unset environment variable"),
-       BLTIN("wait"    , builtin_wait    , "Wait for process"),
+       BLTIN("return"   , builtin_return  , "Return from a function"),
+#endif
+       BLTIN("set"      , builtin_set     , "Set/unset positional parameters"),
+       BLTIN("shift"    , builtin_shift   , "Shift positional parameters"),
+       BLTIN("trap"     , builtin_trap    , "Trap signals"),
+       BLTIN("type"     , builtin_type    , "Write a description of command type"),
+//     BLTIN("ulimit"   , builtin_ulimit  , "Control resource limits"),
+       BLTIN("umask"    , builtin_umask   , "Set file creation mask"),
+       BLTIN("unset"    , builtin_unset   , "Unset variables"),
+       BLTIN("wait"     , builtin_wait    , "Wait for process"),
+};
+/* For now, echo and test are unconditionally enabled.
+ * Maybe make it configurable? */
+static const struct built_in_command bltins2[] = {
+       BLTIN("["        , builtin_test    , NULL),
+       BLTIN("echo"     , builtin_echo    , NULL),
+       BLTIN("pwd"      , builtin_pwd     , NULL),
+       BLTIN("test"     , builtin_test    , NULL),
 };
 
 
@@ -1231,15 +1233,17 @@ static int check_and_run_traps(int sig)
 }
 
 
-static const char *set_cwd(void)
+static const char *get_cwd(int force)
 {
-       /* xrealloc_getcwd_or_warn(arg) calls free(arg),
-        * we must not try to free(bb_msg_unknown) */
-       if (G.cwd == bb_msg_unknown)
-               G.cwd = NULL;
-       G.cwd = xrealloc_getcwd_or_warn((char *)G.cwd);
-       if (!G.cwd)
-               G.cwd = bb_msg_unknown;
+       if (force || G.cwd == NULL) {
+               /* xrealloc_getcwd_or_warn(arg) calls free(arg),
+                * we must not try to free(bb_msg_unknown) */
+               if (G.cwd == bb_msg_unknown)
+                       G.cwd = NULL;
+               G.cwd = xrealloc_getcwd_or_warn((char *)G.cwd);
+               if (!G.cwd)
+                       G.cwd = bb_msg_unknown;
+       }
        return G.cwd;
 }
 
@@ -1579,7 +1583,7 @@ static const char* setup_prompt_string(int promptmode)
                /* Set up the prompt */
                if (promptmode == 0) { /* PS1 */
                        free((char*)G.PS1);
-                       G.PS1 = xasprintf("%s %c ", G.cwd, (geteuid() != 0) ? '$' : '#');
+                       G.PS1 = xasprintf("%s %c ", get_cwd(0), (geteuid() != 0) ? '$' : '#');
                        prompt_str = G.PS1;
                } else
                        prompt_str = G.PS2;
@@ -2511,7 +2515,9 @@ static char **expand_assignments(char **argv, int count)
 
 #if BB_MMU
 /* never called */
-void re_execute_shell(char ***to_free, const char *s, char *argv0, char **argv);
+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)
 {
@@ -2563,10 +2569,14 @@ static void reset_traps_to_defaults(void)
 
 #else /* !BB_MMU */
 
-static void re_execute_shell(char ***to_free, const char *s, char *g_argv0, char **g_argv) NORETURN;
-static void re_execute_shell(char ***to_free, const char *s, char *g_argv0, char **g_argv)
+static void re_execute_shell(char ***to_free, const char *s,
+               char *g_argv0, char **g_argv,
+               char **builtin_argv) NORETURN;
+static void re_execute_shell(char ***to_free, const char *s,
+               char *g_argv0, char **g_argv,
+               char **builtin_argv)
 {
-       char param_buf[sizeof("-$%x:%x:%x:%x") + sizeof(unsigned) * 4];
+       char param_buf[sizeof("-$%x:%x:%x:%x:%x") + sizeof(unsigned) * 2];
        char *heredoc_argv[4];
        struct variable *cur;
 #if ENABLE_HUSH_FUNCTIONS
@@ -2585,16 +2595,22 @@ static void re_execute_shell(char ***to_free, const char *s, char *g_argv0, char
                goto do_exec;
        }
 
-       sprintf(param_buf, "-$%x:%x:%x" IF_HUSH_LOOPS(":%x")
+       cnt = 0;
+       pp = builtin_argv;
+       if (pp) while (*pp++)
+               cnt++;
+
+       sprintf(param_buf, "-$%x:%x:%x:%x" IF_HUSH_LOOPS(":%x")
                        , (unsigned) G.root_pid
                        , (unsigned) G.last_bg_pid
                        , (unsigned) G.last_exitcode
+                       , cnt
                        IF_HUSH_LOOPS(, G.depth_of_loop)
                        );
        /* 1:hush 2:-$<pid>:<pid>:<exitcode>:<depth> <vars...> <funcs...>
         * 3:-c 4:<cmd> 5:<arg0> <argN...> 6:NULL
         */
-       cnt = 6;
+       cnt += 6;
        for (cur = G.top_var; cur; cur = cur->next) {
                if (!cur->flg_export || cur->flg_read_only)
                        cnt += 2;
@@ -2648,6 +2664,11 @@ static void re_execute_shell(char ***to_free, const char *s, char *g_argv0, char
         */
        *pp++ = (char *) "-c";
        *pp++ = (char *) s;
+       if (builtin_argv) {
+               while (*++builtin_argv)
+                       *pp++ = *builtin_argv;
+               *pp++ = (char *) "";
+       }
        *pp++ = g_argv0;
        while (*g_argv)
                *pp++ = *g_argv++;
@@ -2735,7 +2756,7 @@ static void setup_heredoc(struct redir_struct *redir)
 #else
                /* Delegate blocking writes to another process */
                xmove_fd(pair.wr, STDOUT_FILENO);
-               re_execute_shell(&to_free, heredoc, NULL, NULL);
+               re_execute_shell(&to_free, heredoc, NULL, NULL, NULL);
 #endif
        }
        /* parent */
@@ -2958,17 +2979,31 @@ static char *find_in_path(const char *arg)
        return ret;
 }
 
-static const struct built_in_command* find_builtin(const char *name)
+static const struct built_in_command* find_builtin_helper(const char *name,
+               const struct built_in_command *x,
+               const struct built_in_command *end)
 {
-       const struct built_in_command *x;
-       for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
-               if (strcmp(name, x->cmd) != 0)
+       while (x != end) {
+               if (strcmp(name, x->cmd) != 0) {
+                       x++;
                        continue;
+               }
                debug_printf_exec("found builtin '%s'\n", name);
                return x;
        }
        return NULL;
 }
+static const struct built_in_command* find_builtin1(const char *name)
+{
+       return find_builtin_helper(name, bltins1, &bltins1[ARRAY_SIZE(bltins1)]);
+}
+static const struct built_in_command* find_builtin(const char *name)
+{
+       const struct built_in_command *x = find_builtin1(name);
+       if (x)
+               return x;
+       return find_builtin_helper(name, bltins2, &bltins2[ARRAY_SIZE(bltins2)]);
+}
 
 #if ENABLE_HUSH_FUNCTIONS
 static const struct function *find_function(const char *name)
@@ -3084,7 +3119,8 @@ static void exec_function(nommu_save_t *nommu_save,
        re_execute_shell(&nommu_save->argv_from_re_execing,
                        funcp->body_as_string,
                        G.global_argv[0],
-                       argv + 1);
+                       argv + 1,
+                       NULL);
 # endif
 }
 
@@ -3148,6 +3184,37 @@ static int run_function(const struct function *funcp, char **argv)
 #endif /* ENABLE_HUSH_FUNCTIONS */
 
 
+# if BB_MMU
+#define exec_builtin(nommu_save, x, argv) \
+       exec_builtin(x, argv)
+# else
+#define exec_builtin(nommu_save, x, argv) \
+       exec_builtin(nommu_save, argv)
+# endif
+static void exec_builtin(nommu_save_t *nommu_save,
+               const struct built_in_command *x,
+               char **argv) NORETURN;
+static void exec_builtin(nommu_save_t *nommu_save,
+               const struct built_in_command *x,
+               char **argv)
+{
+# if BB_MMU
+       int rcode = x->function(argv);
+       fflush(NULL);
+       _exit(rcode);
+# else
+       /* On NOMMU, we must never block!
+        * Example: { sleep 99 | read line; } & echo Ok
+        */
+       re_execute_shell(&nommu_save->argv_from_re_execing,
+                       argv[0],
+                       G.global_argv[0],
+                       G.global_argv + 1,
+                       argv);
+# endif
+}
+
+
 #if BB_MMU
 #define pseudo_exec_argv(nommu_save, argv, assignment_cnt, argv_expanded) \
        pseudo_exec_argv(argv, assignment_cnt, argv_expanded)
@@ -3197,27 +3264,22 @@ static void pseudo_exec_argv(nommu_save_t *nommu_save,
                goto skip;
 #endif
 
-       /* On NOMMU, we must never block!
-        * Example: { sleep 99999 | read line } & echo Ok
-        * read builtin will block on read syscall, leaving parent blocked
-        * in vfork. Therefore we can't do this:
-        */
-#if BB_MMU
        /* 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.
         */
        {
-               int rcode;
-               const struct built_in_command *x = find_builtin(argv[0]);
+               /* 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) {
-                       rcode = x->function(argv);
-                       fflush(NULL);
-                       _exit(rcode);
+                       exec_builtin(nommu_save, x, argv);
                }
        }
-#endif
 #if ENABLE_HUSH_FUNCTIONS
        /* Check if the command matches any functions */
        {
@@ -3292,7 +3354,8 @@ static void pseudo_exec(nommu_save_t *nommu_save,
                re_execute_shell(&nommu_save->argv_from_re_execing,
                                command->group_as_string,
                                G.global_argv[0],
-                               G.global_argv + 1);
+                               G.global_argv + 1,
+                               NULL);
 #endif
        }
 
@@ -5030,7 +5093,8 @@ static FILE *generate_stream_from_string(const char *s)
                re_execute_shell(&to_free,
                                s,
                                G.global_argv[0],
-                               G.global_argv + 1);
+                               G.global_argv + 1,
+                               NULL);
 #endif
        }
 
@@ -6240,6 +6304,7 @@ int hush_main(int argc, char **argv)
        };
        int signal_mask_is_inited = 0;
        int opt;
+       unsigned builtin_argc;
        char **e;
        struct variable *cur_var;
 
@@ -6277,7 +6342,6 @@ int hush_main(int argc, char **argv)
        G.global_argc = argc;
        G.global_argv = argv;
        /* Initialize some more globals to non-zero values */
-       set_cwd();
        cmdedit_update_prompt();
 
        if (setjmp(die_jmp)) {
@@ -6297,6 +6361,7 @@ int hush_main(int argc, char **argv)
 
        /* Parse options */
        /* http://www.opengroup.org/onlinepubs/9699919799/utilities/sh.html */
+       builtin_argc = 0;
        while (1) {
                opt = getopt(argc, argv, "c:xins"
 #if !BB_MMU
@@ -6310,15 +6375,40 @@ int hush_main(int argc, char **argv)
                        break;
                switch (opt) {
                case 'c':
+                       /* Possibilities:
+                        * sh ... -c 'script'
+                        * sh ... -c 'script' ARG0 [ARG1...]
+                        * On NOMMU, if builtin_argc != 0,
+                        * sh ... -c 'builtin' [BARGV...] "" ARG0 [ARG1...]
+                        * "" needs to be replaced with NULL
+                        * and BARGV vector fed to builtin function.
+                        * Note: this form never happens:
+                        * sh ... -c 'builtin' [BARGV...] ""
+                        */
                        if (!G.root_pid)
                                G.root_pid = getpid();
                        G.global_argv = argv + optind;
-                       if (!argv[optind]) {
-                               /* -c 'script' (no params): prevent empty $0 */
-                               *--G.global_argv = argv[0];
-                               optind--;
-                       } /* else -c 'script' PAR0 PAR1: $0 is PAR0 */
                        G.global_argc = argc - optind;
+                       if (builtin_argc) {
+                               /* -c 'builtin' [BARGV...] "" ARG0 [ARG1...] */
+                               const struct built_in_command *x;
+
+                               block_signals(0); /* 0: called 1st time */
+                               x = find_builtin(optarg);
+                               if (x) { /* paranoia */
+                                       G.global_argc -= builtin_argc; /* skip [BARGV...] "" */
+                                       G.global_argv += builtin_argc;
+                                       G.global_argv[-1] = NULL; /* replace "" */
+                                       G.last_exitcode = x->function(argv + optind - 1);
+                               }
+                               goto final_return;
+                       }
+                       if (!G.global_argv[0]) {
+                               /* -c 'script' (no params): prevent empty $0 */
+                               G.global_argv--; /* points to argv[i] of 'script' */
+                               G.global_argv[0] = argv[0];
+                               G.global_argc--;
+                       } /* else -c 'script' ARG0 [ARG1...]: $0 is ARG0 */
                        block_signals(0); /* 0: called 1st time */
                        parse_and_run_string(optarg);
                        goto final_return;
@@ -6341,6 +6431,8 @@ int hush_main(int argc, char **argv)
                        G.last_bg_pid = bb_strtou(optarg, &optarg, 16);
                        optarg++;
                        G.last_exitcode = bb_strtou(optarg, &optarg, 16);
+                       optarg++;
+                       builtin_argc = bb_strtou(optarg, &optarg, 16);
 # if ENABLE_HUSH_LOOPS
                        optarg++;
                        G.depth_of_loop = bb_strtou(optarg, &optarg, 16);
@@ -6617,7 +6709,7 @@ static int FAST_FUNC builtin_cd(char **argv)
                bb_perror_msg("cd: %s", newdir);
                return EXIT_FAILURE;
        }
-       set_cwd();
+       get_cwd(1);
        return EXIT_SUCCESS;
 }
 
@@ -6663,17 +6755,16 @@ static int FAST_FUNC builtin_exit(char **argv)
 
 static void print_escaped(const char *s)
 {
+       if (*s == '\'')
+               goto squote;
        do {
-               if (*s != '\'') {
-                       const char *p;
-
-                       p = strchrnul(s, '\'');
-                       /* print 'xxxx', possibly just '' */
-                       printf("'%.*s'", (int)(p - s), s);
-                       if (*p == '\0')
-                               break;
-                       s = p;
-               }
+               const char *p = strchrnul(s, '\'');
+               /* print 'xxxx', possibly just '' */
+               printf("'%.*s'", (int)(p - s), s);
+               if (*p == '\0')
+                       break;
+               s = p;
+ squote:
                /* s points to '; print "'''...'''" */
                putchar('"');
                do putchar('\''); while (*++s == '\'');
@@ -6980,13 +7071,14 @@ static int FAST_FUNC builtin_help(char **argv UNUSED_PARAM)
 {
        const struct built_in_command *x;
 
-       printf("\n"
+       printf(
                "Built-in commands:\n"
                "------------------\n");
-       for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
-               printf("%s\t%s\n", x->cmd, x->descr);
+       for (x = bltins1; x != &bltins1[ARRAY_SIZE(bltins1)]; x++) {
+               if (x->descr)
+                       printf("%s\t%s\n", x->cmd, x->descr);
        }
-       printf("\n\n");
+       bb_putchar('\n');
        return EXIT_SUCCESS;
 }
 #endif
@@ -7041,7 +7133,7 @@ static int FAST_FUNC builtin_memleak(char **argv UNUSED_PARAM)
 
 static int FAST_FUNC builtin_pwd(char **argv UNUSED_PARAM)
 {
-       puts(set_cwd());
+       puts(get_cwd(0));
        return EXIT_SUCCESS;
 }
 
index 21ea5471b3e44d3da1227e58a181f9c4af46bec4..2574987c5c07aa6be30ed3b083905a0ebeb16b4c 100644 (file)
@@ -312,7 +312,7 @@ static int builtin_help(struct child_prog UNUSED_PARAM *dummy)
 {
        const struct built_in_command *x;
 
-       printf("\n"
+       printf(
                "Built-in commands:\n"
                "------------------\n");
        for (x = bltins; x <= &VEC_LAST(bltins); x++) {
index dffacf02adac12befb6f19d093b60b185f532ce5..fe85a8170e4ce514ad311f12170c67dd2cf3eb0a 100644 (file)
@@ -3174,7 +3174,7 @@ static int dohelp(struct op *t UNUSED_PARAM, char **args UNUSED_PARAM)
        int col;
        const struct builtincmd *x;
 
-       printf("\n"
+       printf(
                "Built-in commands:\n"
                "------------------\n");