hush: NOMMU-safe support of big heredocs
authorDenis Vlasenko <vda.linux@googlemail.com>
Tue, 7 Apr 2009 10:52:40 +0000 (10:52 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Tue, 7 Apr 2009 10:52:40 +0000 (10:52 -0000)
function                                             old     new   delta
setup_heredoc                                        116     215     +99

shell/hush.c

index dc59d73bbc45f83f35f60a733642f725cc5f7358..f7e5fbc5b0af5fd4c8ff9f679ee10b8719da1927 100644 (file)
@@ -22,7 +22,7 @@
  *
  * Other credits:
  *      o_addchr derived from similar w_addchar function in glibc-2.2.
- *      setup_redirect, redirect_opt_num, and big chunks of main
+ *      parse_redirect, redirect_opt_num, and big chunks of main
  *      and many builtins derived from contributions by Erik Andersen.
  *      Miscellaneous bugfixes from Matt Kraai.
  *
@@ -75,6 +75,9 @@
 #endif
 #include "math.h"
 #include "match.h"
+#ifndef PIPE_BUF
+# define PIPE_BUF 4096           /* amount of buffering in a pipe */
+#endif
 
 #ifdef WANT_TO_TEST_NOMMU
 # undef BB_MMU
@@ -2052,26 +2055,186 @@ static char **expand_assignments(char **argv, int count)
 }
 
 
-//TODO: fix big string case!
+#if BB_MMU
+void re_execute_shell(const char *s, int is_heredoc); /* never called */
+#define clean_up_after_re_execute() ((void)0)
+static void reset_traps_to_defaults(void)
+{
+       unsigned sig;
+       int dirty;
+
+       if (!G.traps)
+               return;
+       dirty = 0;
+       for (sig = 0; sig < NSIG; sig++) {
+               if (!G.traps[sig])
+                       continue;
+               free(G.traps[sig]);
+               G.traps[sig] = NULL;
+               /* There is no signal for 0 (EXIT) */
+               if (sig == 0)
+                       continue;
+               /* there was a trap handler, we are removing it
+                * (if sig has non-DFL handling,
+                * we don't need to do anything) */
+               if (sig < 32 && (G.non_DFL_mask & (1 << sig)))
+                       continue;
+               sigdelset(&G.blocked_set, sig);
+               dirty = 1;
+       }
+       if (dirty)
+               sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
+}
+
+#else /* !BB_MMU */
+
+static void re_execute_shell(const char *s, int is_heredoc) NORETURN;
+static void re_execute_shell(const char *s, int is_heredoc)
+{
+       char param_buf[sizeof("-$%x:%x:%x:%x") + sizeof(unsigned) * 4];
+       struct variable *cur;
+       char **argv, **pp, **pp2;
+       unsigned cnt;
+
+       sprintf(param_buf, "-$%x:%x:%x" USE_HUSH_LOOPS(":%x")
+                       , (unsigned) G.root_pid
+                       , (unsigned) G.last_bg_pid
+                       , (unsigned) G.last_exitcode
+                       USE_HUSH_LOOPS(, G.depth_of_loop)
+                       );
+       /* 1:hush 2:-$<pid>:<pid>:<exitcode>:<depth> <vars...>
+        * 3:-c 4:<cmd> <argN...> 5:NULL
+        */
+       cnt = 5 + G.global_argc;
+       for (cur = G.top_var; cur; cur = cur->next) {
+               if (!cur->flg_export || cur->flg_read_only)
+                       cnt += 2;
+       }
+       G.argv_from_re_execing = pp = xzalloc(sizeof(argv[0]) * cnt);
+       *pp++ = (char *) G.argv0_for_re_execing;
+       *pp++ = param_buf;
+       for (cur = G.top_var; cur; cur = cur->next) {
+               if (cur->varstr == hush_version_str)
+                       continue;
+               if (cur->flg_read_only) {
+                       *pp++ = (char *) "-R";
+                       *pp++ = cur->varstr;
+               } else if (!cur->flg_export) {
+                       *pp++ = (char *) "-V";
+                       *pp++ = cur->varstr;
+               }
+       }
+//TODO: pass functions
+       /* We can pass activated traps here. Say, -Tnn:trap_string
+        *
+        * However, POSIX says that subshells reset signals with traps
+        * to SIG_DFL.
+        * I tested bash-3.2 and it not only does that with true subshells
+        * of the form ( list ), but with any forked children shells.
+        * I set trap "echo W" WINCH; and then tried:
+        *
+        * { echo 1; sleep 20; echo 2; } &
+        * while true; do echo 1; sleep 20; echo 2; break; done &
+        * true | { echo 1; sleep 20; echo 2; } | cat
+        *
+        * In all these cases sending SIGWINCH to the child shell
+        * did not run the trap. If I add trap "echo V" WINCH;
+        * _inside_ group (just before echo 1), it works.
+        *
+        * I conclude it means we don't need to pass active traps here.
+        * exec syscall below resets them to SIG_DFL for us.
+        */
+       *pp++ = (char *) (is_heredoc ? "-<" : "-c");
+       *pp++ = (char *) s;
+       pp2 = G.global_argv;
+       while (*pp2)
+               *pp++ = *pp2++;
+       /* *pp = NULL; - is already there */
+
+       debug_printf_exec("re_execute_shell pid:%d cmd:'%s'\n", getpid(), s);
+       sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
+       execv(bb_busybox_exec_path, G.argv_from_re_execing);
+       /* Fallback. Useful for init=/bin/hush usage etc */
+       if (G.argv0_for_re_execing[0] == '/')
+               execv(G.argv0_for_re_execing, G.argv_from_re_execing);
+       xfunc_error_retval = 127;
+       bb_error_msg_and_die("can't re-execute the shell");
+}
+
+static void clean_up_after_re_execute(void)
+{
+       char **pp = G.argv_from_re_execing;
+       if (pp) {
+               /* Must match re_execute_shell's allocations (if any) */
+               free(pp);
+               G.argv_from_re_execing = NULL;
+       }
+}
+#endif  /* !BB_MMU */
+
+
 static void setup_heredoc(int fd, const char *heredoc)
 {
        struct fd_pair pair;
        pid_t pid;
+       int len, written;
 
        xpiped_pair(pair);
+       xmove_fd(pair.rd, fd);
+
+       len = strlen(heredoc);
+       /* Try writing without forking. Newer kernels have
+        * dynamically growing pipes. Must use non-blocking write! */
+       ndelay_on(pair.wr);
+       while (1) {
+               written = write(pair.wr, heredoc, len);
+               if (written <= 0)
+                       break;
+               len -= written;
+               if (len == 0) {
+                       close(pair.wr);
+                       return;
+               }
+               heredoc += written;
+       }
+       ndelay_off(pair.wr);
+
+       /* Okay, pipe buffer was not big enough */
+       /* Note: we must not create a stray child (bastard? :)
+        * for the unsuspecting parent process. We create a grandchild
+        * and exit before we exec the process which consumes heredoc
+        * (that exec happens after we return from this function) */
        pid = vfork();
        if (pid < 0)
                bb_perror_msg_and_die("vfork");
-       if (pid == 0) { /* child */
-               die_sleep = 0;
-               close(pair.rd);
-               xwrite_str(pair.wr, heredoc);
+       if (pid == 0) {
+               /* child */
+               pid = BB_MMU ? fork() : vfork();
+               if (pid < 0)
+                       bb_perror_msg_and_die(BB_MMU ? "fork" : "vfork");
+               if (pid != 0)
+                       _exit(0);
+               /* grandchild */
+               close(fd); /* read side of the pipe */
+#if BB_MMU
+               full_write(pair.wr, heredoc, len);
                _exit(0);
+#else
+               /* Delegate blocking writes to another process */
+# if ENABLE_HUSH_JOB
+               die_sleep = 0; /* do not restore tty pgrp on xfunc death */
+# endif
+               xmove_fd(pair.wr, STDOUT_FILENO);
+               re_execute_shell(heredoc, 1);
+#endif
        }
        /* parent */
-       die_sleep = -1;
+#if ENABLE_HUSH_JOB
+       die_sleep = -1; /* restore tty pgrp on xfunc death */
+#endif
+       clean_up_after_re_execute();
        close(pair.wr);
-       xmove_fd(pair.rd, fd);
+       wait(NULL); /* wiat till child has died */
 }
 
 /* squirrel != NULL means we squirrel away copies of stdin, stdout,
@@ -2329,122 +2492,6 @@ static void pseudo_exec_argv(nommu_save_t *nommu_save,
        _exit(EXIT_FAILURE);
 }
 
-#if BB_MMU
-static void reset_traps_to_defaults(void)
-{
-       unsigned sig;
-       int dirty;
-
-       if (!G.traps)
-               return;
-       dirty = 0;
-       for (sig = 0; sig < NSIG; sig++) {
-               if (!G.traps[sig])
-                       continue;
-               free(G.traps[sig]);
-               G.traps[sig] = NULL;
-               /* There is no signal for 0 (EXIT) */
-               if (sig == 0)
-                       continue;
-               /* there was a trap handler, we are removing it
-                * (if sig has non-DFL handling,
-                * we don't need to do anything) */
-               if (sig < 32 && (G.non_DFL_mask & (1 << sig)))
-                       continue;
-               sigdelset(&G.blocked_set, sig);
-               dirty = 1;
-       }
-       if (dirty)
-               sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
-}
-#define clean_up_after_re_execute() ((void)0)
-
-#else /* !BB_MMU */
-
-static void re_execute_shell(const char *s) NORETURN;
-static void re_execute_shell(const char *s)
-{
-       char param_buf[sizeof("-$%x:%x:%x:%x") + sizeof(unsigned) * 4];
-       struct variable *cur;
-       char **argv, **pp, **pp2;
-       unsigned cnt;
-
-       sprintf(param_buf, "-$%x:%x:%x" USE_HUSH_LOOPS(":%x")
-                       , (unsigned) G.root_pid
-                       , (unsigned) G.last_bg_pid
-                       , (unsigned) G.last_exitcode
-                       USE_HUSH_LOOPS(, G.depth_of_loop)
-                       );
-       /* 1:hush 2:-$<pid>:<pid>:<exitcode>:<depth> <vars...>
-        * 3:-c 4:<cmd> <argN...> 5:NULL
-        */
-       cnt = 5 + G.global_argc;
-       for (cur = G.top_var; cur; cur = cur->next) {
-               if (!cur->flg_export || cur->flg_read_only)
-                       cnt += 2;
-       }
-       G.argv_from_re_execing = pp = xzalloc(sizeof(argv[0]) * cnt);
-       *pp++ = (char *) G.argv0_for_re_execing;
-       *pp++ = param_buf;
-       for (cur = G.top_var; cur; cur = cur->next) {
-               if (cur->varstr == hush_version_str)
-                       continue;
-               if (cur->flg_read_only) {
-                       *pp++ = (char *) "-R";
-                       *pp++ = cur->varstr;
-               } else if (!cur->flg_export) {
-                       *pp++ = (char *) "-V";
-                       *pp++ = cur->varstr;
-               }
-       }
-//TODO: pass functions
-       /* We can pass activated traps here. Say, -Tnn:trap_string
-        *
-        * However, POSIX says that subshells reset signals with traps
-        * to SIG_DFL.
-        * I tested bash-3.2 and it not only does that with true subshells
-        * of the form ( list ), but with any forked children shells.
-        * I set trap "echo W" WINCH; and then tried:
-        *
-        * { echo 1; sleep 20; echo 2; } &
-        * while true; do echo 1; sleep 20; echo 2; break; done &
-        * true | { echo 1; sleep 20; echo 2; } | cat
-        *
-        * In all these cases sending SIGWINCH to the child shell
-        * did not run the trap. If I add trap "echo V" WINCH;
-        * _inside_ group (just before echo 1), it works.
-        *
-        * I conclude it means we don't need to pass active traps here.
-        * exec syscall below resets them to SIG_DFL for us.
-        */
-       *pp++ = (char *) "-c";
-       *pp++ = (char *) s;
-       pp2 = G.global_argv;
-       while (*pp2)
-               *pp++ = *pp2++;
-       /* *pp = NULL; - is already there */
-
-       debug_printf_exec("re_execute_shell pid:%d cmd:'%s'\n", getpid(), s);
-       sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
-       execv(bb_busybox_exec_path, G.argv_from_re_execing);
-       /* Fallback. Useful for init=/bin/hush usage etc */
-       if (G.argv0_for_re_execing[0] == '/')
-               execv(G.argv0_for_re_execing, G.argv_from_re_execing);
-       xfunc_error_retval = 127;
-       bb_error_msg_and_die("can't re-execute the shell");
-}
-
-static void clean_up_after_re_execute(void)
-{
-       char **pp = G.argv_from_re_execing;
-       if (pp) {
-               /* Must match re_execute_shell's allocations (if any) */
-               free(pp);
-               G.argv_from_re_execing = NULL;
-       }
-}
-#endif
-
 static int run_list(struct pipe *pi);
 
 /* Called after [v]fork() in run_pipe
@@ -2477,7 +2524,7 @@ static void pseudo_exec(nommu_save_t *nommu_save,
                 * since this process is about to exit */
                _exit(rcode);
 #else
-               re_execute_shell(command->group_as_string);
+               re_execute_shell(command->group_as_string, 0);
 #endif
        }
 
@@ -4105,7 +4152,7 @@ static FILE *generate_stream_from_string(const char *s)
         * huge=`cat BIG` # was blocking here forever
         * echo OK
         */
-               re_execute_shell(s);
+               re_execute_shell(s, 0);
 #endif
        }
 
@@ -5317,7 +5364,7 @@ int hush_main(int argc, char **argv)
        while (1) {
                opt = getopt(argc, argv, "c:xins"
 #if !BB_MMU
-                               "$:!:?:D:R:V:"
+                               "<:$:!:?:D:R:V:"
 #endif
                );
                if (opt <= 0)
@@ -5346,6 +5393,9 @@ int hush_main(int argc, char **argv)
                         * operate, so simply do nothing here. */
                        break;
 #if !BB_MMU
+               case '<': /* "big heredoc" support */
+                       full_write(STDOUT_FILENO, optarg, strlen(optarg));
+                       _exit(0);
                case '$':
                        G.root_pid = bb_strtou(optarg, &optarg, 16);
                        optarg++;