From f9375285719035dbf2f7003d582c22447ed579f0 Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Sun, 5 Apr 2009 19:13:39 +0000 Subject: hush: audit and fix "interactive shell" setup code. function old new delta block_signals - 139 +139 maybe_set_to_sigexit - 47 +47 run_list 2018 2030 +12 expand_variables 2155 2165 +10 maybe_set_sighandler 47 - -47 hush_main 992 918 -74 ------------------------------------------------------------------------------ (add/remove: 2/1 grow/shrink: 2/1 up/down: 208/-121) Total: 87 bytes --- shell/hush.c | 323 ++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 175 insertions(+), 148 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index eba7a86df..a3f80d512 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -850,8 +850,6 @@ static void free_strings(char **strings) * Note: as a result, we do not use signal handlers much. The only uses * are to count SIGCHLDs [disabled - bug somewhere, + bloat] * and to restore tty pgrp on signal-induced exit. - * - * TODO: check/fix wait builtin to be interruptible. */ //static void SIGCHLD_handler(int sig UNUSED_PARAM) @@ -859,36 +857,6 @@ static void free_strings(char **strings) // G.count_SIGCHLD++; //} -/* called once at shell init */ -static void init_signal_mask(void) -{ - unsigned sig; - unsigned mask = (1 << SIGQUIT); - if (G_interactive_fd) { - mask = 0 - | (1 << SIGQUIT) - | (1 << SIGTERM) - | (1 << SIGHUP) -#if ENABLE_HUSH_JOB - | (1 << SIGTTIN) | (1 << SIGTTOU) | (1 << SIGTSTP) -#endif - | (1 << SIGINT) - ; - } - G.non_DFL_mask = mask; - - sigprocmask(SIG_SETMASK, NULL, &G.blocked_set); - sig = 0; - while (mask) { - if (mask & 1) - sigaddset(&G.blocked_set, sig); - mask >>= 1; - sig++; - } - sigdelset(&G.blocked_set, SIGCHLD); - sigprocmask(SIG_SETMASK, &G.blocked_set, &G.inherited_set); -} - static int check_and_run_traps(int sig) { static const struct timespec zero_timespec = { 0, 0 }; @@ -934,7 +902,6 @@ static int check_and_run_traps(int sig) } #if ENABLE_HUSH_JOB - /* Restores tty foreground process group, and exits. * May be called as signal handler for fatal signal * (will faithfully resend signal to itself, producing correct exit state) @@ -957,48 +924,6 @@ static void sigexit(int sig) kill_myself_with_sig(sig); /* does not return */ } - -/* helper */ -static void maybe_set_sighandler(int sig) -{ - void (*handler)(int); - /* non_DFL_mask'ed signals are, well, masked, - * no need to set handler for them. - */ - if (!((G.non_DFL_mask >> sig) & 1)) { - handler = signal(sig, sigexit); - if (handler == SIG_IGN) /* oops... restore back to IGN! */ - signal(sig, handler); - } -} -/* Used only to set handler to restore pgrp on exit */ -static void set_fatal_signals_to_sigexit(void) -{ - if (HUSH_DEBUG) { - maybe_set_sighandler(SIGILL ); - maybe_set_sighandler(SIGFPE ); - maybe_set_sighandler(SIGBUS ); - maybe_set_sighandler(SIGSEGV); - maybe_set_sighandler(SIGTRAP); - } /* else: hush is perfect. what SEGV? */ - - maybe_set_sighandler(SIGABRT); - - /* bash 3.2 seems to handle these just like 'fatal' ones */ - maybe_set_sighandler(SIGPIPE); - maybe_set_sighandler(SIGALRM); - maybe_set_sighandler(SIGHUP ); - - /* if we aren't interactive... but in this case - * we never want to restore pgrp on exit, and this fn is not called */ - /*maybe_set_sighandler(SIGTERM);*/ - /*maybe_set_sighandler(SIGINT );*/ -} - -#else /* !JOB */ - -#define set_fatal_signals_to_sigexit(handler) ((void)0) - #endif /* Restores tty foreground process group, and exits. */ @@ -1007,6 +932,7 @@ static void hush_exit(int exitcode) { if (G.traps && G.traps[0] && G.traps[0][0]) { char *argv[] = { NULL, xstrdup(G.traps[0]), NULL }; +//TODO: do we need to prevent recursion? builtin_eval(argv); free(argv[1]); } @@ -2896,7 +2822,7 @@ static int run_pipe(struct pipe *pi) command->pid = BB_MMU ? fork() : vfork(); if (!command->pid) { /* child */ #if ENABLE_HUSH_JOB - die_sleep = 0; /* let nofork's xfuncs die */ + die_sleep = 0; /* do not restore tty pgrp on xfunc death */ /* Every child adds itself to new process group * with pgid == pid_of_first_child_in_pipe */ @@ -2930,7 +2856,10 @@ static int run_pipe(struct pipe *pi) /* pseudo_exec() does not return */ } - /* parent */ + /* parent or error */ +#if ENABLE_HUSH_JOB + die_sleep = -1; /* restore tty pgrp on xfunc death */ +#endif #if !BB_MMU /* Clean up after vforked child */ clean_up_after_re_execute(); @@ -3900,6 +3829,9 @@ static FILE *generate_stream_from_string(const char *s) bb_perror_msg_and_die(BB_MMU ? "fork" : "vfork"); if (pid == 0) { /* child */ +#if ENABLE_HUSH_JOB + die_sleep = 0; /* do not restore tty pgrp on xfunc death */ +#endif /* Process substitution is not considered to be usual * 'command execution'. * SUSv3 says ctrl-Z should be ignored, ctrl-C should not. @@ -3909,8 +3841,6 @@ static FILE *generate_stream_from_string(const char *s) + (1 << SIGTTIN) + (1 << SIGTTOU) , SIG_IGN); - if (ENABLE_HUSH_JOB) - die_sleep = 0; /* let nofork's xfuncs die */ close(channel[0]); /* NB: close _first_, then move fd! */ xmove_fd(channel[1], 1); /* Prevent it from trying to handle ctrl-z etc */ @@ -3920,8 +3850,8 @@ static FILE *generate_stream_from_string(const char *s) _exit(G.last_return_code); #else /* We re-execute after vfork on NOMMU. This makes this script safe: - * yes "0123456789012345678901234567890" | dd bs=32 count=64k >TESTFILE - * huge=`cat TESTFILE` # was blocking here forever + * yes "0123456789012345678901234567890" | dd bs=32 count=64k >BIG + * huge=`cat BIG` # was blocking here forever * echo OK */ re_execute_shell(s); @@ -3929,6 +3859,9 @@ static FILE *generate_stream_from_string(const char *s) } /* parent */ +#if ENABLE_HUSH_JOB + die_sleep = -1; /* restore tty pgrp on xfunc death */ +#endif clean_up_after_re_execute(); close(channel[1]); pf = fdopen(channel[0], "r"); @@ -4945,31 +4878,81 @@ static void parse_and_run_file(FILE *f) parse_and_run_stream(&input, ';'); } -#if ENABLE_HUSH_JOB -/* Make sure we have a controlling tty. If we get started under a job - * aware app (like bash for example), make sure we are now in charge so - * we don't fight over who gets the foreground */ -static void setup_job_control(void) +/* Called a few times only (or even once if "sh -c") */ +static void block_signals(int second_time) { - pid_t shell_pgrp; + unsigned sig; + unsigned mask; - shell_pgrp = getpgrp(); + mask = (1 << SIGQUIT); + if (G_interactive_fd) { + mask = 0 + | (1 << SIGQUIT) + | (1 << SIGTERM) + | (1 << SIGHUP) +#if ENABLE_HUSH_JOB + | (1 << SIGTTIN) | (1 << SIGTTOU) | (1 << SIGTSTP) +#endif + | (1 << SIGINT) + ; + } + G.non_DFL_mask = mask; - /* If we were ran as 'hush &', - * sleep until we are in the foreground. */ - while (tcgetpgrp(G_interactive_fd) != shell_pgrp) { - /* Send TTIN to ourself (should stop us) */ - kill(- shell_pgrp, SIGTTIN); - shell_pgrp = getpgrp(); + if (!second_time) + sigprocmask(SIG_SETMASK, NULL, &G.blocked_set); + sig = 0; + while (mask) { + if (mask & 1) + sigaddset(&G.blocked_set, sig); + mask >>= 1; + sig++; } + sigdelset(&G.blocked_set, SIGCHLD); - /* We _must_ restore tty pgrp on fatal signals */ - set_fatal_signals_to_sigexit(); + sigprocmask(SIG_SETMASK, &G.blocked_set, + second_time ? NULL : &G.inherited_set); + /* POSIX allows shell to re-enable SIGCHLD + * even if it was SIG_IGN on entry */ +// G.count_SIGCHLD++; /* ensure it is != G.handled_SIGCHLD */ + if (!second_time) + signal(SIGCHLD, SIG_DFL); // SIGCHLD_handler); +} - /* Put ourselves in our own process group. */ - bb_setpgrp(); /* is the same as setpgid(our_pid, our_pid); */ - /* Grab control of the terminal. */ - tcsetpgrp(G_interactive_fd, getpid()); +#if ENABLE_HUSH_JOB +/* helper */ +static void maybe_set_to_sigexit(int sig) +{ + void (*handler)(int); + /* non_DFL_mask'ed signals are, well, masked, + * no need to set handler for them. + */ + if (!((G.non_DFL_mask >> sig) & 1)) { + handler = signal(sig, sigexit); + if (handler == SIG_IGN) /* oops... restore back to IGN! */ + signal(sig, handler); + } +} +/* Set handlers to restore tty pgrm and exit */ +static void set_fatal_handlers(void) +{ + /* We _must_ restore tty pgrp on fatal signals */ + if (HUSH_DEBUG) { + maybe_set_to_sigexit(SIGILL ); + maybe_set_to_sigexit(SIGFPE ); + maybe_set_to_sigexit(SIGBUS ); + maybe_set_to_sigexit(SIGSEGV); + maybe_set_to_sigexit(SIGTRAP); + } /* else: hush is perfect. what SEGV? */ + maybe_set_to_sigexit(SIGABRT); + /* bash 3.2 seems to handle these just like 'fatal' ones */ + maybe_set_to_sigexit(SIGPIPE); + maybe_set_to_sigexit(SIGALRM); + maybe_set_to_sigexit(SIGHUP ); + /* if we are interactive, SIGTERM and SIGINT are masked. + * if we aren't interactive... but in this case + * we never want to restore pgrp on exit, and this fn is not called */ + /*maybe_set_to_sigexit(SIGTERM);*/ + /*maybe_set_to_sigexit(SIGINT );*/ } #endif @@ -4994,7 +4977,7 @@ int hush_main(int argc, char **argv) .flg_export = 1, .flg_read_only = 1, }; - + int signal_mask_is_inited = 0; int opt; char **e; struct variable *cur_var; @@ -5060,6 +5043,7 @@ int hush_main(int argc, char **argv) optind--; } /* else -c 'script' PAR0 PAR1: $0 is PAR0 */ G.global_argc = argc - optind; + block_signals(0); /* 0: called 1st time */ parse_and_run_string(optarg); goto final_return; case 'i': @@ -5104,10 +5088,12 @@ int hush_main(int argc, char **argv) bb_show_usage(); #endif } - } + } /* option parsing loop */ if (!G.root_pid) G.root_pid = getpid(); + + /* If we are login shell... */ if (argv[0] && argv[0][0] == '-') { FILE *input; /* XXX what should argv be while sourcing /etc/profile? */ @@ -5115,26 +5101,57 @@ int hush_main(int argc, char **argv) input = fopen_for_read("/etc/profile"); if (input != NULL) { close_on_exec_on(fileno(input)); + block_signals(0); /* 0: called 1st time */ + signal_mask_is_inited = 1; parse_and_run_file(input); fclose(input); } + /* bash: after sourcing /etc/profile, + * tries to source (in the given order): + * ~/.bash_profile, ~/.bash_login, ~/.profile, + * stopping of first found. --noprofile turns this off. + * bash also sources ~/.bash_logout on exit. + * If called as sh, skips .bash_XXX files. + */ } -#if ENABLE_HUSH_JOB + if (argv[optind]) { + FILE *input; + /* + * Non-interactive "bash