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 | |
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>
-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 -- /' \ | ||