diff options
author | Ron Yorston <rmy@pobox.com> | 2017-09-27 10:08:12 +0100 |
---|---|---|
committer | Ron Yorston <rmy@pobox.com> | 2017-09-27 10:11:19 +0100 |
commit | d9383e984da8de72e61e5094a3cf6404c5707ddc (patch) | |
tree | dd42825854fc42aea40d4f7a95548d53721d1733 /shell | |
parent | 166b3e4e82799f87d3b002c7177891111eff079e (diff) | |
parent | 0c4dbd481aedb5d22c1048e7f7eb547a3b5e50a5 (diff) | |
download | busybox-w32-d9383e984da8de72e61e5094a3cf6404c5707ddc.tar.gz busybox-w32-d9383e984da8de72e61e5094a3cf6404c5707ddc.tar.bz2 busybox-w32-d9383e984da8de72e61e5094a3cf6404c5707ddc.zip |
Merge branch 'busybox' into merge
Diffstat (limited to 'shell')
-rw-r--r-- | shell/ash.c | 14 | ||||
-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/cttyhack.c | 12 | ||||
-rw-r--r-- | shell/hush.c | 80 | ||||
-rw-r--r-- | shell/hush_test/hush-getopts/getopt_nested.right | 14 | ||||
-rwxr-xr-x | shell/hush_test/hush-getopts/getopt_nested.tests | 21 | ||||
-rwxr-xr-x | shell/hush_test/hush-vars/unset.tests | 6 | ||||
-rw-r--r-- | shell/shell_common.c | 4 |
9 files changed, 156 insertions, 30 deletions
diff --git a/shell/ash.c b/shell/ash.c index 639f5396a..0d65a225b 100644 --- a/shell/ash.c +++ b/shell/ash.c | |||
@@ -2957,7 +2957,7 @@ cdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) | |||
2957 | goto docd; | 2957 | goto docd; |
2958 | 2958 | ||
2959 | err: | 2959 | err: |
2960 | ash_msg_and_raise_error("can't cd to %s", dest); | 2960 | ash_msg_and_raise_perror("can't cd to %s", dest); |
2961 | /* NOTREACHED */ | 2961 | /* NOTREACHED */ |
2962 | out: | 2962 | out: |
2963 | if (flags & CD_PRINT) | 2963 | if (flags & CD_PRINT) |
@@ -5313,7 +5313,7 @@ forkchild(struct job *jp, union node *n, int mode) | |||
5313 | if (jp->nprocs == 0) { | 5313 | if (jp->nprocs == 0) { |
5314 | close(0); | 5314 | close(0); |
5315 | if (open(bb_dev_null, O_RDONLY) != 0) | 5315 | if (open(bb_dev_null, O_RDONLY) != 0) |
5316 | ash_msg_and_raise_error("can't open '%s'", bb_dev_null); | 5316 | ash_msg_and_raise_perror("can't open '%s'", bb_dev_null); |
5317 | } | 5317 | } |
5318 | } | 5318 | } |
5319 | if (oldlvl == 0) { | 5319 | if (oldlvl == 0) { |
@@ -5414,7 +5414,7 @@ forkshell(struct job *jp, union node *n, int mode) | |||
5414 | TRACE(("Fork failed, errno=%d", errno)); | 5414 | TRACE(("Fork failed, errno=%d", errno)); |
5415 | if (jp) | 5415 | if (jp) |
5416 | freejob(jp); | 5416 | freejob(jp); |
5417 | ash_msg_and_raise_error("can't fork"); | 5417 | ash_msg_and_raise_perror("can't fork"); |
5418 | } | 5418 | } |
5419 | if (pid == 0) { | 5419 | if (pid == 0) { |
5420 | CLEAR_RANDOM_T(&random_gen); /* or else $RANDOM repeats in child */ | 5420 | CLEAR_RANDOM_T(&random_gen); /* or else $RANDOM repeats in child */ |
@@ -5559,7 +5559,7 @@ openhere(union node *redir) | |||
5559 | IF_PLATFORM_MINGW32(struct forkshell fs); | 5559 | IF_PLATFORM_MINGW32(struct forkshell fs); |
5560 | 5560 | ||
5561 | if (pipe(pip) < 0) | 5561 | if (pipe(pip) < 0) |
5562 | ash_msg_and_raise_error("pipe call failed"); | 5562 | ash_msg_and_raise_perror("can't create pipe"); |
5563 | if (redir->type == NHERE) { | 5563 | if (redir->type == NHERE) { |
5564 | len = strlen(redir->nhere.doc->narg.text); | 5564 | len = strlen(redir->nhere.doc->narg.text); |
5565 | if (len <= PIPE_BUF) { | 5565 | if (len <= PIPE_BUF) { |
@@ -6661,7 +6661,7 @@ evalbackcmd(union node *n, struct backcmd *result) | |||
6661 | } | 6661 | } |
6662 | 6662 | ||
6663 | if (pipe(pip) < 0) | 6663 | if (pipe(pip) < 0) |
6664 | ash_msg_and_raise_error("pipe call failed"); | 6664 | ash_msg_and_raise_perror("can't create pipe"); |
6665 | jp = makejob(/*n,*/ 1); | 6665 | jp = makejob(/*n,*/ 1); |
6666 | #if ENABLE_PLATFORM_MINGW32 | 6666 | #if ENABLE_PLATFORM_MINGW32 |
6667 | result->fs.fpid = FS_EVALBACKCMD; | 6667 | result->fs.fpid = FS_EVALBACKCMD; |
@@ -9625,7 +9625,7 @@ evalpipe(union node *n, int flags) | |||
9625 | if (lp->next) { | 9625 | if (lp->next) { |
9626 | if (pipe(pip) < 0) { | 9626 | if (pipe(pip) < 0) { |
9627 | close(prevfd); | 9627 | close(prevfd); |
9628 | ash_msg_and_raise_error("pipe call failed"); | 9628 | ash_msg_and_raise_perror("can't create pipe"); |
9629 | } | 9629 | } |
9630 | } | 9630 | } |
9631 | #if ENABLE_PLATFORM_MINGW32 | 9631 | #if ENABLE_PLATFORM_MINGW32 |
@@ -11058,7 +11058,7 @@ setinputfile(const char *fname, int flags) | |||
11058 | if (flags & INPUT_NOFILE_OK) | 11058 | if (flags & INPUT_NOFILE_OK) |
11059 | goto out; | 11059 | goto out; |
11060 | exitstatus = 127; | 11060 | exitstatus = 127; |
11061 | ash_msg_and_raise_error("can't open '%s'", fname); | 11061 | ash_msg_and_raise_perror("can't open '%s'", fname); |
11062 | } | 11062 | } |
11063 | if (fd < 10) | 11063 | if (fd < 10) |
11064 | fd = savefd(fd); | 11064 | fd = savefd(fd); |
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/cttyhack.c b/shell/cttyhack.c index 849fe9e48..ec1b6c429 100644 --- a/shell/cttyhack.c +++ b/shell/cttyhack.c | |||
@@ -4,12 +4,6 @@ | |||
4 | * | 4 | * |
5 | * Licensed under GPLv2, see file LICENSE in this source tree. | 5 | * Licensed under GPLv2, see file LICENSE in this source tree. |
6 | */ | 6 | */ |
7 | #include "libbb.h" | ||
8 | |||
9 | //applet:IF_CTTYHACK(APPLET_NOEXEC(cttyhack, cttyhack, BB_DIR_BIN, BB_SUID_DROP, cttyhack)) | ||
10 | |||
11 | //kbuild:lib-$(CONFIG_CTTYHACK) += cttyhack.o | ||
12 | |||
13 | //config:config CTTYHACK | 7 | //config:config CTTYHACK |
14 | //config: bool "cttyhack (2.5 kb)" | 8 | //config: bool "cttyhack (2.5 kb)" |
15 | //config: default y | 9 | //config: default y |
@@ -54,6 +48,10 @@ | |||
54 | //config: | 48 | //config: |
55 | //config: # getty 115200 $(cttyhack) | 49 | //config: # getty 115200 $(cttyhack) |
56 | 50 | ||
51 | //applet:IF_CTTYHACK(APPLET_NOEXEC(cttyhack, cttyhack, BB_DIR_BIN, BB_SUID_DROP, cttyhack)) | ||
52 | |||
53 | //kbuild:lib-$(CONFIG_CTTYHACK) += cttyhack.o | ||
54 | |||
57 | //usage:#define cttyhack_trivial_usage | 55 | //usage:#define cttyhack_trivial_usage |
58 | //usage: "[PROG ARGS]" | 56 | //usage: "[PROG ARGS]" |
59 | //usage:#define cttyhack_full_usage "\n\n" | 57 | //usage:#define cttyhack_full_usage "\n\n" |
@@ -65,6 +63,8 @@ | |||
65 | //usage: "\nStarting interactive shell from boot shell script:" | 63 | //usage: "\nStarting interactive shell from boot shell script:" |
66 | //usage: "\n setsid cttyhack sh" | 64 | //usage: "\n setsid cttyhack sh" |
67 | 65 | ||
66 | #include "libbb.h" | ||
67 | |||
68 | #if !defined(__linux__) && !defined(TIOCGSERIAL) && !ENABLE_WERROR | 68 | #if !defined(__linux__) && !defined(TIOCGSERIAL) && !ENABLE_WERROR |
69 | # warning cttyhack will not be able to detect a controlling tty on this system | 69 | # warning cttyhack will not be able to detect a controlling tty on this system |
70 | #endif | 70 | #endif |
diff --git a/shell/hush.c b/shell/hush.c index cdc3a8618..d27550ba0 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,11 @@ 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 | /* defoptindvar is a "OPTIND=..." constant string */ | ||
2222 | if (strncmp(cur->varstr, defoptindvar, 7) == 0) | ||
2223 | G.getopt_count = 0; | ||
2224 | #endif | ||
2217 | if (cur->flg_export) { | 2225 | if (cur->flg_export) { |
2218 | if (flags & SETFLAG_UNEXPORT) { | 2226 | if (flags & SETFLAG_UNEXPORT) { |
2219 | cur->flg_export = 0; | 2227 | cur->flg_export = 0; |
@@ -2244,6 +2252,10 @@ static int unset_local_var_len(const char *name, int name_len) | |||
2244 | 2252 | ||
2245 | if (!name) | 2253 | if (!name) |
2246 | return EXIT_SUCCESS; | 2254 | return EXIT_SUCCESS; |
2255 | #if ENABLE_HUSH_GETOPTS | ||
2256 | if (name_len == 6 && strncmp(name, "OPTIND", 6) == 0) | ||
2257 | G.getopt_count = 0; | ||
2258 | #endif | ||
2247 | var_pp = &G.top_var; | 2259 | var_pp = &G.top_var; |
2248 | while ((cur = *var_pp) != NULL) { | 2260 | while ((cur = *var_pp) != NULL) { |
2249 | if (strncmp(cur->varstr, name, name_len) == 0 && cur->varstr[name_len] == '=') { | 2261 | if (strncmp(cur->varstr, name, name_len) == 0 && cur->varstr[name_len] == '=') { |
@@ -9889,7 +9901,8 @@ Test that VAR is a valid variable name? | |||
9889 | */ | 9901 | */ |
9890 | char cbuf[2]; | 9902 | char cbuf[2]; |
9891 | const char *cp, *optstring, *var; | 9903 | const char *cp, *optstring, *var; |
9892 | int c, exitcode; | 9904 | int c, n, exitcode, my_opterr; |
9905 | unsigned count; | ||
9893 | 9906 | ||
9894 | optstring = *++argv; | 9907 | optstring = *++argv; |
9895 | if (!optstring || !(var = *++argv)) { | 9908 | if (!optstring || !(var = *++argv)) { |
@@ -9897,17 +9910,18 @@ Test that VAR is a valid variable name? | |||
9897 | return EXIT_FAILURE; | 9910 | return EXIT_FAILURE; |
9898 | } | 9911 | } |
9899 | 9912 | ||
9900 | c = 0; | 9913 | if (argv[1]) |
9914 | argv[0] = G.global_argv[0]; /* for error messages in getopt() */ | ||
9915 | else | ||
9916 | argv = G.global_argv; | ||
9917 | cbuf[1] = '\0'; | ||
9918 | |||
9919 | my_opterr = 0; | ||
9901 | if (optstring[0] != ':') { | 9920 | if (optstring[0] != ':') { |
9902 | cp = get_local_var_value("OPTERR"); | 9921 | cp = get_local_var_value("OPTERR"); |
9903 | /* 0 if "OPTERR=0", 1 otherwise */ | 9922 | /* 0 if "OPTERR=0", 1 otherwise */ |
9904 | c = (!cp || NOT_LONE_CHAR(cp, '0')); | 9923 | my_opterr = (!cp || NOT_LONE_CHAR(cp, '0')); |
9905 | } | 9924 | } |
9906 | opterr = c; | ||
9907 | cp = get_local_var_value("OPTIND"); | ||
9908 | optind = cp ? atoi(cp) : 0; | ||
9909 | optarg = NULL; | ||
9910 | cbuf[1] = '\0'; | ||
9911 | 9925 | ||
9912 | /* getopts stops on first non-option. Add "+" to force that */ | 9926 | /* getopts stops on first non-option. Add "+" to force that */ |
9913 | /*if (optstring[0] != '+')*/ { | 9927 | /*if (optstring[0] != '+')*/ { |
@@ -9916,11 +9930,48 @@ Test that VAR is a valid variable name? | |||
9916 | optstring = s; | 9930 | optstring = s; |
9917 | } | 9931 | } |
9918 | 9932 | ||
9919 | if (argv[1]) | 9933 | /* Naively, now we should just |
9920 | argv[0] = G.global_argv[0]; /* for error messages */ | 9934 | * cp = get_local_var_value("OPTIND"); |
9921 | else | 9935 | * optind = cp ? atoi(cp) : 0; |
9922 | argv = G.global_argv; | 9936 | * optarg = NULL; |
9923 | c = getopt(string_array_len(argv), argv, optstring); | 9937 | * opterr = my_opterr; |
9938 | * c = getopt(string_array_len(argv), argv, optstring); | ||
9939 | * and be done? Not so fast... | ||
9940 | * Unlike normal getopt() usage in C programs, here | ||
9941 | * each successive call will (usually) have the same argv[] CONTENTS, | ||
9942 | * but not the ADDRESSES. Worse yet, it's possible that between | ||
9943 | * invocations of "getopts", there will be calls to shell builtins | ||
9944 | * which use getopt() internally. Example: | ||
9945 | * while getopts "abc" RES -a -bc -abc de; do | ||
9946 | * unset -ff func | ||
9947 | * done | ||
9948 | * This would not work correctly: getopt() call inside "unset" | ||
9949 | * modifies internal libc state which is tracking position in | ||
9950 | * multi-option strings ("-abc"). At best, it can skip options | ||
9951 | * or return the same option infinitely. With glibc implementation | ||
9952 | * of getopt(), it would use outright invalid pointers and return | ||
9953 | * garbage even _without_ "unset" mangling internal state. | ||
9954 | * | ||
9955 | * We resort to resetting getopt() state and calling it N times, | ||
9956 | * until we get Nth result (or failure). | ||
9957 | * (N == G.getopt_count is reset to 0 whenever OPTIND is [un]set). | ||
9958 | */ | ||
9959 | GETOPT_RESET(); | ||
9960 | count = 0; | ||
9961 | n = string_array_len(argv); | ||
9962 | do { | ||
9963 | optarg = NULL; | ||
9964 | opterr = (count < G.getopt_count) ? 0 : my_opterr; | ||
9965 | c = getopt(n, argv, optstring); | ||
9966 | if (c < 0) | ||
9967 | break; | ||
9968 | count++; | ||
9969 | } while (count <= G.getopt_count); | ||
9970 | |||
9971 | /* Set OPTIND. Prevent resetting of the magic counter! */ | ||
9972 | set_local_var_from_halves("OPTIND", utoa(optind)); | ||
9973 | G.getopt_count = count; /* "next time, give me N+1'th result" */ | ||
9974 | GETOPT_RESET(); /* just in case */ | ||
9924 | 9975 | ||
9925 | /* Set OPTARG */ | 9976 | /* Set OPTARG */ |
9926 | /* Always set or unset, never left as-is, even on exit/error: | 9977 | /* Always set or unset, never left as-is, even on exit/error: |
@@ -9949,10 +10000,9 @@ Test that VAR is a valid variable name? | |||
9949 | c = '?'; | 10000 | c = '?'; |
9950 | } | 10001 | } |
9951 | 10002 | ||
9952 | /* Set VAR and OPTIND */ | 10003 | /* Set VAR */ |
9953 | cbuf[0] = c; | 10004 | cbuf[0] = c; |
9954 | set_local_var_from_halves(var, cbuf); | 10005 | set_local_var_from_halves(var, cbuf); |
9955 | set_local_var_from_halves("OPTIND", utoa(optind)); | ||
9956 | 10006 | ||
9957 | return exitcode; | 10007 | return exitcode; |
9958 | } | 10008 | } |
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 -- /' \ | ||
diff --git a/shell/hush_test/hush-vars/unset.tests b/shell/hush_test/hush-vars/unset.tests index 81243fbf9..268323a6d 100755 --- a/shell/hush_test/hush-vars/unset.tests +++ b/shell/hush_test/hush-vars/unset.tests | |||
@@ -1,3 +1,5 @@ | |||
1 | ( | ||
2 | |||
1 | # check invalid options are rejected | 3 | # check invalid options are rejected |
2 | # bash: in posix mode, aborts if non-interactive | 4 | # bash: in posix mode, aborts if non-interactive |
3 | unset - | 5 | unset - |
@@ -37,3 +39,7 @@ unset VAR_RO | |||
37 | echo $? $f $g | 39 | echo $? $f $g |
38 | unset f VAR_RO g | 40 | unset f VAR_RO g |
39 | echo $? $f $g | 41 | echo $? $f $g |
42 | |||
43 | ) 2>&1 \ | ||
44 | | sed -e 's/ unrecognized option: / invalid option -- /' \ | ||
45 | -e 's/ illegal option -- / invalid option -- /' \ | ||
diff --git a/shell/shell_common.c b/shell/shell_common.c index bc34de404..ad9048d89 100644 --- a/shell/shell_common.c +++ b/shell/shell_common.c | |||
@@ -432,8 +432,8 @@ shell_builtin_ulimit(char **argv) | |||
432 | * ulimit 123 -c2 -l 456 | 432 | * ulimit 123 -c2 -l 456 |
433 | */ | 433 | */ |
434 | 434 | ||
435 | /* In case getopt was already called: | 435 | /* In case getopt() was already called: |
436 | * reset the libc getopt() function, which keeps internal state. | 436 | * reset libc getopt() internal state. |
437 | */ | 437 | */ |
438 | GETOPT_RESET(); | 438 | GETOPT_RESET(); |
439 | 439 | ||