From d1660cb9ad3adb4b99c098de88f79cbeb74c3a5d Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Mon, 20 Oct 2008 07:52:33 +0000 Subject: ash: fix a bug in standalone mode (corrupted getopt() state) --- libbb/getopt32.c | 22 ------------- libbb/vfork_daemon_rexec.c | 36 ++++++++++++++++++++-- .../ash-standalone/nofork_trashes_getopt.right | 1 + .../ash-standalone/nofork_trashes_getopt.tests | 6 ++++ util-linux/getopt.c | 11 ++----- 5 files changed, 43 insertions(+), 33 deletions(-) create mode 100644 shell/ash_test/ash-standalone/nofork_trashes_getopt.right create mode 100755 shell/ash_test/ash-standalone/nofork_trashes_getopt.tests diff --git a/libbb/getopt32.c b/libbb/getopt32.c index 43fb6eb86..49fb5335d 100644 --- a/libbb/getopt32.c +++ b/libbb/getopt32.c @@ -515,28 +515,6 @@ getopt32(char **argv, const char *applet_opts, ...) } } - /* In case 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 = 0; optopt = 0; - do we need this?? */ pargv = NULL; /* Note: just "getopt() <= 0" will not work well for diff --git a/libbb/vfork_daemon_rexec.c b/libbb/vfork_daemon_rexec.c index da0dc03e5..17b373cd1 100644 --- a/libbb/vfork_daemon_rexec.c +++ b/libbb/vfork_daemon_rexec.c @@ -125,6 +125,7 @@ int FAST_FUNC run_nofork_applet_prime(struct nofork_save_area *old, int applet_n int rc, argc; applet_name = APPLET_NAME(applet_no); + xfunc_error_retval = EXIT_FAILURE; /* Special flag for xfunc_die(). If xfunc will "die" @@ -132,7 +133,30 @@ int FAST_FUNC run_nofork_applet_prime(struct nofork_save_area *old, int applet_n * die_sleep and longjmp here instead. */ die_sleep = -1; - /* option_mask32 = 0; - not needed */ + /* 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]) @@ -161,8 +185,16 @@ int FAST_FUNC run_nofork_applet_prime(struct nofork_save_area *old, int applet_n rc = 0; } - /* Restoring globals */ + /* 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 */ } diff --git a/shell/ash_test/ash-standalone/nofork_trashes_getopt.right b/shell/ash_test/ash-standalone/nofork_trashes_getopt.right new file mode 100644 index 000000000..573541ac9 --- /dev/null +++ b/shell/ash_test/ash-standalone/nofork_trashes_getopt.right @@ -0,0 +1 @@ +0 diff --git a/shell/ash_test/ash-standalone/nofork_trashes_getopt.tests b/shell/ash_test/ash-standalone/nofork_trashes_getopt.tests new file mode 100755 index 000000000..f42c50730 --- /dev/null +++ b/shell/ash_test/ash-standalone/nofork_trashes_getopt.tests @@ -0,0 +1,6 @@ +# In this test, rm is NOFORK and it modifies getopt internal state +rm -f non_existent_file +# Subsequent hexdump is run as NOEXEC, and thus still uses this state +hexdump