diff --git a/include/libbb.h b/include/libbb.h index 72e891302..2dfe38760 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -868,22 +868,6 @@ int file_is_executable(const char *name) FAST_FUNC; char *find_executable(const char *filename, char **PATHp) FAST_FUNC; int executable_exists(const char *filename) FAST_FUNC; -/* BB_EXECxx always execs (it's not doing NOFORK/NOEXEC stuff), - * but it may exec busybox and call applet instead of searching PATH. - */ -#if ENABLE_FEATURE_PREFER_APPLETS -int BB_EXECVP(const char *file, char *const argv[]) FAST_FUNC; -#define BB_EXECLP(prog,cmd,...) \ - do { \ - if (find_applet_by_name(prog) >= 0) \ - execlp(bb_busybox_exec_path, cmd, __VA_ARGS__); \ - execlp(prog, cmd, __VA_ARGS__); \ - } while (0) -#else -#define BB_EXECVP(prog,cmd) execvp(prog,cmd) -#endif -int BB_EXECVP_or_die(char **argv) NORETURN FAST_FUNC; - /* xvfork() can't be a _function_, return after vfork mangles stack * in the parent. It must be a macro. */ #define xvfork() \ @@ -1716,6 +1700,12 @@ static ALWAYS_INLINE unsigned char bb_ascii_tolower(unsigned char a) #endif /* !defined(__ORCAC__) */ +/* 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); + + /* Simple unit-testing framework */ typedef void (*bbunit_testfunc)(void); diff --git a/libbb/vfork.and.run.c b/libbb/vfork.and.run.c index 0e19903b6..979d2563c 100644 --- a/libbb/vfork.and.run.c +++ b/libbb/vfork.and.run.c @@ -1,25 +1,6 @@ #include #include "libbb.h" -/* Like vfork, but calls fn(arg) in the child instead of returning. - * This is designed to match the semantics of GNO's fork2 call. - */ -#ifndef __GNO__ -pid_t vfork_and_run(void (*fn)(void*) NORETURN, void *arg) { - pid_t pid = vfork(); - - if (pid == 0) { - fn(arg); - } - - return pid; -} -#else -pid_t vfork_and_run(void (*fn)(void*) NORETURN, void *arg) { - return fork2((fn), 1024, 0, "hush (forked)", 2, (arg)); -} -#endif - pid_t xvfork_and_run(void (*fn)(void*) NORETURN, void *arg) { pid_t pid = vfork_and_run(fn, arg); @@ -28,3 +9,62 @@ pid_t xvfork_and_run(void (*fn)(void*) NORETURN, void *arg) { return pid; } + +/* Like vfork, but calls fn(arg) in the child instead of returning. + * This is designed to match the semantics of GNO's fork2 call. + */ +#ifndef __GNO__ + +pid_t vfork_and_run(void (*fn)(void*) NORETURN, void *arg) { + pid_t pid = vfork(); + + if (pid == 0) { + fn(arg); + } + + return pid; +} + +#else + +/* Turn off all ORCA/C stack repair code to avoid corruption. */ +#ifdef __ORCAC__ +# pragma optimize 72 +#endif + +pid_t vfork_and_run(void (*fn)(void*) NORETURN, void *arg) { + /* GNO's fork2 call will return immediately and allow the parent and + * child processes to execute concurrently using the same memory + * space. To prevent them stomping on each other, we want to get + * 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 + * 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. + */ + + long oldmask; + pid_t pid; + + /* Block all signals for now */ + oldmask = sigblock(-1); + + pid = fork2((fn), 1024, 0, "hush (forked)", 2, (arg)); + + /* Now wait until we get SIGUSR2 */ + sigpause(~sigmask(SIGUSR2)); + + /* Restore original signal mask */ + sigsetmask(oldmask); + + return pid; +} + +#endif diff --git a/libbb/xfunc.die.c b/libbb/xfunc.die.c index 204e5e49d..9be791074 100644 --- a/libbb/xfunc.die.c +++ b/libbb/xfunc.die.c @@ -19,6 +19,8 @@ jmp_buf die_jmp; void FAST_FUNC xfunc_die(void) { + int forked; + if (die_sleep) { if ((ENABLE_FEATURE_PREFER_APPLETS || ENABLE_HUSH) && die_sleep < 0 @@ -36,5 +38,9 @@ void FAST_FUNC xfunc_die(void) } sleep(die_sleep); } - exit(xfunc_error_retval); + forked = signal_parent_to_resume(); + if (!forked) + exit(xfunc_error_retval); + else + _exit(xfunc_error_retval); } diff --git a/shell/hush.c b/shell/hush.c index 202414328..5c2dc6176 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -745,6 +745,14 @@ struct globals { pid_t root_pid; pid_t root_ppid; pid_t last_bg_pid; +#ifdef __GNO__ + /* Pid of the hush instance that was exec'd closest to us in the + * process tree. We're running in this pid's memory space, even + * if we've forked. Used to determine if parent should be signaled + * to resume when we terminate or exec. + */ + pid_t last_execed_pid; +#endif #if ENABLE_HUSH_RANDOM_SUPPORT random_t random_gen; #endif @@ -842,7 +850,9 @@ static struct globals G; G.sa.sa_flags = SA_RESTART; \ } while (0) #else -# define INIT_G() +# define INIT_G() do { \ + G.last_execed_pid = getpid(); \ +} while (0) #endif /* Used by lineedit. */ @@ -1331,6 +1341,19 @@ 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) { +#ifdef __GNO__ + if (getpid() != G.last_execed_pid) { + kill(getppid(), SIGUSR2); + return 1; + } +#endif + return 0; +} + /* Basic theory of signal handling in shell * ======================================== * This does not describe what hush does, rather, it is current understanding @@ -1564,8 +1587,10 @@ static void sigexit(int sig) } /* Not a signal, just exit */ - if (sig <= 0) + if (sig <= 0) { + signal_parent_to_resume(); _exit(- sig); + } kill_myself_with_sig(sig); /* does not return */ } @@ -5848,12 +5873,13 @@ static void re_execute_shell(char ***to_free, const char *s, /* Don't propagate SIG_IGN to the child */ if (SPECIAL_JOBSTOP_SIGS != 0) switch_off_special_sigs(G.special_sig_mask & SPECIAL_JOBSTOP_SIGS); + signal_parent_to_resume(); execve(bb_busybox_exec_path, argv, pp); /* Fallback. Useful for init=/bin/hush usage etc */ if (argv[0][0] == '/') execve(argv[0], argv, pp); - xfunc_error_retval = 127; - bb_error_msg_and_die("can't re-execute the shell"); + bb_perror_msg("can't re-execute the shell"); + _exit(127); /* bash compat */ } #endif /* !BB_MMU */ @@ -6040,6 +6066,7 @@ static void xforked_child(void *args_struct) { ) { static const char *const argv[] = { NULL, NULL }; builtin_trap((char**)argv); + signal_parent_to_resume(); exit(0); /* not _exit() - we need to fflush */ } # if BB_MMU @@ -6198,8 +6225,10 @@ static void xvforked_child(void *grandchild_args) { #else pid = xvfork_and_run(xforked_grandchild, grandchild_args); #endif - if (pid != 0) + if (pid != 0) { + signal_parent_to_resume(); _exit(0); + } xforked_grandchild(grandchild_args); // Only get here in BB_MMU case } @@ -6574,6 +6603,7 @@ static void execvp_or_die(char **argv) /* Don't propagate SIG_IGN to the child */ if (SPECIAL_JOBSTOP_SIGS != 0) switch_off_special_sigs(G.special_sig_mask & SPECIAL_JOBSTOP_SIGS); + signal_parent_to_resume(); execvp(argv[0], argv); bb_perror_msg("can't execute '%s'", argv[0]); _exit(127); /* bash compat */ @@ -6638,6 +6668,7 @@ 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); } @@ -6761,6 +6792,7 @@ 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); } @@ -7499,8 +7531,10 @@ static void forked_child(void *args_struct) { close(args->pipefds_p->rd); /* Like bash, explicit redirects override pipes, * and the pipe fd is available for dup'ing. */ - if (setup_redirects(*args->command_p, NULL)) + if (setup_redirects(*args->command_p, NULL)) { + signal_parent_to_resume(); _exit(1); + } /* Stores to nommu_save list of env vars putenv'ed * (NOMMU, on MMU we don't need that) */ @@ -8417,6 +8451,15 @@ int hush_main(int argc, char **argv) /* We have interactiveness code disabled */ 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)