aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDenys Vlasenko <vda.linux@googlemail.com>2017-08-29 13:38:30 +0200
committerDenys Vlasenko <vda.linux@googlemail.com>2017-08-29 13:38:30 +0200
commit238ff98bb85adfb5563d10b37ea4c33fef3af2f2 (patch)
treea556435d7011ee928419d26a9d7d796745f97f62
parent0d1eaf407c3d077f1d6ec97ceffbafbe7591ecbf (diff)
downloadbusybox-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.right14
-rwxr-xr-xshell/ash_test/ash-getopts/getopt_nested.tests21
-rw-r--r--shell/hush.c78
-rw-r--r--shell/hush_test/hush-getopts/getopt_nested.right14
-rwxr-xr-xshell/hush_test/hush-getopts/getopt_nested.tests21
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 @@
1var:a
2var:b
3var:c
4var:a
5var:b
6var:c
7Illegal option -d
8var:?
9Illegal option -e
10var:?
11Illegal option -f
12var:?
13var:a
14End: 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
10loop=99
11while 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
16done
17echo "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 @@
1var:a
2var:b
3var:c
4var:a
5var:b
6var:c
7./getopt_nested.tests: invalid option -- d
8var:?
9./getopt_nested.tests: invalid option -- e
10var:?
11./getopt_nested.tests: invalid option -- f
12var:?
13var:a
14End: 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
10loop=99
11while 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
16done
17echo "End: var:$var OPTIND:$OPTIND"
18
19) 2>&1 \
20| sed -e 's/ unrecognized option: / invalid option -- /' \
21 -e 's/ illegal option -- / invalid option -- /' \