diff --git a/include/libbb.h b/include/libbb.h index c54357a48..0893226a8 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -1636,7 +1636,8 @@ static ALWAYS_INLINE unsigned char bb_ascii_tolower(unsigned char a) /* Signal to the parent that it can resume executing after a fork, * because the child is about to exec or terminate. */ -int signal_parent_to_resume(void); +void signal_parent_to_resume(void); +int is_forked_child(void); /* Get path of current executable */ char *get_exec_path(void); diff --git a/libbb/signals.c b/libbb/signals.c index 91193f7e0..f7d557fcc 100644 --- a/libbb/signals.c +++ b/libbb/signals.c @@ -49,6 +49,7 @@ void FAST_FUNC kill_myself_with_sig(int sig) { signal(sig, SIG_DFL); sig_unblock(sig); + signal_parent_to_resume(); raise(sig); _exit(sig | 128); /* Should not reach it */ } diff --git a/libbb/vfork.and.run.c b/libbb/vfork.and.run.c index 979d2563c..5084f3508 100644 --- a/libbb/vfork.and.run.c +++ b/libbb/vfork.and.run.c @@ -27,10 +27,23 @@ pid_t vfork_and_run(void (*fn)(void*) NORETURN, void *arg) { #else +# include +# include +# include + /* Turn off all ORCA/C stack repair code to avoid corruption. */ -#ifdef __ORCAC__ -# pragma optimize 72 -#endif +# ifdef __ORCAC__ +# pragma optimize 72 +# endif + +# pragma databank 1 +void fork_thunk(void (*fn)(void*) NORETURN, void *arg, long sigmask) { + sigsetmask(sigmask); + fn(arg); +} +# pragma databank 0 + +const char * forked_child_name = "hush (forked)"; pid_t vfork_and_run(void (*fn)(void*) NORETURN, void *arg) { /* GNO's fork2 call will return immediately and allow the parent and @@ -39,31 +52,60 @@ pid_t vfork_and_run(void (*fn)(void*) NORETURN, void *arg) { * behavior like a traditional vfork() implementation, where the * parent blocks until the child terminates or execs. * - * Our approach will be to have the child send SIGUSR2 to the parent + * Our approach will be to have the child send SIGALRM to the parent * just before it terminates or execs, and block waiting for that here. - * - * It's tempting to use waitpid(). That would have the advantage of - * catching cases where the child process terminates abruptly, but - * GNO's implementation of waitpid() is a wrapper around wait() and - * therefore is buggy: it may swallow the information about termination - * of other child processes (ones that forked earlier and have already - * exec'd), which we want to avoid. + * We also set an alarm in case the child doesn't signal. + * + * When we get SIGALRM, we check the process tables to make sure the + * child has actually finished or exec'd. If not, we loop and try again. + * We can't just rely on the fact that the child signaled us, because + * it may still be running in libc's implementation of exec*. */ long oldmask; + sig_t prev_alarm_sig; pid_t pid; + kvmt *kvm_context; + struct pentry *proc_entry; + bool done = 0; /* Block all signals for now */ oldmask = sigblock(-1); - pid = fork2((fn), 1024, 0, "hush (forked)", 2, (arg)); + pid = fork2(fork_thunk, 1024, 0, forked_child_name, + (sizeof(fn) + sizeof(arg) + sizeof(oldmask) + 1) / 2, + fn, arg, oldmask); + if (pid < 0) + goto ret; - /* Now wait until we get SIGUSR2 */ - sigpause(~sigmask(SIGUSR2)); + prev_alarm_sig = signal(SIGALRM, SIG_IGN); - /* Restore original signal mask */ + while (!done) { + /* Set alarm. This is a backup in case the child dies without signaling us. */ + alarm10(1); + + /* Wait until we get SIGALRM */ + sigpause(~sigmask(SIGALRM)); + + /* Check if the child is really dead or forked by inspecting + * the kernel's process entry for it. */ + kvm_context = kvm_open(); + if (kvm_context == NULL) + break; + proc_entry = kvmgetproc(kvm_context, pid); + if (proc_entry == NULL + || (proc_entry->args != NULL + && strcmp(forked_child_name, proc_entry->args + 8) != 0)) + done = 1; + kvm_close(kvm_context); + } + + alarm10(0); + sigsetmask(~sigmask(SIGALRM)); + signal(SIGALRM, prev_alarm_sig); + +ret: sigsetmask(oldmask); - return pid; } diff --git a/libbb/xfunc.die.c b/libbb/xfunc.die.c index 9be791074..83540c260 100644 --- a/libbb/xfunc.die.c +++ b/libbb/xfunc.die.c @@ -38,8 +38,7 @@ void FAST_FUNC xfunc_die(void) } sleep(die_sleep); } - forked = signal_parent_to_resume(); - if (!forked) + if (!is_forked_child()) exit(xfunc_error_retval); else _exit(xfunc_error_retval); diff --git a/shell/hush.c b/shell/hush.c index c86adb7d6..a1df08ea7 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -1343,19 +1343,26 @@ static void restore_G_args(save_arg_t *sv, char **argv) } -/* Signal to the parent that it can resume executing after a fork, - * because the child is about to exec or terminate. - */ -int signal_parent_to_resume(void) { +int is_forked_child(void) { #ifdef __GNO__ if (getpid() != G.last_execed_pid) { - kill(getppid(), SIGUSR2); return 1; } #endif return 0; } +/* Signal to the parent that it can resume executing after a fork, + * because the child is about to exec or terminate. + */ +void signal_parent_to_resume(void) { +#ifdef __GNO__ + if (getpid() != G.last_execed_pid) { + kill(getppid(), SIGALRM); + } +#endif +} + #ifdef __GNO__ #undef _exit @@ -1374,8 +1381,12 @@ void _exit_wrapper(int status) { // Call regular _exit() _exit(status); } else { - // We're a forked child. Just call QuitGS. We do it in - // assembly so we can push the return value on the stack. + // We're a forked child. + fflush(stdout); + fflush(stderr); + signal_parent_to_resume(); + + // Call QuitGS in assembly so we can push the return value on the stack. while (1) { asm { lda status @@ -1627,7 +1638,6 @@ static void sigexit(int sig) /* Not a signal, just exit */ if (sig <= 0) { - signal_parent_to_resume(); _exit(- sig); } @@ -6105,8 +6115,11 @@ static void xforked_child(void *args_struct) { ) { static const char *const argv[] = { NULL, NULL }; builtin_trap((char**)argv); - signal_parent_to_resume(); +# ifdef __GNO__ + _exit(0); +# else exit(0); /* not _exit() - we need to fflush */ +# endif } # if BB_MMU reset_traps_to_defaults(); @@ -6264,7 +6277,6 @@ static void xvforked_child(void *grandchild_args) { pid = xvfork_and_run(xforked_grandchild, grandchild_args); #endif if (pid != 0) { - signal_parent_to_resume(); _exit(0); } xforked_grandchild(grandchild_args); // Only get here in BB_MMU case @@ -6706,7 +6718,6 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, * expand_assignments(): think about ... | var=`sleep 1` | ... */ free_strings(new_env); - signal_parent_to_resume(); _exit(EXIT_SUCCESS); } @@ -6830,7 +6841,6 @@ static void pseudo_exec(nommu_save_t *nommu_save, /* Case when we are here: ... | >file */ debug_printf_exec(("pseudo_exec'ed null command\n")); - signal_parent_to_resume(); _exit(EXIT_SUCCESS); } @@ -7570,7 +7580,6 @@ static void forked_child(void *args_struct) { /* Like bash, explicit redirects override pipes, * and the pipe fd is available for dup'ing. */ if (setup_redirects(*args->command_p, NULL)) { - signal_parent_to_resume(); _exit(1); } @@ -8494,14 +8503,6 @@ int hush_main(int argc, char **argv) install_special_sighandlers(); #endif -#ifdef __GNO__ - /* Don't terminate on SIGUSR2, because we'll be using it to signal when - * it's safe to resume after a fork. We don't need to actually do any - * other handling for it, so just set it to SIG_IGN. - */ - install_sighandler(SIGUSR2, SIG_IGN); -#endif - /* bash: * if interactive but not a login shell, sources ~/.bashrc * (--norc turns this off, --rcfile overrides)