diff options
| author | Denys Vlasenko <vda.linux@googlemail.com> | 2017-08-29 13:38:30 +0200 |
|---|---|---|
| committer | Denys Vlasenko <vda.linux@googlemail.com> | 2017-08-29 13:38:30 +0200 |
| commit | 238ff98bb85adfb5563d10b37ea4c33fef3af2f2 (patch) | |
| tree | a556435d7011ee928419d26a9d7d796745f97f62 /shell | |
| parent | 0d1eaf407c3d077f1d6ec97ceffbafbe7591ecbf (diff) | |
| download | busybox-w32-238ff98bb85adfb5563d10b37ea4c33fef3af2f2.tar.gz busybox-w32-238ff98bb85adfb5563d10b37ea4c33fef3af2f2.tar.bz2 busybox-w32-238ff98bb85adfb5563d10b37ea4c33fef3af2f2.zip | |
hush: fix "getopts" builtin to not be upset by other builtins calling getopt()
function old new delta
builtin_getopts 363 403 +40
unset_local_var_len 185 215 +30
set_local_var 440 466 +26
reset_traps_to_defaults 151 157 +6
pseudo_exec_argv 320 326 +6
install_special_sighandlers 52 58 +6
pick_sighandler 62 65 +3
execvp_or_die 85 88 +3
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 8/0 up/down: 120/0) Total: 120 bytes
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
Diffstat (limited to 'shell')
| -rw-r--r-- | shell/ash_test/ash-getopts/getopt_nested.right | 14 | ||||
| -rwxr-xr-x | shell/ash_test/ash-getopts/getopt_nested.tests | 21 | ||||
| -rw-r--r-- | shell/hush.c | 78 | ||||
| -rw-r--r-- | shell/hush_test/hush-getopts/getopt_nested.right | 14 | ||||
| -rwxr-xr-x | shell/hush_test/hush-getopts/getopt_nested.tests | 21 |
5 files changed, 133 insertions, 15 deletions
diff --git a/shell/ash_test/ash-getopts/getopt_nested.right b/shell/ash_test/ash-getopts/getopt_nested.right new file mode 100644 index 000000000..b0c339db1 --- /dev/null +++ b/shell/ash_test/ash-getopts/getopt_nested.right | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | var:a | ||
| 2 | var:b | ||
| 3 | var:c | ||
| 4 | var:a | ||
| 5 | var:b | ||
| 6 | var:c | ||
| 7 | Illegal option -d | ||
| 8 | var:? | ||
| 9 | Illegal option -e | ||
| 10 | var:? | ||
| 11 | Illegal option -f | ||
| 12 | var:? | ||
| 13 | var:a | ||
| 14 | End: var:? OPTIND:6 | ||
diff --git a/shell/ash_test/ash-getopts/getopt_nested.tests b/shell/ash_test/ash-getopts/getopt_nested.tests new file mode 100755 index 000000000..1b48b4075 --- /dev/null +++ b/shell/ash_test/ash-getopts/getopt_nested.tests | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | # Test that there is no interference of getopt() | ||
| 2 | # in getopts and unset. | ||
| 3 | # It's unclear what "correct" OPTIND values should be | ||
| 4 | # for "b" and "c" results from "-bc": 2? 3? | ||
| 5 | # What we focus on here is that all options are reported | ||
| 6 | # correct number of times and in correct sequence. | ||
| 7 | |||
| 8 | ( | ||
| 9 | |||
| 10 | loop=99 | ||
| 11 | while getopts "abc" var -a -bc -abc -def -a; do | ||
| 12 | echo "var:$var" #OPTIND:$OPTIND | ||
| 13 | # this may use getopt(): | ||
| 14 | unset -ff func | ||
| 15 | test $((--loop)) = 0 && break # BUG if this triggers | ||
| 16 | done | ||
| 17 | echo "End: var:$var OPTIND:$OPTIND" | ||
| 18 | |||
| 19 | ) 2>&1 \ | ||
| 20 | | sed -e 's/ unrecognized option: / invalid option -- /' \ | ||
| 21 | -e 's/ illegal option -- / invalid option -- /' \ | ||
diff --git a/shell/hush.c b/shell/hush.c index cdc3a8618..ceb8cbb0a 100644 --- a/shell/hush.c +++ b/shell/hush.c | |||
| @@ -907,6 +907,9 @@ struct globals { | |||
| 907 | unsigned depth_break_continue; | 907 | unsigned depth_break_continue; |
| 908 | unsigned depth_of_loop; | 908 | unsigned depth_of_loop; |
| 909 | #endif | 909 | #endif |
| 910 | #if ENABLE_HUSH_GETOPTS | ||
| 911 | unsigned getopt_count; | ||
| 912 | #endif | ||
| 910 | const char *ifs; | 913 | const char *ifs; |
| 911 | const char *cwd; | 914 | const char *cwd; |
| 912 | struct variable *top_var; | 915 | struct variable *top_var; |
| @@ -2214,6 +2217,10 @@ static int set_local_var(char *str, unsigned flags) | |||
| 2214 | cur->flg_export = 1; | 2217 | cur->flg_export = 1; |
| 2215 | if (name_len == 4 && cur->varstr[0] == 'P' && cur->varstr[1] == 'S') | 2218 | if (name_len == 4 && cur->varstr[0] == 'P' && cur->varstr[1] == 'S') |
| 2216 | cmdedit_update_prompt(); | 2219 | cmdedit_update_prompt(); |
| 2220 | #if ENABLE_HUSH_GETOPTS | ||
| 2221 | if (strncmp(cur->varstr, "OPTIND=", 7) == 0) | ||
| 2222 | G.getopt_count = 0; | ||
| 2223 | #endif | ||
| 2217 | if (cur->flg_export) { | 2224 | if (cur->flg_export) { |
| 2218 | if (flags & SETFLAG_UNEXPORT) { | 2225 | if (flags & SETFLAG_UNEXPORT) { |
| 2219 | cur->flg_export = 0; | 2226 | cur->flg_export = 0; |
| @@ -2244,6 +2251,10 @@ static int unset_local_var_len(const char *name, int name_len) | |||
| 2244 | 2251 | ||
| 2245 | if (!name) | 2252 | if (!name) |
| 2246 | return EXIT_SUCCESS; | 2253 | return EXIT_SUCCESS; |
| 2254 | #if ENABLE_HUSH_GETOPTS | ||
| 2255 | if (name_len == 6 && strncmp(name, "OPTIND", 6) == 0) | ||
| 2256 | G.getopt_count = 0; | ||
| 2257 | #endif | ||
| 2247 | var_pp = &G.top_var; | 2258 | var_pp = &G.top_var; |
| 2248 | while ((cur = *var_pp) != NULL) { | 2259 | while ((cur = *var_pp) != NULL) { |
| 2249 | if (strncmp(cur->varstr, name, name_len) == 0 && cur->varstr[name_len] == '=') { | 2260 | if (strncmp(cur->varstr, name, name_len) == 0 && cur->varstr[name_len] == '=') { |
| @@ -9889,7 +9900,8 @@ Test that VAR is a valid variable name? | |||
| 9889 | */ | 9900 | */ |
| 9890 | char cbuf[2]; | 9901 | char cbuf[2]; |
| 9891 | const char *cp, *optstring, *var; | 9902 | const char *cp, *optstring, *var; |
| 9892 | int c, exitcode; | 9903 | int c, n, exitcode, my_opterr; |
| 9904 | unsigned count; | ||
| 9893 | 9905 | ||
| 9894 | optstring = *++argv; | 9906 | optstring = *++argv; |
| 9895 | if (!optstring || !(var = *++argv)) { | 9907 | if (!optstring || !(var = *++argv)) { |
| @@ -9897,17 +9909,18 @@ Test that VAR is a valid variable name? | |||
| 9897 | return EXIT_FAILURE; | 9909 | return EXIT_FAILURE; |
| 9898 | } | 9910 | } |
| 9899 | 9911 | ||
| 9900 | c = 0; | 9912 | if (argv[1]) |
| 9913 | argv[0] = G.global_argv[0]; /* for error messages in getopt() */ | ||
| 9914 | else | ||
| 9915 | argv = G.global_argv; | ||
| 9916 | cbuf[1] = '\0'; | ||
| 9917 | |||
| 9918 | my_opterr = 0; | ||
| 9901 | if (optstring[0] != ':') { | 9919 | if (optstring[0] != ':') { |
| 9902 | cp = get_local_var_value("OPTERR"); | 9920 | cp = get_local_var_value("OPTERR"); |
| 9903 | /* 0 if "OPTERR=0", 1 otherwise */ | 9921 | /* 0 if "OPTERR=0", 1 otherwise */ |
| 9904 | c = (!cp || NOT_LONE_CHAR(cp, '0')); | 9922 | my_opterr = (!cp || NOT_LONE_CHAR(cp, '0')); |
| 9905 | } | 9923 | } |
| 9906 | opterr = c; | ||
| 9907 | cp = get_local_var_value("OPTIND"); | ||
| 9908 | optind = cp ? atoi(cp) : 0; | ||
| 9909 | optarg = NULL; | ||
| 9910 | cbuf[1] = '\0'; | ||
| 9911 | 9924 | ||
| 9912 | /* getopts stops on first non-option. Add "+" to force that */ | 9925 | /* getopts stops on first non-option. Add "+" to force that */ |
| 9913 | /*if (optstring[0] != '+')*/ { | 9926 | /*if (optstring[0] != '+')*/ { |
| @@ -9916,11 +9929,47 @@ Test that VAR is a valid variable name? | |||
| 9916 | optstring = s; | 9929 | optstring = s; |
| 9917 | } | 9930 | } |
| 9918 | 9931 | ||
| 9919 | if (argv[1]) | 9932 | /* Naively, now we should just |
| 9920 | argv[0] = G.global_argv[0]; /* for error messages */ | 9933 | * cp = get_local_var_value("OPTIND"); |
| 9921 | else | 9934 | * optind = cp ? atoi(cp) : 0; |
| 9922 | argv = G.global_argv; | 9935 | * optarg = NULL; |
| 9923 | c = getopt(string_array_len(argv), argv, optstring); | 9936 | * opterr = my_opterr; |
| 9937 | * c = getopt(string_array_len(argv), argv, optstring); | ||
| 9938 | * and be done? Not so fast... | ||
| 9939 | * Unlike normal getopt() usage in C programs, here | ||
| 9940 | * each successive call will (usually) have the same argv[] CONTENTS, | ||
| 9941 | * but not the ADDRESSES. Worse yet, it's possible that between | ||
| 9942 | * invocations of "getopts", there will be calls to shell builtins | ||
| 9943 | * which use getopt() internally. Example: | ||
| 9944 | * while getopts "abc" RES -a -bc -abc de; do | ||
| 9945 | * unset -ff func | ||
| 9946 | * done | ||
| 9947 | * This would not work correctly: getopt() call inside "unset" | ||
| 9948 | * modifies internal libc state which is tracking position in | ||
| 9949 | * multi-option strings ("-abc"). At best, it can skip options | ||
| 9950 | * or return the same option infinitely. With glibc implementation | ||
| 9951 | * of getopt(), it would use outright invalid pointers and return | ||
| 9952 | * garbage even _without_ "unset" mangling internal state. | ||
| 9953 | * | ||
| 9954 | * We resort to resetting getopt() state and calling it N times, | ||
| 9955 | * until we get Nth result (or failure). | ||
| 9956 | * (N == G.getopt_count is reset to 0 whenever OPTIND is [un]set). | ||
| 9957 | */ | ||
| 9958 | optind = 0; /* reset getopt() state */ | ||
| 9959 | count = 0; | ||
| 9960 | n = string_array_len(argv); | ||
| 9961 | do { | ||
| 9962 | optarg = NULL; | ||
| 9963 | opterr = (count < G.getopt_count) ? 0 : my_opterr; | ||
| 9964 | c = getopt(n, argv, optstring); | ||
| 9965 | if (c < 0) | ||
| 9966 | break; | ||
| 9967 | count++; | ||
| 9968 | } while (count <= G.getopt_count); | ||
| 9969 | |||
| 9970 | /* Set OPTIND. Prevent resetting of the magic counter! */ | ||
| 9971 | set_local_var_from_halves("OPTIND", utoa(optind)); | ||
| 9972 | G.getopt_count = count; /* "next time, give me N+1'th result" */ | ||
| 9924 | 9973 | ||
| 9925 | /* Set OPTARG */ | 9974 | /* Set OPTARG */ |
| 9926 | /* Always set or unset, never left as-is, even on exit/error: | 9975 | /* Always set or unset, never left as-is, even on exit/error: |
| @@ -9949,10 +9998,9 @@ Test that VAR is a valid variable name? | |||
| 9949 | c = '?'; | 9998 | c = '?'; |
| 9950 | } | 9999 | } |
| 9951 | 10000 | ||
| 9952 | /* Set VAR and OPTIND */ | 10001 | /* Set VAR */ |
| 9953 | cbuf[0] = c; | 10002 | cbuf[0] = c; |
| 9954 | set_local_var_from_halves(var, cbuf); | 10003 | set_local_var_from_halves(var, cbuf); |
| 9955 | set_local_var_from_halves("OPTIND", utoa(optind)); | ||
| 9956 | 10004 | ||
| 9957 | return exitcode; | 10005 | return exitcode; |
| 9958 | } | 10006 | } |
diff --git a/shell/hush_test/hush-getopts/getopt_nested.right b/shell/hush_test/hush-getopts/getopt_nested.right new file mode 100644 index 000000000..0218dba56 --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_nested.right | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | var:a | ||
| 2 | var:b | ||
| 3 | var:c | ||
| 4 | var:a | ||
| 5 | var:b | ||
| 6 | var:c | ||
| 7 | ./getopt_nested.tests: invalid option -- d | ||
| 8 | var:? | ||
| 9 | ./getopt_nested.tests: invalid option -- e | ||
| 10 | var:? | ||
| 11 | ./getopt_nested.tests: invalid option -- f | ||
| 12 | var:? | ||
| 13 | var:a | ||
| 14 | End: var:? OPTIND:6 | ||
diff --git a/shell/hush_test/hush-getopts/getopt_nested.tests b/shell/hush_test/hush-getopts/getopt_nested.tests new file mode 100755 index 000000000..1b48b4075 --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_nested.tests | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | # Test that there is no interference of getopt() | ||
| 2 | # in getopts and unset. | ||
| 3 | # It's unclear what "correct" OPTIND values should be | ||
| 4 | # for "b" and "c" results from "-bc": 2? 3? | ||
| 5 | # What we focus on here is that all options are reported | ||
| 6 | # correct number of times and in correct sequence. | ||
| 7 | |||
| 8 | ( | ||
| 9 | |||
| 10 | loop=99 | ||
| 11 | while getopts "abc" var -a -bc -abc -def -a; do | ||
| 12 | echo "var:$var" #OPTIND:$OPTIND | ||
| 13 | # this may use getopt(): | ||
| 14 | unset -ff func | ||
| 15 | test $((--loop)) = 0 && break # BUG if this triggers | ||
| 16 | done | ||
| 17 | echo "End: var:$var OPTIND:$OPTIND" | ||
| 18 | |||
| 19 | ) 2>&1 \ | ||
| 20 | | sed -e 's/ unrecognized option: / invalid option -- /' \ | ||
| 21 | -e 's/ illegal option -- / invalid option -- /' \ | ||
