From 50f3aa454785b6acc9ef19b94eff003a83bd0b01 Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Tue, 7 Apr 2009 10:52:40 +0000 Subject: [PATCH] hush: NOMMU-safe support of big heredocs function old new delta setup_heredoc 116 215 +99 --- shell/hush.c | 304 ++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 177 insertions(+), 127 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index dc59d73..f7e5fbc 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -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:-$::: + * 3:-c 4: 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:-$::: - * 3:-c 4: 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++; -- 2.7.4