X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=libbb%2Fvfork_daemon_rexec.c;h=9be90172290ef67bfd611c64371befa24c2e56b8;hb=d7171c114028815df4e92f9c26875f1d60c6af2a;hp=7dbc152e2b1d28222fe3c2639e22f69e48941af1;hpb=831a20f51246cd8d54a246ba7e239a062eeb002c;p=oweals%2Fbusybox.git diff --git a/libbb/vfork_daemon_rexec.c b/libbb/vfork_daemon_rexec.c index 7dbc152e2..9be901722 100644 --- a/libbb/vfork_daemon_rexec.c +++ b/libbb/vfork_daemon_rexec.c @@ -15,18 +15,17 @@ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. */ -#include -#include "busybox.h" /* for struct bb_applet */ +#include "busybox.h" /* uses applet tables */ /* This does a fork/exec in one call, using vfork(). Returns PID of new child, * -1 for failure. Runs argv[0], searching path if that has no / in it. */ -pid_t spawn(char **argv) +pid_t FAST_FUNC spawn(char **argv) { /* Compiler should not optimize stores here */ volatile int failed; pid_t pid; -// Ain't it a good place to fflush(NULL)? + fflush_all(); /* Be nice to nommu machines. */ failed = 0; @@ -42,6 +41,8 @@ pid_t spawn(char **argv) * (but don't run atexit() stuff, which would screw up parent.) */ failed = errno; + /* mount, for example, does not want the message */ + /*bb_perror_msg("can't execute '%s'", argv[0]);*/ _exit(111); } /* parent */ @@ -58,16 +59,31 @@ pid_t spawn(char **argv) } /* Die with an error message if we can't spawn a child process. */ -pid_t xspawn(char **argv) +pid_t FAST_FUNC xspawn(char **argv) { pid_t pid = spawn(argv); if (pid < 0) - bb_perror_msg_and_die("%s", *argv); + bb_simple_perror_msg_and_die(*argv); return pid; } +pid_t FAST_FUNC safe_waitpid(pid_t pid, int *wstat, int options) +{ + pid_t r; + + do + r = waitpid(pid, wstat, options); + while ((r == -1) && (errno == EINTR)); + return r; +} + +pid_t FAST_FUNC wait_any_nohang(int *wstat) +{ + return safe_waitpid(-1, wstat, WNOHANG); +} + // Wait for the specified child PID to exit, returning child's error return. -int wait4pid(int pid) +int FAST_FUNC wait4pid(pid_t pid) { int status; @@ -76,7 +92,7 @@ int wait4pid(int pid) /* we expect errno to be already set from failed [v]fork/exec */ return -1; } - if (waitpid(pid, &status, 0) == -1) + if (safe_waitpid(pid, &status, 0) == -1) return -1; if (WIFEXITED(status)) return WEXITSTATUS(status); @@ -85,76 +101,130 @@ int wait4pid(int pid) return 0; } -int wait_nohang(int *wstat) +#if ENABLE_FEATURE_PREFER_APPLETS +void FAST_FUNC save_nofork_data(struct nofork_save_area *save) { - return waitpid(-1, wstat, WNOHANG); + memcpy(&save->die_jmp, &die_jmp, sizeof(die_jmp)); + save->applet_name = applet_name; + save->xfunc_error_retval = xfunc_error_retval; + save->option_mask32 = option_mask32; + save->die_sleep = die_sleep; + save->saved = 1; } -int wait_pid(int *wstat, int pid) +void FAST_FUNC restore_nofork_data(struct nofork_save_area *save) { - int r; + memcpy(&die_jmp, &save->die_jmp, sizeof(die_jmp)); + applet_name = save->applet_name; + xfunc_error_retval = save->xfunc_error_retval; + option_mask32 = save->option_mask32; + die_sleep = save->die_sleep; +} - do - r = waitpid(pid, wstat, 0); - while ((r == -1) && (errno == EINTR)); - return r; +int FAST_FUNC run_nofork_applet_prime(struct nofork_save_area *old, int applet_no, char **argv) +{ + int rc, argc; + + applet_name = APPLET_NAME(applet_no); + + xfunc_error_retval = EXIT_FAILURE; + + /* Special flag for xfunc_die(). If xfunc will "die" + * in NOFORK applet, xfunc_die() sees negative + * die_sleep and longjmp here instead. */ + die_sleep = -1; + + /* In case getopt() or getopt32() was already called: + * reset the libc getopt() function, which keeps internal state. + * + * BSD-derived getopt() functions require that optind be set to 1 in + * order to reset getopt() state. This used to be generally accepted + * way of resetting getopt(). However, glibc's getopt() + * has additional getopt() state beyond optind, and requires that + * optind be set to zero to reset its state. So the unfortunate state of + * affairs is that BSD-derived versions of getopt() misbehave if + * optind is set to 0 in order to reset getopt(), and glibc's getopt() + * will core dump if optind is set 1 in order to reset getopt(). + * + * More modern versions of BSD require that optreset be set to 1 in + * order to reset getopt(). Sigh. Standards, anyone? + */ +#ifdef __GLIBC__ + optind = 0; +#else /* BSD style */ + optind = 1; + /* optreset = 1; */ +#endif + /* optarg = NULL; opterr = 1; optopt = 63; - do we need this too? */ + /* (values above are what they initialized to in glibc and uclibc) */ + /* option_mask32 = 0; - not needed, no applet depends on it being 0 */ + + argc = 1; + while (argv[argc]) + argc++; + + rc = setjmp(die_jmp); + if (!rc) { + /* Some callers (xargs) + * need argv untouched because they free argv[i]! */ + char *tmp_argv[argc+1]; + memcpy(tmp_argv, argv, (argc+1) * sizeof(tmp_argv[0])); + /* Finally we can call NOFORK applet's main() */ + rc = applet_main[applet_no](argc, tmp_argv); + + /* The whole reason behind nofork_save_area is that _main + * may exit non-locally! For example, in hush Ctrl-Z tries + * (modulo bugs) to dynamically create a child (backgrounded task) + * if it detects that Ctrl-Z was pressed when a NOFORK was running. + * Testcase: interactive "rm -i". + * Don't fool yourself into thinking "and _main() returns + * quickly here" and removing "useless" nofork_save_area code. */ + + } else { /* xfunc died in NOFORK applet */ + /* in case they meant to return 0... */ + if (rc == -2222) + rc = 0; + } + + /* Restoring some globals */ + restore_nofork_data(old); + + /* Other globals can be simply reset to defaults */ +#ifdef __GLIBC__ + optind = 0; +#else /* BSD style */ + optind = 1; +#endif + + return rc & 0xff; /* don't confuse people with "exitcodes" >255 */ } -int spawn_and_wait(char **argv) +int FAST_FUNC run_nofork_applet(int applet_no, char **argv) +{ + struct nofork_save_area old; + + /* Saving globals */ + save_nofork_data(&old); + return run_nofork_applet_prime(&old, applet_no, argv); +} +#endif /* FEATURE_PREFER_APPLETS */ + +int FAST_FUNC spawn_and_wait(char **argv) { int rc; #if ENABLE_FEATURE_PREFER_APPLETS - const struct bb_applet *a = find_applet_by_name(argv[0]); + int a = find_applet_by_name(argv[0]); - if (a && (a->nofork + if (a >= 0 && (APPLET_IS_NOFORK(a) #if BB_MMU - || a->noexec /* NOEXEC trick needs fork() */ + || APPLET_IS_NOEXEC(a) /* NOEXEC trick needs fork() */ #endif )) { - int argc = 1; - char **pp = argv; - while (*++pp) - argc++; #if BB_MMU - if (a->nofork) + if (APPLET_IS_NOFORK(a)) #endif { - /* Save some shared globals */ - const struct bb_applet *old_a = current_applet; - int old_x = xfunc_error_retval; - uint32_t old_m = option_mask32; - int old_sleep = die_sleep; - - current_applet = a; - applet_name = a->name; - xfunc_error_retval = EXIT_FAILURE; - /*option_mask32 = 0; - not needed */ - /* special flag for xfunc_die(). If xfunc will "die" - * in NOFORK applet, xfunc_die() sees negative - * die_sleep and longjmp here instead. */ - die_sleep = -1; - - rc = setjmp(die_jmp); - if (!rc) { - /* Some callers (xargs) - * need argv untouched because they free argv[i]! */ - char *tmp_argv[argc+1]; - memcpy(tmp_argv, argv, (argc+1) * sizeof(tmp_argv[0])); - /* Finally we can call NOFORK applet's main() */ - rc = a->main(argc, tmp_argv); - } else { /* xfunc died in NOFORK applet */ - /* in case they meant to return 0... */ - if (rc == -111) - rc = 0; - } - - /* Restoring globals */ - current_applet = old_a; - applet_name = old_a->name; - xfunc_error_retval = old_x; - option_mask32 = old_m; - die_sleep = old_sleep; - return rc; + return run_nofork_applet(a, argv); } #if BB_MMU /* MMU only */ @@ -164,8 +234,7 @@ int spawn_and_wait(char **argv) return wait4pid(rc); /* child */ xfunc_error_retval = EXIT_FAILURE; - current_applet = a; - run_current_applet_and_exit(argc, argv); + run_applet_no_and_exit(a, argv); #endif } #endif /* FEATURE_PREFER_APPLETS */ @@ -173,51 +242,50 @@ int spawn_and_wait(char **argv) return wait4pid(rc); } - #if !BB_MMU -void forkexit_or_rexec(char **argv) +void FAST_FUNC re_exec(char **argv) +{ + /* high-order bit of first char in argv[0] is a hidden + * "we have (already) re-execed, don't do it again" flag */ + argv[0][0] |= 0x80; + execv(bb_busybox_exec_path, argv); + bb_perror_msg_and_die("exec %s", bb_busybox_exec_path); +} + +pid_t FAST_FUNC fork_or_rexec(char **argv) { pid_t pid; /* Maybe we are already re-execed and come here again? */ if (re_execed) - return; - + return 0; pid = vfork(); if (pid < 0) /* wtf? */ bb_perror_msg_and_die("vfork"); if (pid) /* parent */ - exit(0); + return pid; /* child - re-exec ourself */ - /* high-order bit of first char in argv[0] is a hidden - * "we have (alrealy) re-execed, don't do it again" flag */ - argv[0][0] |= 0x80; - execv(CONFIG_BUSYBOX_EXEC_PATH, argv); - bb_perror_msg_and_die("exec %s", CONFIG_BUSYBOX_EXEC_PATH); + re_exec(argv); } #else /* Dance around (void)...*/ -#undef forkexit_or_rexec -void forkexit_or_rexec(void) +#undef fork_or_rexec +pid_t FAST_FUNC fork_or_rexec(void) { pid_t pid; pid = fork(); if (pid < 0) /* wtf? */ bb_perror_msg_and_die("fork"); - if (pid) /* parent */ - exit(0); - /* child */ + return pid; } -#define forkexit_or_rexec(argv) forkexit_or_rexec() +#define fork_or_rexec(argv) fork_or_rexec() #endif /* Due to a #define in libbb.h on MMU systems we actually have 1 argument - * char **argv "vanishes" */ -void bb_daemonize_or_rexec(int flags, char **argv) +void FAST_FUNC bb_daemonize_or_rexec(int flags, char **argv) { int fd; - fd = xopen(bb_dev_null, O_RDWR); - if (flags & DAEMON_CHDIR_ROOT) xchdir("/"); @@ -227,25 +295,36 @@ void bb_daemonize_or_rexec(int flags, char **argv) close(2); } + fd = open(bb_dev_null, O_RDWR); + if (fd < 0) { + /* NB: we can be called as bb_sanitize_stdio() from init + * or mdev, and there /dev/null may legitimately not (yet) exist! + * Do not use xopen above, but obtain _ANY_ open descriptor, + * even bogus one as below. */ + fd = xopen("/", O_RDONLY); /* don't believe this can fail */ + } + while ((unsigned)fd < 2) fd = dup(fd); /* have 0,1,2 open at least to /dev/null */ if (!(flags & DAEMON_ONLY_SANITIZE)) { - forkexit_or_rexec(argv); - /* if daemonizing, make sure we detach from stdio */ + if (fork_or_rexec(argv)) + exit(EXIT_SUCCESS); /* parent */ + /* if daemonizing, make sure we detach from stdio & ctty */ setsid(); dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); } - if (fd > 2) + while (fd > 2) { close(fd--); - if (flags & DAEMON_CLOSE_EXTRA_FDS) - while (fd > 2) - close(fd--); /* close everything after fd#2 */ + if (!(flags & DAEMON_CLOSE_EXTRA_FDS)) + return; + /* else close everything after fd#2 */ + } } -void bb_sanitize_stdio(void) +void FAST_FUNC bb_sanitize_stdio(void) { bb_daemonize_or_rexec(DAEMON_ONLY_SANITIZE, NULL); }