diff options
Diffstat (limited to 'shell/hush.c')
-rw-r--r-- | shell/hush.c | 80 |
1 files changed, 65 insertions, 15 deletions
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 | } |