diff options
| author | Ron Yorston <rmy@pobox.com> | 2017-08-22 14:56:12 +0100 |
|---|---|---|
| committer | Ron Yorston <rmy@pobox.com> | 2017-08-22 14:56:12 +0100 |
| commit | ce9af1cc5ea23f754587448cf35b5120c77bfeef (patch) | |
| tree | 69e5eaba5e75ab909ed92d5045393471b8ff3c13 /shell | |
| parent | c170026700eabb10147dd848c45c06995b43a32e (diff) | |
| parent | e837a0dbbebf4229306df98fe9ee3b9bb30630c4 (diff) | |
| download | busybox-w32-ce9af1cc5ea23f754587448cf35b5120c77bfeef.tar.gz busybox-w32-ce9af1cc5ea23f754587448cf35b5120c77bfeef.tar.bz2 busybox-w32-ce9af1cc5ea23f754587448cf35b5120c77bfeef.zip | |
Merge branch 'busybox' into merge
Diffstat (limited to 'shell')
34 files changed, 1422 insertions, 82 deletions
diff --git a/shell/ash.c b/shell/ash.c index 6a572cbc3..ac1ae05fc 100644 --- a/shell/ash.c +++ b/shell/ash.c | |||
| @@ -215,6 +215,9 @@ | |||
| 215 | #define BASH_PIPEFAIL ENABLE_ASH_BASH_COMPAT | 215 | #define BASH_PIPEFAIL ENABLE_ASH_BASH_COMPAT |
| 216 | #define BASH_HOSTNAME_VAR ENABLE_ASH_BASH_COMPAT | 216 | #define BASH_HOSTNAME_VAR ENABLE_ASH_BASH_COMPAT |
| 217 | #define BASH_SHLVL_VAR ENABLE_ASH_BASH_COMPAT | 217 | #define BASH_SHLVL_VAR ENABLE_ASH_BASH_COMPAT |
| 218 | #define BASH_XTRACEFD ENABLE_ASH_BASH_COMPAT | ||
| 219 | #define BASH_READ_D ENABLE_ASH_BASH_COMPAT | ||
| 220 | #define IF_BASH_READ_D IF_ASH_BASH_COMPAT | ||
| 218 | 221 | ||
| 219 | #if defined(__ANDROID_API__) && __ANDROID_API__ <= 24 | 222 | #if defined(__ANDROID_API__) && __ANDROID_API__ <= 24 |
| 220 | /* Bionic at least up to version 24 has no glob() */ | 223 | /* Bionic at least up to version 24 has no glob() */ |
| @@ -452,7 +455,7 @@ struct globals_misc { | |||
| 452 | #define S_DFL 1 /* default signal handling (SIG_DFL) */ | 455 | #define S_DFL 1 /* default signal handling (SIG_DFL) */ |
| 453 | #define S_CATCH 2 /* signal is caught */ | 456 | #define S_CATCH 2 /* signal is caught */ |
| 454 | #define S_IGN 3 /* signal is ignored (SIG_IGN) */ | 457 | #define S_IGN 3 /* signal is ignored (SIG_IGN) */ |
| 455 | #define S_HARD_IGN 4 /* signal is ignored permanently */ | 458 | #define S_HARD_IGN 4 /* signal is ignored permanently (it was SIG_IGN on entry to shell) */ |
| 456 | 459 | ||
| 457 | /* indicates specified signal received */ | 460 | /* indicates specified signal received */ |
| 458 | uint8_t gotsig[NSIG - 1]; /* offset by 1: "signal" 0 is meaningless */ | 461 | uint8_t gotsig[NSIG - 1]; /* offset by 1: "signal" 0 is meaningless */ |
| @@ -1313,6 +1316,10 @@ struct strpush { | |||
| 1313 | int unget; | 1316 | int unget; |
| 1314 | }; | 1317 | }; |
| 1315 | 1318 | ||
| 1319 | /* | ||
| 1320 | * The parsefile structure pointed to by the global variable parsefile | ||
| 1321 | * contains information about the current file being read. | ||
| 1322 | */ | ||
| 1316 | struct parsefile { | 1323 | struct parsefile { |
| 1317 | struct parsefile *prev; /* preceding file on stack */ | 1324 | struct parsefile *prev; /* preceding file on stack */ |
| 1318 | int linno; /* current line */ | 1325 | int linno; /* current line */ |
| @@ -1986,10 +1993,6 @@ nextopt(const char *optstring) | |||
| 1986 | 1993 | ||
| 1987 | /* ============ Shell variables */ | 1994 | /* ============ Shell variables */ |
| 1988 | 1995 | ||
| 1989 | /* | ||
| 1990 | * The parsefile structure pointed to by the global variable parsefile | ||
| 1991 | * contains information about the current file being read. | ||
| 1992 | */ | ||
| 1993 | struct shparam { | 1996 | struct shparam { |
| 1994 | int nparam; /* # of positional parameters (without $0) */ | 1997 | int nparam; /* # of positional parameters (without $0) */ |
| 1995 | #if ENABLE_ASH_GETOPTS | 1998 | #if ENABLE_ASH_GETOPTS |
| @@ -2183,7 +2186,9 @@ extern struct globals_var *const ash_ptr_to_globals_var; | |||
| 2183 | static void FAST_FUNC | 2186 | static void FAST_FUNC |
| 2184 | getoptsreset(const char *value) | 2187 | getoptsreset(const char *value) |
| 2185 | { | 2188 | { |
| 2186 | shellparam.optind = number(value) ?: 1; | 2189 | shellparam.optind = 1; |
| 2190 | if (is_number(value)) | ||
| 2191 | shellparam.optind = number(value) ?: 1; | ||
| 2187 | shellparam.optoff = -1; | 2192 | shellparam.optoff = -1; |
| 2188 | } | 2193 | } |
| 2189 | #endif | 2194 | #endif |
| @@ -3751,9 +3756,12 @@ setsignal(int signo) | |||
| 3751 | #endif | 3756 | #endif |
| 3752 | } | 3757 | } |
| 3753 | } | 3758 | } |
| 3754 | //TODO: if !rootshell, we reset SIGQUIT to DFL, | 3759 | /* if !rootshell, we reset SIGQUIT to DFL, |
| 3755 | //whereas we have to restore it to what shell got on entry | 3760 | * whereas we have to restore it to what shell got on entry. |
| 3756 | //from the parent. See comment above | 3761 | * This is handled by the fact that if signal was IGNored on entry, |
| 3762 | * then cur_act is S_HARD_IGN and we never change its sigaction | ||
| 3763 | * (see code below). | ||
| 3764 | */ | ||
| 3757 | 3765 | ||
| 3758 | if (signo == SIGCHLD) | 3766 | if (signo == SIGCHLD) |
| 3759 | new_act = S_CATCH; | 3767 | new_act = S_CATCH; |
| @@ -3777,10 +3785,18 @@ setsignal(int signo) | |||
| 3777 | cur_act = S_IGN; /* don't hard ignore these */ | 3785 | cur_act = S_IGN; /* don't hard ignore these */ |
| 3778 | } | 3786 | } |
| 3779 | } | 3787 | } |
| 3788 | if (act.sa_handler == SIG_DFL && new_act == S_DFL) { | ||
| 3789 | /* installing SIG_DFL over SIG_DFL is a no-op */ | ||
| 3790 | /* saves one sigaction call in each "sh -c SCRIPT" invocation */ | ||
| 3791 | *t = S_DFL; | ||
| 3792 | return; | ||
| 3793 | } | ||
| 3780 | } | 3794 | } |
| 3781 | if (cur_act == S_HARD_IGN || cur_act == new_act) | 3795 | if (cur_act == S_HARD_IGN || cur_act == new_act) |
| 3782 | return; | 3796 | return; |
| 3783 | 3797 | ||
| 3798 | *t = new_act; | ||
| 3799 | |||
| 3784 | act.sa_handler = SIG_DFL; | 3800 | act.sa_handler = SIG_DFL; |
| 3785 | switch (new_act) { | 3801 | switch (new_act) { |
| 3786 | case S_CATCH: | 3802 | case S_CATCH: |
| @@ -3790,16 +3806,13 @@ setsignal(int signo) | |||
| 3790 | act.sa_handler = SIG_IGN; | 3806 | act.sa_handler = SIG_IGN; |
| 3791 | break; | 3807 | break; |
| 3792 | } | 3808 | } |
| 3793 | |||
| 3794 | /* flags and mask matter only if !DFL and !IGN, but we do it | 3809 | /* flags and mask matter only if !DFL and !IGN, but we do it |
| 3795 | * for all cases for more deterministic behavior: | 3810 | * for all cases for more deterministic behavior: |
| 3796 | */ | 3811 | */ |
| 3797 | act.sa_flags = 0; | 3812 | act.sa_flags = 0; //TODO: why not SA_RESTART? |
| 3798 | sigfillset(&act.sa_mask); | 3813 | sigfillset(&act.sa_mask); |
| 3799 | 3814 | ||
| 3800 | sigaction_set(signo, &act); | 3815 | sigaction_set(signo, &act); |
| 3801 | |||
| 3802 | *t = new_act; | ||
| 3803 | } | 3816 | } |
| 3804 | #else | 3817 | #else |
| 3805 | #define setsignal(s) | 3818 | #define setsignal(s) |
| @@ -5915,12 +5928,10 @@ static void | |||
| 5915 | redirect(union node *redir, int flags) | 5928 | redirect(union node *redir, int flags) |
| 5916 | { | 5929 | { |
| 5917 | struct redirtab *sv; | 5930 | struct redirtab *sv; |
| 5918 | int sv_pos; | ||
| 5919 | 5931 | ||
| 5920 | if (!redir) | 5932 | if (!redir) |
| 5921 | return; | 5933 | return; |
| 5922 | 5934 | ||
| 5923 | sv_pos = 0; | ||
| 5924 | sv = NULL; | 5935 | sv = NULL; |
| 5925 | INT_OFF; | 5936 | INT_OFF; |
| 5926 | if (flags & REDIR_PUSH) | 5937 | if (flags & REDIR_PUSH) |
| @@ -8076,8 +8087,9 @@ static int | |||
| 8076 | patmatch(char *pattern, const char *string) | 8087 | patmatch(char *pattern, const char *string) |
| 8077 | { | 8088 | { |
| 8078 | char *p = preglob(pattern, 0); | 8089 | char *p = preglob(pattern, 0); |
| 8079 | //bb_error_msg("fnmatch(pattern:'%s',str:'%s')", p, string); | 8090 | int r = pmatch(p, string); |
| 8080 | return pmatch(p, string); | 8091 | //bb_error_msg("!fnmatch(pattern:'%s',str:'%s',0):%d", p, string, r); |
| 8092 | return r; | ||
| 8081 | } | 8093 | } |
| 8082 | 8094 | ||
| 8083 | /* | 8095 | /* |
| @@ -8179,7 +8191,7 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) const char *cmd, char **argv, c | |||
| 8179 | while (*envp) | 8191 | while (*envp) |
| 8180 | putenv(*envp++); | 8192 | putenv(*envp++); |
| 8181 | popredir(/*drop:*/ 1); | 8193 | popredir(/*drop:*/ 1); |
| 8182 | run_applet_no_and_exit(applet_no, cmd, argv); | 8194 | run_noexec_applet_and_exit(applet_no, cmd, argv); |
| 8183 | } | 8195 | } |
| 8184 | /* re-exec ourselves with the new arguments */ | 8196 | /* re-exec ourselves with the new arguments */ |
| 8185 | execve(bb_busybox_exec_path, argv, envp); | 8197 | execve(bb_busybox_exec_path, argv, envp); |
| @@ -10250,6 +10262,15 @@ evalcommand(union node *cmd, int flags) | |||
| 10250 | expredir(cmd->ncmd.redirect); | 10262 | expredir(cmd->ncmd.redirect); |
| 10251 | redir_stop = pushredir(cmd->ncmd.redirect); | 10263 | redir_stop = pushredir(cmd->ncmd.redirect); |
| 10252 | preverrout_fd = 2; | 10264 | preverrout_fd = 2; |
| 10265 | if (BASH_XTRACEFD && xflag) { | ||
| 10266 | /* NB: bash closes fd == $BASH_XTRACEFD when it is changed. | ||
| 10267 | * we do not emulate this. We only use its value. | ||
| 10268 | */ | ||
| 10269 | const char *xtracefd = lookupvar("BASH_XTRACEFD"); | ||
| 10270 | if (xtracefd && is_number(xtracefd)) | ||
| 10271 | preverrout_fd = atoi(xtracefd); | ||
| 10272 | |||
| 10273 | } | ||
| 10253 | status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH | REDIR_SAVEFD2); | 10274 | status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH | REDIR_SAVEFD2); |
| 10254 | 10275 | ||
| 10255 | path = vpath.var_text; | 10276 | path = vpath.var_text; |
| @@ -10383,12 +10404,30 @@ evalcommand(union node *cmd, int flags) | |||
| 10383 | #endif | 10404 | #endif |
| 10384 | ) { | 10405 | ) { |
| 10385 | listsetvar(varlist.list, VEXPORT|VSTACK); | 10406 | listsetvar(varlist.list, VEXPORT|VSTACK); |
| 10386 | /* run <applet>_main() */ | 10407 | /* |
| 10408 | * Run <applet>_main(). | ||
| 10409 | * Signals (^C) can't interrupt here. | ||
| 10410 | * Otherwise we can mangle stdio or malloc internal state. | ||
| 10411 | * This makes applets which can run for a long time | ||
| 10412 | * and/or wait for user input ineligible for NOFORK: | ||
| 10413 | * for example, "yes" or "rm" (rm -i waits for input). | ||
| 10414 | */ | ||
| 10415 | INT_OFF; | ||
| 10387 | status = run_nofork_applet(applet_no, argv); | 10416 | status = run_nofork_applet(applet_no, argv); |
| 10417 | /* | ||
| 10418 | * Try enabling NOFORK for "yes" applet. | ||
| 10419 | * ^C _will_ stop it (write returns EINTR), | ||
| 10420 | * but this causes stdout FILE to be stuck | ||
| 10421 | * and needing clearerr(). What if other applets | ||
| 10422 | * also can get EINTRs? Do we need to switch | ||
| 10423 | * our signals to SA_RESTART? | ||
| 10424 | */ | ||
| 10425 | /*clearerr(stdout);*/ | ||
| 10426 | INT_ON; | ||
| 10388 | break; | 10427 | break; |
| 10389 | } | 10428 | } |
| 10390 | #endif | 10429 | #endif |
| 10391 | /* Can we avoid forking off? For example, very last command | 10430 | /* Can we avoid forking? For example, very last command |
| 10392 | * in a script or a subshell does not need forking, | 10431 | * in a script or a subshell does not need forking, |
| 10393 | * we can just exec it. | 10432 | * we can just exec it. |
| 10394 | */ | 10433 | */ |
| @@ -10674,8 +10713,8 @@ preadfd(void) | |||
| 10674 | if (!iflag || g_parsefile->pf_fd != STDIN_FILENO) | 10713 | if (!iflag || g_parsefile->pf_fd != STDIN_FILENO) |
| 10675 | nr = nonblock_immune_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1); | 10714 | nr = nonblock_immune_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1); |
| 10676 | else { | 10715 | else { |
| 10677 | int timeout = -1; | ||
| 10678 | # if ENABLE_ASH_IDLE_TIMEOUT | 10716 | # if ENABLE_ASH_IDLE_TIMEOUT |
| 10717 | int timeout = -1; | ||
| 10679 | if (iflag) { | 10718 | if (iflag) { |
| 10680 | const char *tmout_var = lookupvar("TMOUT"); | 10719 | const char *tmout_var = lookupvar("TMOUT"); |
| 10681 | if (tmout_var) { | 10720 | if (tmout_var) { |
| @@ -10684,12 +10723,13 @@ preadfd(void) | |||
| 10684 | timeout = -1; | 10723 | timeout = -1; |
| 10685 | } | 10724 | } |
| 10686 | } | 10725 | } |
| 10726 | line_input_state->timeout = timeout; | ||
| 10687 | # endif | 10727 | # endif |
| 10688 | # if ENABLE_FEATURE_TAB_COMPLETION | 10728 | # if ENABLE_FEATURE_TAB_COMPLETION |
| 10689 | line_input_state->path_lookup = pathval(); | 10729 | line_input_state->path_lookup = pathval(); |
| 10690 | # endif | 10730 | # endif |
| 10691 | reinit_unicode_for_ash(); | 10731 | reinit_unicode_for_ash(); |
| 10692 | nr = read_line_input(line_input_state, cmdedit_prompt, buf, IBUFSIZ, timeout); | 10732 | nr = read_line_input(line_input_state, cmdedit_prompt, buf, IBUFSIZ); |
| 10693 | if (nr == 0) { | 10733 | if (nr == 0) { |
| 10694 | /* ^C pressed, "convert" to SIGINT */ | 10734 | /* ^C pressed, "convert" to SIGINT */ |
| 10695 | write(STDOUT_FILENO, "^C", 2); | 10735 | write(STDOUT_FILENO, "^C", 2); |
| @@ -11399,6 +11439,7 @@ getopts(char *optstr, char *optvar, char **optfirst) | |||
| 11399 | p = *optnext; | 11439 | p = *optnext; |
| 11400 | if (p == NULL || *p != '-' || *++p == '\0') { | 11440 | if (p == NULL || *p != '-' || *++p == '\0') { |
| 11401 | atend: | 11441 | atend: |
| 11442 | unsetvar("OPTARG"); | ||
| 11402 | p = NULL; | 11443 | p = NULL; |
| 11403 | done = 1; | 11444 | done = 1; |
| 11404 | goto out; | 11445 | goto out; |
| @@ -11411,7 +11452,11 @@ getopts(char *optstr, char *optvar, char **optfirst) | |||
| 11411 | c = *p++; | 11452 | c = *p++; |
| 11412 | for (q = optstr; *q != c;) { | 11453 | for (q = optstr; *q != c;) { |
| 11413 | if (*q == '\0') { | 11454 | if (*q == '\0') { |
| 11414 | if (optstr[0] == ':') { | 11455 | /* OPTERR is a bashism */ |
| 11456 | const char *cp = lookupvar("OPTERR"); | ||
| 11457 | if ((cp && LONE_CHAR(cp, '0')) | ||
| 11458 | || (optstr[0] == ':') | ||
| 11459 | ) { | ||
| 11415 | sbuf[0] = c; | 11460 | sbuf[0] = c; |
| 11416 | /*sbuf[1] = '\0'; - already is */ | 11461 | /*sbuf[1] = '\0'; - already is */ |
| 11417 | setvar0("OPTARG", sbuf); | 11462 | setvar0("OPTARG", sbuf); |
| @@ -11428,7 +11473,11 @@ getopts(char *optstr, char *optvar, char **optfirst) | |||
| 11428 | 11473 | ||
| 11429 | if (*++q == ':') { | 11474 | if (*++q == ':') { |
| 11430 | if (*p == '\0' && (p = *optnext) == NULL) { | 11475 | if (*p == '\0' && (p = *optnext) == NULL) { |
| 11431 | if (optstr[0] == ':') { | 11476 | /* OPTERR is a bashism */ |
| 11477 | const char *cp = lookupvar("OPTERR"); | ||
| 11478 | if ((cp && LONE_CHAR(cp, '0')) | ||
| 11479 | || (optstr[0] == ':') | ||
| 11480 | ) { | ||
| 11432 | sbuf[0] = c; | 11481 | sbuf[0] = c; |
| 11433 | /*sbuf[1] = '\0'; - already is */ | 11482 | /*sbuf[1] = '\0'; - already is */ |
| 11434 | setvar0("OPTARG", sbuf); | 11483 | setvar0("OPTARG", sbuf); |
| @@ -12571,7 +12620,7 @@ parsesub: { | |||
| 12571 | STPUTC(c, out); | 12620 | STPUTC(c, out); |
| 12572 | c = pgetc_eatbnl(); | 12621 | c = pgetc_eatbnl(); |
| 12573 | } while (isdigit(c)); | 12622 | } while (isdigit(c)); |
| 12574 | } else if (is_special(c)) { | 12623 | } else { |
| 12575 | /* $[{[#]]<specialchar>[}] */ | 12624 | /* $[{[#]]<specialchar>[}] */ |
| 12576 | int cc = c; | 12625 | int cc = c; |
| 12577 | 12626 | ||
| @@ -12589,10 +12638,16 @@ parsesub: { | |||
| 12589 | cc = '#'; | 12638 | cc = '#'; |
| 12590 | } | 12639 | } |
| 12591 | } | 12640 | } |
| 12641 | |||
| 12642 | if (!is_special(cc)) { | ||
| 12643 | if (subtype == VSLENGTH) | ||
| 12644 | subtype = 0; | ||
| 12645 | goto badsub; | ||
| 12646 | } | ||
| 12647 | |||
| 12592 | USTPUTC(cc, out); | 12648 | USTPUTC(cc, out); |
| 12593 | } else { | ||
| 12594 | goto badsub; | ||
| 12595 | } | 12649 | } |
| 12650 | |||
| 12596 | if (c != '}' && subtype == VSLENGTH) { | 12651 | if (c != '}' && subtype == VSLENGTH) { |
| 12597 | /* ${#VAR didn't end with } */ | 12652 | /* ${#VAR didn't end with } */ |
| 12598 | goto badsub; | 12653 | goto badsub; |
| @@ -13850,21 +13905,23 @@ static const unsigned char timescmd_str[] ALIGN1 = { | |||
| 13850 | static int FAST_FUNC | 13905 | static int FAST_FUNC |
| 13851 | timescmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) | 13906 | timescmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) |
| 13852 | { | 13907 | { |
| 13853 | unsigned long clk_tck, s, t; | 13908 | unsigned clk_tck; |
| 13854 | const unsigned char *p; | 13909 | const unsigned char *p; |
| 13855 | struct tms buf; | 13910 | struct tms buf; |
| 13856 | 13911 | ||
| 13857 | clk_tck = bb_clk_tck(); | 13912 | clk_tck = bb_clk_tck(); |
| 13858 | times(&buf); | ||
| 13859 | 13913 | ||
| 13914 | times(&buf); | ||
| 13860 | p = timescmd_str; | 13915 | p = timescmd_str; |
| 13861 | do { | 13916 | do { |
| 13917 | unsigned sec, frac; | ||
| 13918 | unsigned long t; | ||
| 13862 | t = *(clock_t *)(((char *) &buf) + p[1]); | 13919 | t = *(clock_t *)(((char *) &buf) + p[1]); |
| 13863 | s = t / clk_tck; | 13920 | sec = t / clk_tck; |
| 13864 | t = t % clk_tck; | 13921 | frac = t % clk_tck; |
| 13865 | out1fmt("%lum%lu.%03lus%c", | 13922 | out1fmt("%um%u.%03us%c", |
| 13866 | s / 60, s % 60, | 13923 | sec / 60, sec % 60, |
| 13867 | (t * 1000) / clk_tck, | 13924 | (frac * 1000) / clk_tck, |
| 13868 | p[0]); | 13925 | p[0]); |
| 13869 | p += 2; | 13926 | p += 2; |
| 13870 | } while (*p); | 13927 | } while (*p); |
| @@ -13903,10 +13960,10 @@ letcmd(int argc UNUSED_PARAM, char **argv) | |||
| 13903 | * -p PROMPT Display PROMPT on stderr (if input is from tty) | 13960 | * -p PROMPT Display PROMPT on stderr (if input is from tty) |
| 13904 | * -t SECONDS Timeout after SECONDS (tty or pipe only) | 13961 | * -t SECONDS Timeout after SECONDS (tty or pipe only) |
| 13905 | * -u FD Read from given FD instead of fd 0 | 13962 | * -u FD Read from given FD instead of fd 0 |
| 13963 | * -d DELIM End on DELIM char, not newline | ||
| 13906 | * This uses unbuffered input, which may be avoidable in some cases. | 13964 | * This uses unbuffered input, which may be avoidable in some cases. |
| 13907 | * TODO: bash also has: | 13965 | * TODO: bash also has: |
| 13908 | * -a ARRAY Read into array[0],[1],etc | 13966 | * -a ARRAY Read into array[0],[1],etc |
| 13909 | * -d DELIM End on DELIM char, not newline | ||
| 13910 | * -e Use line editing (tty only) | 13967 | * -e Use line editing (tty only) |
| 13911 | */ | 13968 | */ |
| 13912 | static int FAST_FUNC | 13969 | static int FAST_FUNC |
| @@ -13916,11 +13973,12 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) | |||
| 13916 | char *opt_p = NULL; | 13973 | char *opt_p = NULL; |
| 13917 | char *opt_t = NULL; | 13974 | char *opt_t = NULL; |
| 13918 | char *opt_u = NULL; | 13975 | char *opt_u = NULL; |
| 13976 | char *opt_d = NULL; /* optimized out if !BASH */ | ||
| 13919 | int read_flags = 0; | 13977 | int read_flags = 0; |
| 13920 | const char *r; | 13978 | const char *r; |
| 13921 | int i; | 13979 | int i; |
| 13922 | 13980 | ||
| 13923 | while ((i = nextopt("p:u:rt:n:s")) != '\0') { | 13981 | while ((i = nextopt("p:u:rt:n:sd:")) != '\0') { |
| 13924 | switch (i) { | 13982 | switch (i) { |
| 13925 | case 'p': | 13983 | case 'p': |
| 13926 | opt_p = optionarg; | 13984 | opt_p = optionarg; |
| @@ -13940,6 +13998,11 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) | |||
| 13940 | case 'u': | 13998 | case 'u': |
| 13941 | opt_u = optionarg; | 13999 | opt_u = optionarg; |
| 13942 | break; | 14000 | break; |
| 14001 | #if BASH_READ_D | ||
| 14002 | case 'd': | ||
| 14003 | opt_d = optionarg; | ||
| 14004 | break; | ||
| 14005 | #endif | ||
| 13943 | default: | 14006 | default: |
| 13944 | break; | 14007 | break; |
| 13945 | } | 14008 | } |
| @@ -13957,12 +14020,15 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) | |||
| 13957 | opt_n, | 14020 | opt_n, |
| 13958 | opt_p, | 14021 | opt_p, |
| 13959 | opt_t, | 14022 | opt_t, |
| 13960 | opt_u | 14023 | opt_u, |
| 14024 | opt_d | ||
| 13961 | ); | 14025 | ); |
| 13962 | INT_ON; | 14026 | INT_ON; |
| 13963 | 14027 | ||
| 13964 | if ((uintptr_t)r == 1 && errno == EINTR) { | 14028 | if ((uintptr_t)r == 1 && errno == EINTR) { |
| 13965 | /* to get SIGCHLD: sleep 1 & read x; echo $x */ | 14029 | /* To get SIGCHLD: sleep 1 & read x; echo $x |
| 14030 | * Correct behavior is to not exit "read" | ||
| 14031 | */ | ||
| 13966 | if (pending_sig == 0) | 14032 | if (pending_sig == 0) |
| 13967 | goto again; | 14033 | goto again; |
| 13968 | } | 14034 | } |
| @@ -14077,7 +14143,8 @@ exitshell(void) | |||
| 14077 | /* NOTREACHED */ | 14143 | /* NOTREACHED */ |
| 14078 | } | 14144 | } |
| 14079 | 14145 | ||
| 14080 | static void | 14146 | /* Don't inline: conserve stack of caller from having our locals too */ |
| 14147 | static NOINLINE void | ||
| 14081 | #if ENABLE_PLATFORM_MINGW32 | 14148 | #if ENABLE_PLATFORM_MINGW32 |
| 14082 | init(int xp) | 14149 | init(int xp) |
| 14083 | #else | 14150 | #else |
| @@ -14086,8 +14153,9 @@ init(void) | |||
| 14086 | { | 14153 | { |
| 14087 | /* we will never free this */ | 14154 | /* we will never free this */ |
| 14088 | basepf.next_to_pgetc = basepf.buf = ckmalloc(IBUFSIZ); | 14155 | basepf.next_to_pgetc = basepf.buf = ckmalloc(IBUFSIZ); |
| 14156 | basepf.linno = 1; | ||
| 14089 | 14157 | ||
| 14090 | sigmode[SIGCHLD - 1] = S_DFL; | 14158 | sigmode[SIGCHLD - 1] = S_DFL; /* ensure we install handler even if it is SIG_IGNed */ |
| 14091 | setsignal(SIGCHLD); | 14159 | setsignal(SIGCHLD); |
| 14092 | 14160 | ||
| 14093 | /* bash re-enables SIGHUP which is SIG_IGNed on entry. | 14161 | /* bash re-enables SIGHUP which is SIG_IGNed on entry. |
| @@ -14098,7 +14166,6 @@ init(void) | |||
| 14098 | { | 14166 | { |
| 14099 | char **envp; | 14167 | char **envp; |
| 14100 | const char *p; | 14168 | const char *p; |
| 14101 | struct stat st1, st2; | ||
| 14102 | 14169 | ||
| 14103 | initvar(); | 14170 | initvar(); |
| 14104 | 14171 | ||
| @@ -14204,6 +14271,7 @@ init(void) | |||
| 14204 | #endif | 14271 | #endif |
| 14205 | p = lookupvar("PWD"); | 14272 | p = lookupvar("PWD"); |
| 14206 | if (p) { | 14273 | if (p) { |
| 14274 | struct stat st1, st2; | ||
| 14207 | if (p[0] != '/' || stat(p, &st1) || stat(".", &st2) | 14275 | if (p[0] != '/' || stat(p, &st1) || stat(".", &st2) |
| 14208 | || st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino | 14276 | || st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino |
| 14209 | ) { | 14277 | ) { |
| @@ -15161,7 +15229,7 @@ sticky_free(void *base) | |||
| 15161 | * may be used to endorse or promote products derived from this software | 15229 | * may be used to endorse or promote products derived from this software |
| 15162 | * without specific prior written permission. | 15230 | * without specific prior written permission. |
| 15163 | * | 15231 | * |
| 15164 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | 15232 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND |
| 15165 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 15233 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 15166 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | 15234 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| 15167 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | 15235 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
diff --git a/shell/ash_LINENO.patch b/shell/ash_LINENO.patch new file mode 100644 index 000000000..a71549d6a --- /dev/null +++ b/shell/ash_LINENO.patch | |||
| @@ -0,0 +1,498 @@ | |||
| 1 | This patch is a backport from dash of the combination of: | ||
| 2 | [SHELL] Add preliminary LINENO support | ||
| 3 | [VAR] Fix varinit ordering that broke fc | ||
| 4 | [SHELL] Improve LINENO support | ||
| 5 | |||
| 6 | Applies cleanly on top of: | ||
| 7 | commit 9832bbaba966f0e52e183f10cd93fad7f8f643fe | ||
| 8 | Date: Tue Aug 15 15:44:41 2017 +0200 | ||
| 9 | |||
| 10 | Testsuite needs some tweaks (line numbers in some messages change). | ||
| 11 | |||
| 12 | Unfortunately, it is somewhat big: | ||
| 13 | |||
| 14 | function old new delta | ||
| 15 | parse_command 1581 1658 +77 | ||
| 16 | calcsize 203 272 +69 | ||
| 17 | copynode 195 257 +62 | ||
| 18 | lookupvar 59 108 +49 | ||
| 19 | evaltree 494 534 +40 | ||
| 20 | evalfor 152 187 +35 | ||
| 21 | evalcase 278 313 +35 | ||
| 22 | evalcommand 1547 1581 +34 | ||
| 23 | evalsubshell 169 199 +30 | ||
| 24 | linenovar - 22 +22 | ||
| 25 | raise_error_syntax 11 29 +18 | ||
| 26 | evalfun 266 280 +14 | ||
| 27 | varinit_data 96 108 +12 | ||
| 28 | cmdtxt 626 631 +5 | ||
| 29 | lineno - 4 +4 | ||
| 30 | funcline - 4 +4 | ||
| 31 | ash_vmsg 144 141 -3 | ||
| 32 | startlinno 4 - -4 | ||
| 33 | funcnest 4 - -4 | ||
| 34 | xxreadtoken 272 259 -13 | ||
| 35 | readtoken1 2635 2594 -41 | ||
| 36 | ------------------------------------------------------------------------------ | ||
| 37 | (add/remove: 3/2 grow/shrink: 13/3 up/down: 510/-65) Total: 445 bytes | ||
| 38 | text data bss dec hex filename | ||
| 39 | 912030 563 5844 918437 e03a5 busybox_old | ||
| 40 | 912473 587 5844 918904 e0578 busybox_unstripped | ||
| 41 | |||
| 42 | diff --git a/shell/ash.c b/shell/ash.c | ||
| 43 | index 703802f..93a3814 100644 | ||
| 44 | --- a/shell/ash.c | ||
| 45 | +++ b/shell/ash.c | ||
| 46 | @@ -312,6 +312,8 @@ struct globals_misc { | ||
| 47 | /* shell level: 0 for the main shell, 1 for its children, and so on */ | ||
| 48 | int shlvl; | ||
| 49 | #define rootshell (!shlvl) | ||
| 50 | + int errlinno; | ||
| 51 | + | ||
| 52 | char *minusc; /* argument to -c option */ | ||
| 53 | |||
| 54 | char *curdir; // = nullstr; /* current working directory */ | ||
| 55 | @@ -389,6 +391,7 @@ extern struct globals_misc *const ash_ptr_to_globals_misc; | ||
| 56 | #define job_warning (G_misc.job_warning) | ||
| 57 | #define rootpid (G_misc.rootpid ) | ||
| 58 | #define shlvl (G_misc.shlvl ) | ||
| 59 | +#define errlinno (G_misc.errlinno ) | ||
| 60 | #define minusc (G_misc.minusc ) | ||
| 61 | #define curdir (G_misc.curdir ) | ||
| 62 | #define physdir (G_misc.physdir ) | ||
| 63 | @@ -723,6 +726,7 @@ union node; | ||
| 64 | |||
| 65 | struct ncmd { | ||
| 66 | smallint type; /* Nxxxx */ | ||
| 67 | + int linno; | ||
| 68 | union node *assign; | ||
| 69 | union node *args; | ||
| 70 | union node *redirect; | ||
| 71 | @@ -736,6 +740,7 @@ struct npipe { | ||
| 72 | |||
| 73 | struct nredir { | ||
| 74 | smallint type; | ||
| 75 | + int linno; | ||
| 76 | union node *n; | ||
| 77 | union node *redirect; | ||
| 78 | }; | ||
| 79 | @@ -755,6 +760,7 @@ struct nif { | ||
| 80 | |||
| 81 | struct nfor { | ||
| 82 | smallint type; | ||
| 83 | + int linno; | ||
| 84 | union node *args; | ||
| 85 | union node *body; | ||
| 86 | char *var; | ||
| 87 | @@ -762,6 +768,7 @@ struct nfor { | ||
| 88 | |||
| 89 | struct ncase { | ||
| 90 | smallint type; | ||
| 91 | + int linno; | ||
| 92 | union node *expr; | ||
| 93 | union node *cases; | ||
| 94 | }; | ||
| 95 | @@ -773,6 +780,13 @@ struct nclist { | ||
| 96 | union node *body; | ||
| 97 | }; | ||
| 98 | |||
| 99 | +struct ndefun { | ||
| 100 | + smallint type; | ||
| 101 | + int linno; | ||
| 102 | + char *text; | ||
| 103 | + union node *body; | ||
| 104 | +}; | ||
| 105 | + | ||
| 106 | struct narg { | ||
| 107 | smallint type; | ||
| 108 | union node *next; | ||
| 109 | @@ -824,6 +838,7 @@ union node { | ||
| 110 | struct nfor nfor; | ||
| 111 | struct ncase ncase; | ||
| 112 | struct nclist nclist; | ||
| 113 | + struct ndefun ndefun; | ||
| 114 | struct narg narg; | ||
| 115 | struct nfile nfile; | ||
| 116 | struct ndup ndup; | ||
| 117 | @@ -1253,7 +1268,6 @@ struct parsefile { | ||
| 118 | |||
| 119 | static struct parsefile basepf; /* top level input file */ | ||
| 120 | static struct parsefile *g_parsefile = &basepf; /* current input file */ | ||
| 121 | -static int startlinno; /* line # where last token started */ | ||
| 122 | static char *commandname; /* currently executing command */ | ||
| 123 | |||
| 124 | |||
| 125 | @@ -1267,7 +1281,7 @@ ash_vmsg(const char *msg, va_list ap) | ||
| 126 | if (strcmp(arg0, commandname)) | ||
| 127 | fprintf(stderr, "%s: ", commandname); | ||
| 128 | if (!iflag || g_parsefile->pf_fd > 0) | ||
| 129 | - fprintf(stderr, "line %d: ", startlinno); | ||
| 130 | + fprintf(stderr, "line %d: ", errlinno); | ||
| 131 | } | ||
| 132 | vfprintf(stderr, msg, ap); | ||
| 133 | newline_and_flush(stderr); | ||
| 134 | @@ -1327,6 +1341,7 @@ static void raise_error_syntax(const char *) NORETURN; | ||
| 135 | static void | ||
| 136 | raise_error_syntax(const char *msg) | ||
| 137 | { | ||
| 138 | + errlinno = g_parsefile->linno; | ||
| 139 | ash_msg_and_raise_error("syntax error: %s", msg); | ||
| 140 | /* NOTREACHED */ | ||
| 141 | } | ||
| 142 | @@ -1993,6 +2008,9 @@ static void changepath(const char *) FAST_FUNC; | ||
| 143 | static void change_random(const char *) FAST_FUNC; | ||
| 144 | #endif | ||
| 145 | |||
| 146 | +static int lineno; | ||
| 147 | +static char linenovar[sizeof("LINENO=%d") + sizeof(int)*3] = "LINENO="; | ||
| 148 | + | ||
| 149 | static const struct { | ||
| 150 | int flags; | ||
| 151 | const char *var_text; | ||
| 152 | @@ -2014,6 +2032,7 @@ static const struct { | ||
| 153 | #if ENABLE_ASH_GETOPTS | ||
| 154 | { VSTRFIXED|VTEXTFIXED , defoptindvar, getoptsreset }, | ||
| 155 | #endif | ||
| 156 | + { VSTRFIXED|VTEXTFIXED , linenovar , NULL }, | ||
| 157 | #if ENABLE_ASH_RANDOM_SUPPORT | ||
| 158 | { VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM", change_random }, | ||
| 159 | #endif | ||
| 160 | @@ -2066,12 +2085,14 @@ extern struct globals_var *const ash_ptr_to_globals_var; | ||
| 161 | #define vps4 (&vps2)[1] | ||
| 162 | #if ENABLE_ASH_GETOPTS | ||
| 163 | # define voptind (&vps4)[1] | ||
| 164 | +# define vlineno (&voptind)[1] | ||
| 165 | # if ENABLE_ASH_RANDOM_SUPPORT | ||
| 166 | -# define vrandom (&voptind)[1] | ||
| 167 | +# define vrandom (&vlineno)[1] | ||
| 168 | # endif | ||
| 169 | #else | ||
| 170 | +# define vlineno (&vps4)[1] | ||
| 171 | # if ENABLE_ASH_RANDOM_SUPPORT | ||
| 172 | -# define vrandom (&vps4)[1] | ||
| 173 | +# define vrandom (&vlineno)[1] | ||
| 174 | # endif | ||
| 175 | #endif | ||
| 176 | |||
| 177 | @@ -2209,8 +2230,12 @@ lookupvar(const char *name) | ||
| 178 | if (v->flags & VDYNAMIC) | ||
| 179 | v->var_func(NULL); | ||
| 180 | #endif | ||
| 181 | - if (!(v->flags & VUNSET)) | ||
| 182 | + if (!(v->flags & VUNSET)) { | ||
| 183 | + if (v == &vlineno && v->var_text == linenovar) { | ||
| 184 | + fmtstr(linenovar+7, sizeof(linenovar)-7, "%d", lineno); | ||
| 185 | + } | ||
| 186 | return var_end(v->var_text); | ||
| 187 | + } | ||
| 188 | } | ||
| 189 | return NULL; | ||
| 190 | } | ||
| 191 | @@ -4783,7 +4808,7 @@ cmdtxt(union node *n) | ||
| 192 | p = "; done"; | ||
| 193 | goto dodo; | ||
| 194 | case NDEFUN: | ||
| 195 | - cmdputs(n->narg.text); | ||
| 196 | + cmdputs(n->ndefun.text); | ||
| 197 | p = "() { ... }"; | ||
| 198 | goto dotail2; | ||
| 199 | case NCMD: | ||
| 200 | @@ -8551,6 +8576,9 @@ calcsize(int funcblocksize, union node *n) | ||
| 201 | funcblocksize = calcsize(funcblocksize, n->nclist.next); | ||
| 202 | break; | ||
| 203 | case NDEFUN: | ||
| 204 | + funcblocksize = calcsize(funcblocksize, n->ndefun.body); | ||
| 205 | + funcblocksize += SHELL_ALIGN(strlen(n->ndefun.text) + 1); | ||
| 206 | + break; | ||
| 207 | case NARG: | ||
| 208 | funcblocksize = sizenodelist(funcblocksize, n->narg.backquote); | ||
| 209 | funcblocksize += SHELL_ALIGN(strlen(n->narg.text) + 1); /* was funcstringsize += ... */ | ||
| 210 | @@ -8626,6 +8654,7 @@ copynode(union node *n) | ||
| 211 | new->ncmd.redirect = copynode(n->ncmd.redirect); | ||
| 212 | new->ncmd.args = copynode(n->ncmd.args); | ||
| 213 | new->ncmd.assign = copynode(n->ncmd.assign); | ||
| 214 | + new->ncmd.linno = n->ncmd.linno; | ||
| 215 | break; | ||
| 216 | case NPIPE: | ||
| 217 | new->npipe.cmdlist = copynodelist(n->npipe.cmdlist); | ||
| 218 | @@ -8636,6 +8665,7 @@ copynode(union node *n) | ||
| 219 | case NSUBSHELL: | ||
| 220 | new->nredir.redirect = copynode(n->nredir.redirect); | ||
| 221 | new->nredir.n = copynode(n->nredir.n); | ||
| 222 | + new->nredir.linno = n->nredir.linno; | ||
| 223 | break; | ||
| 224 | case NAND: | ||
| 225 | case NOR: | ||
| 226 | @@ -8654,10 +8684,12 @@ copynode(union node *n) | ||
| 227 | new->nfor.var = nodeckstrdup(n->nfor.var); | ||
| 228 | new->nfor.body = copynode(n->nfor.body); | ||
| 229 | new->nfor.args = copynode(n->nfor.args); | ||
| 230 | + new->nfor.linno = n->nfor.linno; | ||
| 231 | break; | ||
| 232 | case NCASE: | ||
| 233 | new->ncase.cases = copynode(n->ncase.cases); | ||
| 234 | new->ncase.expr = copynode(n->ncase.expr); | ||
| 235 | + new->ncase.linno = n->ncase.linno; | ||
| 236 | break; | ||
| 237 | case NCLIST: | ||
| 238 | new->nclist.body = copynode(n->nclist.body); | ||
| 239 | @@ -8665,6 +8697,10 @@ copynode(union node *n) | ||
| 240 | new->nclist.next = copynode(n->nclist.next); | ||
| 241 | break; | ||
| 242 | case NDEFUN: | ||
| 243 | + new->ndefun.body = copynode(n->ndefun.body); | ||
| 244 | + new->ndefun.text = nodeckstrdup(n->ndefun.text); | ||
| 245 | + new->ndefun.linno = n->ndefun.linno; | ||
| 246 | + break; | ||
| 247 | case NARG: | ||
| 248 | new->narg.backquote = copynodelist(n->narg.backquote); | ||
| 249 | new->narg.text = nodeckstrdup(n->narg.text); | ||
| 250 | @@ -8733,7 +8769,7 @@ defun(union node *func) | ||
| 251 | INT_OFF; | ||
| 252 | entry.cmdtype = CMDFUNCTION; | ||
| 253 | entry.u.func = copyfunc(func); | ||
| 254 | - addcmdentry(func->narg.text, &entry); | ||
| 255 | + addcmdentry(func->ndefun.text, &entry); | ||
| 256 | INT_ON; | ||
| 257 | } | ||
| 258 | |||
| 259 | @@ -8743,8 +8779,8 @@ defun(union node *func) | ||
| 260 | #define SKIPFUNC (1 << 2) | ||
| 261 | static smallint evalskip; /* set to SKIPxxx if we are skipping commands */ | ||
| 262 | static int skipcount; /* number of levels to skip */ | ||
| 263 | -static int funcnest; /* depth of function calls */ | ||
| 264 | static int loopnest; /* current loop nesting level */ | ||
| 265 | +static int funcline; /* starting line number of current function, or 0 if not in a function */ | ||
| 266 | |||
| 267 | /* Forward decl way out to parsing code - dotrap needs it */ | ||
| 268 | static int evalstring(char *s, int flags); | ||
| 269 | @@ -8839,6 +8875,9 @@ evaltree(union node *n, int flags) | ||
| 270 | status = !evaltree(n->nnot.com, EV_TESTED); | ||
| 271 | goto setstatus; | ||
| 272 | case NREDIR: | ||
| 273 | + errlinno = lineno = n->nredir.linno; | ||
| 274 | + if (funcline) | ||
| 275 | + lineno -= funcline - 1; | ||
| 276 | expredir(n->nredir.redirect); | ||
| 277 | pushredir(n->nredir.redirect); | ||
| 278 | status = redirectsafe(n->nredir.redirect, REDIR_PUSH); | ||
| 279 | @@ -8993,6 +9032,10 @@ evalfor(union node *n, int flags) | ||
| 280 | struct stackmark smark; | ||
| 281 | int status = 0; | ||
| 282 | |||
| 283 | + errlinno = lineno = n->ncase.linno; | ||
| 284 | + if (funcline) | ||
| 285 | + lineno -= funcline - 1; | ||
| 286 | + | ||
| 287 | setstackmark(&smark); | ||
| 288 | arglist.list = NULL; | ||
| 289 | arglist.lastp = &arglist.list; | ||
| 290 | @@ -9024,6 +9067,10 @@ evalcase(union node *n, int flags) | ||
| 291 | struct stackmark smark; | ||
| 292 | int status = 0; | ||
| 293 | |||
| 294 | + errlinno = lineno = n->ncase.linno; | ||
| 295 | + if (funcline) | ||
| 296 | + lineno -= funcline - 1; | ||
| 297 | + | ||
| 298 | setstackmark(&smark); | ||
| 299 | arglist.list = NULL; | ||
| 300 | arglist.lastp = &arglist.list; | ||
| 301 | @@ -9058,6 +9105,10 @@ evalsubshell(union node *n, int flags) | ||
| 302 | int backgnd = (n->type == NBACKGND); /* FORK_BG(1) if yes, else FORK_FG(0) */ | ||
| 303 | int status; | ||
| 304 | |||
| 305 | + errlinno = lineno = n->nredir.linno; | ||
| 306 | + if (funcline) | ||
| 307 | + lineno -= funcline - 1; | ||
| 308 | + | ||
| 309 | expredir(n->nredir.redirect); | ||
| 310 | if (!backgnd && (flags & EV_EXIT) && !may_have_traps) | ||
| 311 | goto nofork; | ||
| 312 | @@ -9365,8 +9416,10 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) | ||
| 313 | struct jmploc *volatile savehandler; | ||
| 314 | struct jmploc jmploc; | ||
| 315 | int e; | ||
| 316 | + int savefuncline; | ||
| 317 | |||
| 318 | saveparam = shellparam; | ||
| 319 | + savefuncline = funcline; | ||
| 320 | savehandler = exception_handler; | ||
| 321 | e = setjmp(jmploc.loc); | ||
| 322 | if (e) { | ||
| 323 | @@ -9376,7 +9429,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) | ||
| 324 | exception_handler = &jmploc; | ||
| 325 | shellparam.malloced = 0; | ||
| 326 | func->count++; | ||
| 327 | - funcnest++; | ||
| 328 | + funcline = func->n.ndefun.linno; | ||
| 329 | INT_ON; | ||
| 330 | shellparam.nparam = argc - 1; | ||
| 331 | shellparam.p = argv + 1; | ||
| 332 | @@ -9385,11 +9438,11 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) | ||
| 333 | shellparam.optoff = -1; | ||
| 334 | #endif | ||
| 335 | pushlocalvars(); | ||
| 336 | - evaltree(func->n.narg.next, flags & EV_TESTED); | ||
| 337 | + evaltree(func->n.ndefun.body, flags & EV_TESTED); | ||
| 338 | poplocalvars(0); | ||
| 339 | funcdone: | ||
| 340 | INT_OFF; | ||
| 341 | - funcnest--; | ||
| 342 | + funcline = savefuncline; | ||
| 343 | freefunc(func); | ||
| 344 | freeparam(&shellparam); | ||
| 345 | shellparam = saveparam; | ||
| 346 | @@ -9753,6 +9806,10 @@ evalcommand(union node *cmd, int flags) | ||
| 347 | char **nargv; | ||
| 348 | smallint cmd_is_exec; | ||
| 349 | |||
| 350 | + errlinno = lineno = cmd->ncmd.linno; | ||
| 351 | + if (funcline) | ||
| 352 | + lineno -= funcline - 1; | ||
| 353 | + | ||
| 354 | /* First expand the arguments. */ | ||
| 355 | TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); | ||
| 356 | setstackmark(&smark); | ||
| 357 | @@ -9798,7 +9855,7 @@ evalcommand(union node *cmd, int flags) | ||
| 358 | *nargv = NULL; | ||
| 359 | |||
| 360 | lastarg = NULL; | ||
| 361 | - if (iflag && funcnest == 0 && argc > 0) | ||
| 362 | + if (iflag && funcline == 0 && argc > 0) | ||
| 363 | lastarg = nargv[-1]; | ||
| 364 | |||
| 365 | expredir(cmd->ncmd.redirect); | ||
| 366 | @@ -11317,6 +11374,7 @@ simplecmd(void) | ||
| 367 | union node *vars, **vpp; | ||
| 368 | union node **rpp, *redir; | ||
| 369 | int savecheckkwd; | ||
| 370 | + int savelinno; | ||
| 371 | #if BASH_TEST2 | ||
| 372 | smallint double_brackets_flag = 0; | ||
| 373 | #endif | ||
| 374 | @@ -11330,6 +11388,7 @@ simplecmd(void) | ||
| 375 | rpp = &redir; | ||
| 376 | |||
| 377 | savecheckkwd = CHKALIAS; | ||
| 378 | + savelinno = g_parsefile->linno; | ||
| 379 | for (;;) { | ||
| 380 | int t; | ||
| 381 | checkkwd = savecheckkwd; | ||
| 382 | @@ -11419,7 +11478,9 @@ simplecmd(void) | ||
| 383 | } | ||
| 384 | n->type = NDEFUN; | ||
| 385 | checkkwd = CHKNL | CHKKWD | CHKALIAS; | ||
| 386 | - n->narg.next = parse_command(); | ||
| 387 | + n->ndefun.text = n->narg.text; | ||
| 388 | + n->ndefun.linno = g_parsefile->linno; | ||
| 389 | + n->ndefun.body = parse_command(); | ||
| 390 | return n; | ||
| 391 | } | ||
| 392 | IF_BASH_FUNCTION(function_flag = 0;) | ||
| 393 | @@ -11435,6 +11496,7 @@ simplecmd(void) | ||
| 394 | *rpp = NULL; | ||
| 395 | n = stzalloc(sizeof(struct ncmd)); | ||
| 396 | n->type = NCMD; | ||
| 397 | + n->ncmd.linno = savelinno; | ||
| 398 | n->ncmd.args = args; | ||
| 399 | n->ncmd.assign = vars; | ||
| 400 | n->ncmd.redirect = redir; | ||
| 401 | @@ -11450,10 +11512,13 @@ parse_command(void) | ||
| 402 | union node *redir, **rpp; | ||
| 403 | union node **rpp2; | ||
| 404 | int t; | ||
| 405 | + int savelinno; | ||
| 406 | |||
| 407 | redir = NULL; | ||
| 408 | rpp2 = &redir; | ||
| 409 | |||
| 410 | + savelinno = g_parsefile->linno; | ||
| 411 | + | ||
| 412 | switch (readtoken()) { | ||
| 413 | default: | ||
| 414 | raise_error_unexpected_syntax(-1); | ||
| 415 | @@ -11504,6 +11569,7 @@ parse_command(void) | ||
| 416 | raise_error_syntax("bad for loop variable"); | ||
| 417 | n1 = stzalloc(sizeof(struct nfor)); | ||
| 418 | n1->type = NFOR; | ||
| 419 | + n1->nfor.linno = savelinno; | ||
| 420 | n1->nfor.var = wordtext; | ||
| 421 | checkkwd = CHKNL | CHKKWD | CHKALIAS; | ||
| 422 | if (readtoken() == TIN) { | ||
| 423 | @@ -11544,6 +11610,7 @@ parse_command(void) | ||
| 424 | case TCASE: | ||
| 425 | n1 = stzalloc(sizeof(struct ncase)); | ||
| 426 | n1->type = NCASE; | ||
| 427 | + n1->ncase.linno = savelinno; | ||
| 428 | if (readtoken() != TWORD) | ||
| 429 | raise_error_unexpected_syntax(TWORD); | ||
| 430 | n1->ncase.expr = n2 = stzalloc(sizeof(struct narg)); | ||
| 431 | @@ -11595,6 +11662,7 @@ parse_command(void) | ||
| 432 | case TLP: | ||
| 433 | n1 = stzalloc(sizeof(struct nredir)); | ||
| 434 | n1->type = NSUBSHELL; | ||
| 435 | + n1->nredir.linno = savelinno; | ||
| 436 | n1->nredir.n = list(0); | ||
| 437 | /*n1->nredir.redirect = NULL; - stzalloc did it */ | ||
| 438 | t = TRP; | ||
| 439 | @@ -11628,6 +11696,7 @@ parse_command(void) | ||
| 440 | if (n1->type != NSUBSHELL) { | ||
| 441 | n2 = stzalloc(sizeof(struct nredir)); | ||
| 442 | n2->type = NREDIR; | ||
| 443 | + n2->nredir.linno = savelinno; | ||
| 444 | n2->nredir.n = n1; | ||
| 445 | n1 = n2; | ||
| 446 | } | ||
| 447 | @@ -11726,10 +11795,8 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) | ||
| 448 | IF_FEATURE_SH_MATH(int arinest;) /* levels of arithmetic expansion */ | ||
| 449 | IF_FEATURE_SH_MATH(int parenlevel;) /* levels of parens in arithmetic */ | ||
| 450 | int dqvarnest; /* levels of variables expansion within double quotes */ | ||
| 451 | - | ||
| 452 | IF_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;) | ||
| 453 | |||
| 454 | - startlinno = g_parsefile->linno; | ||
| 455 | bqlist = NULL; | ||
| 456 | quotef = 0; | ||
| 457 | IF_FEATURE_SH_MATH(prevsyntax = 0;) | ||
| 458 | @@ -11906,7 +11973,6 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) | ||
| 459 | if (syntax != BASESYNTAX && eofmark == NULL) | ||
| 460 | raise_error_syntax("unterminated quoted string"); | ||
| 461 | if (varnest != 0) { | ||
| 462 | - startlinno = g_parsefile->linno; | ||
| 463 | /* { */ | ||
| 464 | raise_error_syntax("missing '}'"); | ||
| 465 | } | ||
| 466 | @@ -12298,7 +12364,6 @@ parsebackq: { | ||
| 467 | |||
| 468 | case PEOF: | ||
| 469 | IF_ASH_ALIAS(case PEOA:) | ||
| 470 | - startlinno = g_parsefile->linno; | ||
| 471 | raise_error_syntax("EOF in backquote substitution"); | ||
| 472 | |||
| 473 | case '\n': | ||
| 474 | @@ -12380,8 +12445,6 @@ parsearith: { | ||
| 475 | * quoted. | ||
| 476 | * If the token is TREDIR, then we set redirnode to a structure containing | ||
| 477 | * the redirection. | ||
| 478 | - * In all cases, the variable startlinno is set to the number of the line | ||
| 479 | - * on which the token starts. | ||
| 480 | * | ||
| 481 | * [Change comment: here documents and internal procedures] | ||
| 482 | * [Readtoken shouldn't have any arguments. Perhaps we should make the | ||
| 483 | @@ -12419,7 +12482,6 @@ xxreadtoken(void) | ||
| 484 | return lasttoken; | ||
| 485 | } | ||
| 486 | setprompt_if(needprompt, 2); | ||
| 487 | - startlinno = g_parsefile->linno; | ||
| 488 | for (;;) { /* until token or start of word found */ | ||
| 489 | c = pgetc(); | ||
| 490 | if (c == ' ' || c == '\t' IF_ASH_ALIAS( || c == PEOA)) | ||
| 491 | @@ -12480,7 +12542,6 @@ xxreadtoken(void) | ||
| 492 | return lasttoken; | ||
| 493 | } | ||
| 494 | setprompt_if(needprompt, 2); | ||
| 495 | - startlinno = g_parsefile->linno; | ||
| 496 | for (;;) { /* until token or start of word found */ | ||
| 497 | c = pgetc(); | ||
| 498 | switch (c) { | ||
diff --git a/shell/ash_test/ash-getopts/getopt_optarg.right b/shell/ash_test/ash-getopts/getopt_optarg.right new file mode 100644 index 000000000..dff28de57 --- /dev/null +++ b/shell/ash_test/ash-getopts/getopt_optarg.right | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | *** no OPTIND, optstring:'w:et' args:-q -w e -r -t -y | ||
| 2 | Illegal option -q | ||
| 3 | var:'?' OPTIND:2 OPTARG:'' | ||
| 4 | var:'w' OPTIND:4 OPTARG:'e' | ||
| 5 | Illegal option -r | ||
| 6 | var:'?' OPTIND:5 OPTARG:'' | ||
| 7 | var:'t' OPTIND:6 OPTARG:'' | ||
| 8 | Illegal option -y | ||
| 9 | var:'?' OPTIND:7 OPTARG:'' | ||
| 10 | exited: var:'?' OPTIND:7 OPTARG:'' | ||
| 11 | *** OPTIND=0, optstring:'w:et' args:-w 1 -w2 -w -e -e -t -t | ||
| 12 | var:'w' OPTIND:3 OPTARG:'1' | ||
| 13 | var:'w' OPTIND:4 OPTARG:'2' | ||
| 14 | var:'w' OPTIND:6 OPTARG:'-e' | ||
| 15 | var:'e' OPTIND:7 OPTARG:'' | ||
| 16 | var:'t' OPTIND:8 OPTARG:'' | ||
| 17 | var:'t' OPTIND:9 OPTARG:'' | ||
| 18 | exited: var:'?' OPTIND:9 OPTARG:'' | ||
diff --git a/shell/ash_test/ash-getopts/getopt_optarg.tests b/shell/ash_test/ash-getopts/getopt_optarg.tests new file mode 100755 index 000000000..33682e868 --- /dev/null +++ b/shell/ash_test/ash-getopts/getopt_optarg.tests | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | set -- -q -w e -r -t -y | ||
| 2 | echo "*** no OPTIND, optstring:'w:et' args:$*" | ||
| 3 | var=QWERTY | ||
| 4 | OPTARG=ASDFGH | ||
| 5 | while getopts "w:et" var; do | ||
| 6 | echo "var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 7 | OPTARG=ASDFGH | ||
| 8 | done | ||
| 9 | echo "exited: var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 10 | |||
| 11 | set -- -w 1 -w2 -w -e -e -t -t | ||
| 12 | echo "*** OPTIND=0, optstring:'w:et' args:$*" | ||
| 13 | OPTIND=0 | ||
| 14 | while getopts "w:et" var; do | ||
| 15 | echo "var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 16 | OPTARG=ASDFGH | ||
| 17 | done | ||
| 18 | echo "exited: var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
diff --git a/shell/ash_test/ash-getopts/getopt_positional.right b/shell/ash_test/ash-getopts/getopt_positional.right new file mode 100644 index 000000000..37d0ec845 --- /dev/null +++ b/shell/ash_test/ash-getopts/getopt_positional.right | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | *** no OPTIND, optstring:'we' args:-q -w -e r -t -y | ||
| 2 | Illegal option -q | ||
| 3 | var:'?' OPTIND:2 | ||
| 4 | var:'w' OPTIND:3 | ||
| 5 | var:'e' OPTIND:4 | ||
| 6 | exited: var:'?' OPTIND:4 | ||
diff --git a/shell/ash_test/ash-getopts/getopt_positional.tests b/shell/ash_test/ash-getopts/getopt_positional.tests new file mode 100755 index 000000000..a5404a2a0 --- /dev/null +++ b/shell/ash_test/ash-getopts/getopt_positional.tests | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | set -- -q -w -e r -t -y | ||
| 2 | echo "*** no OPTIND, optstring:'we' args:$*" | ||
| 3 | var=QWERTY | ||
| 4 | while getopts "we" var; do | ||
| 5 | echo "var:'$var' OPTIND:$OPTIND" | ||
| 6 | done | ||
| 7 | # unfortunately, "rc:0" is shown since while's overall exitcode is "success" | ||
| 8 | echo "exited: var:'$var' OPTIND:$OPTIND" | ||
diff --git a/shell/ash_test/ash-getopts/getopt_silent.right b/shell/ash_test/ash-getopts/getopt_silent.right new file mode 100644 index 000000000..03d4eb149 --- /dev/null +++ b/shell/ash_test/ash-getopts/getopt_silent.right | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | *** optstring:':ac' args:-a -b -c | ||
| 2 | 1 rc:0 var:'a' OPTIND:2 OPTARG:'' | ||
| 3 | 2 rc:0 var:'?' OPTIND:3 OPTARG:'b' | ||
| 4 | 3 rc:0 var:'c' OPTIND:4 OPTARG:'' | ||
| 5 | 4 rc:1 var:'?' OPTIND:4 OPTARG:'' | ||
| 6 | 5 rc:1 var:'?' OPTIND:4 OPTARG:'' | ||
diff --git a/shell/ash_test/ash-getopts/getopt_silent.tests b/shell/ash_test/ash-getopts/getopt_silent.tests new file mode 100755 index 000000000..097d7ba85 --- /dev/null +++ b/shell/ash_test/ash-getopts/getopt_silent.tests | |||
| @@ -0,0 +1,23 @@ | |||
| 1 | # Open Group Base Specifications Issue 7: | ||
| 2 | # """ | ||
| 3 | # If an unknown option is met, VAR shall be set to "?". In this case, | ||
| 4 | # if the first character in optstring is ":", OPTARG shall be set | ||
| 5 | # to the option character found, but no output shall be written | ||
| 6 | # to standard error; otherwise, the shell variable OPTARG shall be | ||
| 7 | # unset and a diagnostic message shall be written to standard error." | ||
| 8 | # ... | ||
| 9 | # If an option-argument is missing: | ||
| 10 | # If the first character of optstring is ":", VAR shall be set to ":" | ||
| 11 | # and OPTARG shall be set to the option character found. | ||
| 12 | # """ | ||
| 13 | |||
| 14 | echo "*** optstring:':ac' args:-a -b -c" | ||
| 15 | getopts ":ac" var -a -b -c; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 16 | getopts ":ac" var -a -b -c; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 17 | getopts ":ac" var -a -b -c; echo "3 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 18 | getopts ":ac" var -a -b -c; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 19 | # Previous line should result in "rc:1", which is normally treated | ||
| 20 | # in getopts loops as exit condition. | ||
| 21 | # Nevertheless, let's verify that calling it yet another time doesn't do | ||
| 22 | # anything weird: | ||
| 23 | getopts ":ac" var -a -b -c; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
diff --git a/shell/ash_test/ash-getopts/getopt_simple.right b/shell/ash_test/ash-getopts/getopt_simple.right new file mode 100644 index 000000000..07e3c57f5 --- /dev/null +++ b/shell/ash_test/ash-getopts/getopt_simple.right | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | *** no OPTIND, optstring:'ab' args:-a -b c | ||
| 2 | var:'a' OPTIND:2 | ||
| 3 | var:'b' OPTIND:3 | ||
| 4 | exited: rc:0 var:'?' OPTIND:3 | ||
| 5 | *** OPTIND=1, optstring:'ab' args:-a -b c | ||
| 6 | var:'a' OPTIND:2 | ||
| 7 | var:'b' OPTIND:3 | ||
| 8 | exited: rc:0 var:'?' OPTIND:3 | ||
| 9 | *** OPTIND=0, optstring:'ab' args:-a -b c | ||
| 10 | var:'a' OPTIND:2 | ||
| 11 | var:'b' OPTIND:3 | ||
| 12 | exited: rc:0 var:'?' OPTIND:3 | ||
| 13 | *** unset OPTIND, optstring:'ab' args:-a -b c | ||
| 14 | var:'a' OPTIND:2 | ||
| 15 | var:'b' OPTIND:3 | ||
| 16 | exited: rc:0 var:'?' OPTIND:3 | ||
| 17 | *** optstring:'ab' args:-a -b c | ||
| 18 | 1 rc:0 var:'a' OPTIND:2 | ||
| 19 | 2 rc:0 var:'b' OPTIND:3 | ||
| 20 | 3 rc:1 var:'?' OPTIND:3 | ||
| 21 | *** unset OPTIND, optstring:'ab' args:-a c -c -b d | ||
| 22 | var:'a' OPTIND:2 | ||
| 23 | exited: rc:0 var:'?' OPTIND:2 | ||
| 24 | *** unset OPTIND, optstring:'ab' args:-a -c -b d | ||
| 25 | var:'a' OPTIND:2 | ||
| 26 | Illegal option -c | ||
| 27 | var:'?' OPTIND:3 | ||
| 28 | var:'b' OPTIND:4 | ||
| 29 | exited: rc:0 var:'?' OPTIND:4 | ||
| 30 | *** unset OPTIND, OPTERR=0, optstring:'ab' args:-a -c -b d | ||
| 31 | var:'a' OPTIND:2 | ||
| 32 | var:'?' OPTIND:3 | ||
| 33 | var:'b' OPTIND:4 | ||
| 34 | exited: rc:0 var:'?' OPTIND:4 | ||
diff --git a/shell/ash_test/ash-getopts/getopt_simple.tests b/shell/ash_test/ash-getopts/getopt_simple.tests new file mode 100755 index 000000000..8615ae366 --- /dev/null +++ b/shell/ash_test/ash-getopts/getopt_simple.tests | |||
| @@ -0,0 +1,75 @@ | |||
| 1 | # Simple usage cases for getopts. | ||
| 2 | # | ||
| 3 | # OPTIND is either not touched at all (first loop with getopts, | ||
| 4 | # relying on shell startup init), or getopts state is reset | ||
| 5 | # before new loop with "unset OPTIND", "OPTIND=1" or "OPTIND=0". | ||
| 6 | # | ||
| 7 | # Each option is a separate argument (no "-abc"). This conceptually | ||
| 8 | # needs only $OPTIND to hold getopts state. | ||
| 9 | # | ||
| 10 | # We check that loop does not stop on unknown option (sets "?"), | ||
| 11 | # stops on _first_ non-option argument. | ||
| 12 | |||
| 13 | echo "*** no OPTIND, optstring:'ab' args:-a -b c" | ||
| 14 | var=QWERTY | ||
| 15 | while getopts "ab" var -a -b c; do | ||
| 16 | echo "var:'$var' OPTIND:$OPTIND" | ||
| 17 | done | ||
| 18 | # unfortunately, "rc:0" is shown since while's overall exitcode is "success" | ||
| 19 | echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" | ||
| 20 | |||
| 21 | # Resetting behavior =1 | ||
| 22 | echo "*** OPTIND=1, optstring:'ab' args:-a -b c" | ||
| 23 | OPTIND=1 | ||
| 24 | while getopts "ab" var -a -b c; do | ||
| 25 | echo "var:'$var' OPTIND:$OPTIND" | ||
| 26 | done | ||
| 27 | echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" | ||
| 28 | |||
| 29 | # Resetting behavior =0 | ||
| 30 | echo "*** OPTIND=0, optstring:'ab' args:-a -b c" | ||
| 31 | OPTIND=0 | ||
| 32 | while getopts "ab" var -a -b c; do | ||
| 33 | echo "var:'$var' OPTIND:$OPTIND" | ||
| 34 | done | ||
| 35 | echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" | ||
| 36 | |||
| 37 | # Resetting behavior "unset" | ||
| 38 | echo "*** unset OPTIND, optstring:'ab' args:-a -b c" | ||
| 39 | unset OPTIND | ||
| 40 | while getopts "ab" var -a -b c; do | ||
| 41 | echo "var:'$var' OPTIND:$OPTIND" | ||
| 42 | done | ||
| 43 | echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" | ||
| 44 | |||
| 45 | # What is the final exitcode? | ||
| 46 | echo "*** optstring:'ab' args:-a -b c" | ||
| 47 | unset OPTIND | ||
| 48 | getopts "ab" var -a -b c; echo "1 rc:$? var:'$var' OPTIND:$OPTIND" | ||
| 49 | getopts "ab" var -a -b c; echo "2 rc:$? var:'$var' OPTIND:$OPTIND" | ||
| 50 | getopts "ab" var -a -b c; echo "3 rc:$? var:'$var' OPTIND:$OPTIND" | ||
| 51 | |||
| 52 | # Where would it stop? c or -c? | ||
| 53 | echo "*** unset OPTIND, optstring:'ab' args:-a c -c -b d" | ||
| 54 | unset OPTIND | ||
| 55 | while getopts "ab" var -a c -c -b d; do | ||
| 56 | echo "var:'$var' OPTIND:$OPTIND" | ||
| 57 | done | ||
| 58 | echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" | ||
| 59 | |||
| 60 | # What happens on unknown option? | ||
| 61 | echo "*** unset OPTIND, optstring:'ab' args:-a -c -b d" | ||
| 62 | unset OPTIND | ||
| 63 | while getopts "ab" var -a -c -b d; do | ||
| 64 | echo "var:'$var' OPTIND:$OPTIND" | ||
| 65 | done | ||
| 66 | echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" | ||
| 67 | |||
| 68 | # ORTERR=0 suppresses error message? | ||
| 69 | echo "*** unset OPTIND, OPTERR=0, optstring:'ab' args:-a -c -b d" | ||
| 70 | unset OPTIND | ||
| 71 | OPTERR=0 | ||
| 72 | while getopts "ab" var -a -c -b d; do | ||
| 73 | echo "var:'$var' OPTIND:$OPTIND" | ||
| 74 | done | ||
| 75 | echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" | ||
diff --git a/shell/ash_test/ash-getopts/getopt_test_libc_bug.right b/shell/ash_test/ash-getopts/getopt_test_libc_bug.right new file mode 100644 index 000000000..f6ad4602d --- /dev/null +++ b/shell/ash_test/ash-getopts/getopt_test_libc_bug.right | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | *** optstring:'ac' args:-a -b -c -d e | ||
| 2 | 1 rc:0 var:'a' OPTIND:2 OPTARG:'' | ||
| 3 | Illegal option -b | ||
| 4 | 2 rc:0 var:'?' OPTIND:3 OPTARG:'' | ||
| 5 | 3 rc:0 var:'c' OPTIND:4 OPTARG:'' | ||
| 6 | Illegal option -d | ||
| 7 | 4 rc:0 var:'?' OPTIND:5 OPTARG:'' | ||
| 8 | 5 rc:1 var:'?' OPTIND:5 OPTARG:'' | ||
| 9 | |||
| 10 | *** optstring:'ac' args:-a -b -c -d e | ||
| 11 | 1 rc:0 var:'a' OPTIND:2 OPTARG:'' | ||
| 12 | Illegal option -b | ||
| 13 | 2 rc:0 var:'?' OPTIND:3 OPTARG:'' | ||
| 14 | 3 rc:0 var:'c' OPTIND:4 OPTARG:'' | ||
| 15 | Illegal option -d | ||
| 16 | 4 rc:0 var:'?' OPTIND:5 OPTARG:'' | ||
| 17 | 5 rc:1 var:'?' OPTIND:5 OPTARG:'' | ||
| 18 | |||
| 19 | *** optstring:'ac' args:-a -b -c -d e | ||
| 20 | 1 rc:0 var:'a' OPTIND:2 OPTARG:'' | ||
| 21 | Illegal option -b | ||
| 22 | 2 rc:0 var:'?' OPTIND:3 OPTARG:'' | ||
| 23 | 3 rc:0 var:'c' OPTIND:4 OPTARG:'' | ||
| 24 | Illegal option -d | ||
| 25 | 4 rc:0 var:'?' OPTIND:5 OPTARG:'' | ||
| 26 | 5 rc:1 var:'?' OPTIND:5 OPTARG:'' | ||
diff --git a/shell/ash_test/ash-getopts/getopt_test_libc_bug.tests b/shell/ash_test/ash-getopts/getopt_test_libc_bug.tests new file mode 100755 index 000000000..fcaac81a2 --- /dev/null +++ b/shell/ash_test/ash-getopts/getopt_test_libc_bug.tests | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | # This test can fail with libc with buggy getopt() implementation. | ||
| 2 | # If getopt() wants to parse multi-option args (-abc), | ||
| 3 | # it needs to remember a position within current arg. | ||
| 4 | # | ||
| 5 | # If this position is kept as a POINTER, not an offset, | ||
| 6 | # and if argv[] ADDRESSES (not contents!) change, it blows up. | ||
| 7 | |||
| 8 | echo "*** optstring:'ac' args:-a -b -c -d e" | ||
| 9 | getopts "ac" var -a -b -c -d e; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 10 | getopts "ac" var -a -b -c -d e; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 11 | getopts "ac" var -a -b -c -d e; echo "3 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 12 | getopts "ac" var -a -b -c -d e; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 13 | getopts "ac" var -a -b -c -d e; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 14 | |||
| 15 | # Above: args are (usually) in the same locations in memory. | ||
| 16 | # Below: variable allocations change the location. | ||
| 17 | |||
| 18 | echo | ||
| 19 | echo "*** optstring:'ac' args:-a -b -c -d e" | ||
| 20 | unset OPTIND | ||
| 21 | OPTARG=QWERTY; getopts "ac" var -a -b -c -d e; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 22 | NEWVAR=NEWVAL; getopts "ac" var -a -b -c -d e; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 23 | VAR111=NEWVAL; getopts "ac" var -a -b -c -d e; echo "3 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 24 | VAR222=NEWVAL; getopts "ac" var -a -b -c -d e; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 25 | VAR333=NEWVAL; getopts "ac" var -a -b -c -d e; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 26 | |||
| 27 | # Slightly different attempts to force reallocations | ||
| 28 | |||
| 29 | echo | ||
| 30 | echo "*** optstring:'ac' args:-a -b -c -d e" | ||
| 31 | unset OPTIND | ||
| 32 | export OPTARG; getopts "ac" var -a -b -c -d e; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 33 | export NEWVAR; getopts "ac" var -a -b -c -d e; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 34 | export VAR111; getopts "ac" var -a -b -c -d e; echo "3 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 35 | export VAR222; getopts "ac" var -a -b -c -d e; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 36 | export VAR333; getopts "ac" var -a -b -c -d e; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 37 | |||
| 38 | # All copies of code above should generate identical output | ||
diff --git a/shell/ash_test/ash-parsing/groups_and_keywords2.right b/shell/ash_test/ash-parsing/groups_and_keywords2.right new file mode 100644 index 000000000..3fcbeb662 --- /dev/null +++ b/shell/ash_test/ash-parsing/groups_and_keywords2.right | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | ./groups_and_keywords2.tests: eval: line 1: syntax error: unexpected ")" | ||
| 2 | Fail:2 | ||
| 3 | ./groups_and_keywords2.tests: line 8: syntax error: unexpected ")" | ||
diff --git a/shell/ash_test/ash-parsing/groups_and_keywords2.tests b/shell/ash_test/ash-parsing/groups_and_keywords2.tests new file mode 100755 index 000000000..ab33b909f --- /dev/null +++ b/shell/ash_test/ash-parsing/groups_and_keywords2.tests | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | # This is an error | ||
| 2 | (eval 'if() { echo; }') | ||
| 3 | echo Fail:$? | ||
| 4 | # ^^^^^^ bash prints 1, but interactively it sets $? = 2 | ||
| 5 | # we print 2 | ||
| 6 | |||
| 7 | # This is an error, and it aborts in script | ||
| 8 | if() { echo; } | ||
| 9 | echo Not reached | ||
diff --git a/shell/ash_test/ash-vars/param_expand_assign.right b/shell/ash_test/ash-vars/param_expand_assign.right index 9b07d8cd4..6e9ea1379 100644 --- a/shell/ash_test/ash-vars/param_expand_assign.right +++ b/shell/ash_test/ash-vars/param_expand_assign.right | |||
| @@ -1,6 +1,6 @@ | |||
| 1 | SHELL: line 1: syntax error: bad substitution | 1 | SHELL: line 1: syntax error: bad substitution |
| 2 | SHELL: line 1: syntax error: bad substitution | 2 | SHELL: line 1: syntax error: bad substitution |
| 3 | 0 | 3 | SHELL: line 1: syntax error: bad substitution |
| 4 | 0 | 4 | 0 |
| 5 | SHELL: line 1: 1: bad variable name | 5 | SHELL: line 1: 1: bad variable name |
| 6 | SHELL: line 1: 1: bad variable name | 6 | SHELL: line 1: 1: bad variable name |
diff --git a/shell/ash_test/printenv.c b/shell/ash_test/printenv.c index c0c5e197c..c86308d3b 100644 --- a/shell/ash_test/printenv.c +++ b/shell/ash_test/printenv.c | |||
| @@ -56,7 +56,7 @@ main (argc, argv) | |||
| 56 | if (**argv == **envp && strncmp (*envp, *argv, len) == 0) | 56 | if (**argv == **envp && strncmp (*envp, *argv, len) == 0) |
| 57 | { | 57 | { |
| 58 | eval = *envp + len; | 58 | eval = *envp + len; |
| 59 | /* If the environment variable doesn't have an `=', ignore it. */ | 59 | /* If the environment variable doesn't have an '=', ignore it. */ |
| 60 | if (*eval == '=') | 60 | if (*eval == '=') |
| 61 | { | 61 | { |
| 62 | puts (eval + 1); | 62 | puts (eval + 1); |
diff --git a/shell/cttyhack.c b/shell/cttyhack.c index 9004b4763..849fe9e48 100644 --- a/shell/cttyhack.c +++ b/shell/cttyhack.c | |||
| @@ -6,7 +6,7 @@ | |||
| 6 | */ | 6 | */ |
| 7 | #include "libbb.h" | 7 | #include "libbb.h" |
| 8 | 8 | ||
| 9 | //applet:IF_CTTYHACK(APPLET(cttyhack, BB_DIR_BIN, BB_SUID_DROP)) | 9 | //applet:IF_CTTYHACK(APPLET_NOEXEC(cttyhack, cttyhack, BB_DIR_BIN, BB_SUID_DROP, cttyhack)) |
| 10 | 10 | ||
| 11 | //kbuild:lib-$(CONFIG_CTTYHACK) += cttyhack.o | 11 | //kbuild:lib-$(CONFIG_CTTYHACK) += cttyhack.o |
| 12 | 12 | ||
diff --git a/shell/hush.c b/shell/hush.c index 9f946d82f..cdc3a8618 100644 --- a/shell/hush.c +++ b/shell/hush.c | |||
| @@ -48,7 +48,7 @@ | |||
| 48 | * tilde expansion | 48 | * tilde expansion |
| 49 | * aliases | 49 | * aliases |
| 50 | * builtins mandated by standards we don't support: | 50 | * builtins mandated by standards we don't support: |
| 51 | * [un]alias, command, fc, getopts, times: | 51 | * [un]alias, command, fc: |
| 52 | * command -v CMD: print "/path/to/CMD" | 52 | * command -v CMD: print "/path/to/CMD" |
| 53 | * prints "CMD" for builtins | 53 | * prints "CMD" for builtins |
| 54 | * prints "alias ALIAS='EXPANSION'" for aliases | 54 | * prints "alias ALIAS='EXPANSION'" for aliases |
| @@ -58,8 +58,6 @@ | |||
| 58 | * (can use this to override standalone shell as well) | 58 | * (can use this to override standalone shell as well) |
| 59 | * -p: use default $PATH | 59 | * -p: use default $PATH |
| 60 | * command BLTIN: disables special-ness (e.g. errors do not abort) | 60 | * command BLTIN: disables special-ness (e.g. errors do not abort) |
| 61 | * getopts: getopt() for shells | ||
| 62 | * times: print getrusage(SELF/CHILDREN).ru_utime/ru_stime | ||
| 63 | * fc -l[nr] [BEG] [END]: list range of commands in history | 61 | * fc -l[nr] [BEG] [END]: list range of commands in history |
| 64 | * fc [-e EDITOR] [BEG] [END]: edit/rerun range of commands | 62 | * fc [-e EDITOR] [BEG] [END]: edit/rerun range of commands |
| 65 | * fc -s [PAT=REP] [CMD]: rerun CMD, replacing PAT with REP | 63 | * fc -s [PAT=REP] [CMD]: rerun CMD, replacing PAT with REP |
| @@ -265,6 +263,11 @@ | |||
| 265 | //config: default y | 263 | //config: default y |
| 266 | //config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH | 264 | //config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH |
| 267 | //config: | 265 | //config: |
| 266 | //config:config HUSH_TIMES | ||
| 267 | //config: bool "times builtin" | ||
| 268 | //config: default y | ||
| 269 | //config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH | ||
| 270 | //config: | ||
| 268 | //config:config HUSH_READ | 271 | //config:config HUSH_READ |
| 269 | //config: bool "read builtin" | 272 | //config: bool "read builtin" |
| 270 | //config: default y | 273 | //config: default y |
| @@ -290,6 +293,11 @@ | |||
| 290 | //config: default y | 293 | //config: default y |
| 291 | //config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH | 294 | //config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH |
| 292 | //config: | 295 | //config: |
| 296 | //config:config HUSH_GETOPTS | ||
| 297 | //config: bool "getopts builtin" | ||
| 298 | //config: default y | ||
| 299 | //config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH | ||
| 300 | //config: | ||
| 293 | //config:config HUSH_MEMLEAK | 301 | //config:config HUSH_MEMLEAK |
| 294 | //config: bool "memleak builtin (debugging)" | 302 | //config: bool "memleak builtin (debugging)" |
| 295 | //config: default n | 303 | //config: default n |
| @@ -325,6 +333,7 @@ | |||
| 325 | #if ENABLE_HUSH_CASE | 333 | #if ENABLE_HUSH_CASE |
| 326 | # include <fnmatch.h> | 334 | # include <fnmatch.h> |
| 327 | #endif | 335 | #endif |
| 336 | #include <sys/times.h> | ||
| 328 | #include <sys/utsname.h> /* for setting $HOSTNAME */ | 337 | #include <sys/utsname.h> /* for setting $HOSTNAME */ |
| 329 | 338 | ||
| 330 | #include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */ | 339 | #include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */ |
| @@ -352,6 +361,7 @@ | |||
| 352 | #define BASH_SOURCE ENABLE_HUSH_BASH_COMPAT | 361 | #define BASH_SOURCE ENABLE_HUSH_BASH_COMPAT |
| 353 | #define BASH_HOSTNAME_VAR ENABLE_HUSH_BASH_COMPAT | 362 | #define BASH_HOSTNAME_VAR ENABLE_HUSH_BASH_COMPAT |
| 354 | #define BASH_TEST2 (ENABLE_HUSH_BASH_COMPAT && ENABLE_HUSH_TEST) | 363 | #define BASH_TEST2 (ENABLE_HUSH_BASH_COMPAT && ENABLE_HUSH_TEST) |
| 364 | #define BASH_READ_D ENABLE_HUSH_BASH_COMPAT | ||
| 355 | 365 | ||
| 356 | 366 | ||
| 357 | /* Build knobs */ | 367 | /* Build knobs */ |
| @@ -977,6 +987,9 @@ static int builtin_readonly(char **argv) FAST_FUNC; | |||
| 977 | static int builtin_fg_bg(char **argv) FAST_FUNC; | 987 | static int builtin_fg_bg(char **argv) FAST_FUNC; |
| 978 | static int builtin_jobs(char **argv) FAST_FUNC; | 988 | static int builtin_jobs(char **argv) FAST_FUNC; |
| 979 | #endif | 989 | #endif |
| 990 | #if ENABLE_HUSH_GETOPTS | ||
| 991 | static int builtin_getopts(char **argv) FAST_FUNC; | ||
| 992 | #endif | ||
| 980 | #if ENABLE_HUSH_HELP | 993 | #if ENABLE_HUSH_HELP |
| 981 | static int builtin_help(char **argv) FAST_FUNC; | 994 | static int builtin_help(char **argv) FAST_FUNC; |
| 982 | #endif | 995 | #endif |
| @@ -1010,6 +1023,9 @@ static int builtin_trap(char **argv) FAST_FUNC; | |||
| 1010 | #if ENABLE_HUSH_TYPE | 1023 | #if ENABLE_HUSH_TYPE |
| 1011 | static int builtin_type(char **argv) FAST_FUNC; | 1024 | static int builtin_type(char **argv) FAST_FUNC; |
| 1012 | #endif | 1025 | #endif |
| 1026 | #if ENABLE_HUSH_TIMES | ||
| 1027 | static int builtin_times(char **argv) FAST_FUNC; | ||
| 1028 | #endif | ||
| 1013 | static int builtin_true(char **argv) FAST_FUNC; | 1029 | static int builtin_true(char **argv) FAST_FUNC; |
| 1014 | #if ENABLE_HUSH_UMASK | 1030 | #if ENABLE_HUSH_UMASK |
| 1015 | static int builtin_umask(char **argv) FAST_FUNC; | 1031 | static int builtin_umask(char **argv) FAST_FUNC; |
| @@ -1070,6 +1086,9 @@ static const struct built_in_command bltins1[] = { | |||
| 1070 | #if ENABLE_HUSH_JOB | 1086 | #if ENABLE_HUSH_JOB |
| 1071 | BLTIN("fg" , builtin_fg_bg , "Bring job to foreground"), | 1087 | BLTIN("fg" , builtin_fg_bg , "Bring job to foreground"), |
| 1072 | #endif | 1088 | #endif |
| 1089 | #if ENABLE_HUSH_GETOPTS | ||
| 1090 | BLTIN("getopts" , builtin_getopts , NULL), | ||
| 1091 | #endif | ||
| 1073 | #if ENABLE_HUSH_HELP | 1092 | #if ENABLE_HUSH_HELP |
| 1074 | BLTIN("help" , builtin_help , NULL), | 1093 | BLTIN("help" , builtin_help , NULL), |
| 1075 | #endif | 1094 | #endif |
| @@ -1104,6 +1123,9 @@ static const struct built_in_command bltins1[] = { | |||
| 1104 | #if BASH_SOURCE | 1123 | #if BASH_SOURCE |
| 1105 | BLTIN("source" , builtin_source , NULL), | 1124 | BLTIN("source" , builtin_source , NULL), |
| 1106 | #endif | 1125 | #endif |
| 1126 | #if ENABLE_HUSH_TIMES | ||
| 1127 | BLTIN("times" , builtin_times , NULL), | ||
| 1128 | #endif | ||
| 1107 | #if ENABLE_HUSH_TRAP | 1129 | #if ENABLE_HUSH_TRAP |
| 1108 | BLTIN("trap" , builtin_trap , "Trap signals"), | 1130 | BLTIN("trap" , builtin_trap , "Trap signals"), |
| 1109 | #endif | 1131 | #endif |
| @@ -1272,7 +1294,7 @@ static void xxfree(void *ptr) | |||
| 1272 | * HUSH_DEBUG >= 2 prints line number in this file where it was detected. | 1294 | * HUSH_DEBUG >= 2 prints line number in this file where it was detected. |
| 1273 | */ | 1295 | */ |
| 1274 | #if HUSH_DEBUG < 2 | 1296 | #if HUSH_DEBUG < 2 |
| 1275 | # define die_if_script(lineno, ...) die_if_script(__VA_ARGS__) | 1297 | # define msg_and_die_if_script(lineno, ...) msg_and_die_if_script(__VA_ARGS__) |
| 1276 | # define syntax_error(lineno, msg) syntax_error(msg) | 1298 | # define syntax_error(lineno, msg) syntax_error(msg) |
| 1277 | # define syntax_error_at(lineno, msg) syntax_error_at(msg) | 1299 | # define syntax_error_at(lineno, msg) syntax_error_at(msg) |
| 1278 | # define syntax_error_unterm_ch(lineno, ch) syntax_error_unterm_ch(ch) | 1300 | # define syntax_error_unterm_ch(lineno, ch) syntax_error_unterm_ch(ch) |
| @@ -1280,7 +1302,16 @@ static void xxfree(void *ptr) | |||
| 1280 | # define syntax_error_unexpected_ch(lineno, ch) syntax_error_unexpected_ch(ch) | 1302 | # define syntax_error_unexpected_ch(lineno, ch) syntax_error_unexpected_ch(ch) |
| 1281 | #endif | 1303 | #endif |
| 1282 | 1304 | ||
| 1283 | static void die_if_script(unsigned lineno, const char *fmt, ...) | 1305 | static void die_if_script(void) |
| 1306 | { | ||
| 1307 | if (!G_interactive_fd) { | ||
| 1308 | if (G.last_exitcode) /* sometines it's 2, not 1 (bash compat) */ | ||
| 1309 | xfunc_error_retval = G.last_exitcode; | ||
| 1310 | xfunc_die(); | ||
| 1311 | } | ||
| 1312 | } | ||
| 1313 | |||
| 1314 | static void msg_and_die_if_script(unsigned lineno, const char *fmt, ...) | ||
| 1284 | { | 1315 | { |
| 1285 | va_list p; | 1316 | va_list p; |
| 1286 | 1317 | ||
| @@ -1290,8 +1321,7 @@ static void die_if_script(unsigned lineno, const char *fmt, ...) | |||
| 1290 | va_start(p, fmt); | 1321 | va_start(p, fmt); |
| 1291 | bb_verror_msg(fmt, p, NULL); | 1322 | bb_verror_msg(fmt, p, NULL); |
| 1292 | va_end(p); | 1323 | va_end(p); |
| 1293 | if (!G_interactive_fd) | 1324 | die_if_script(); |
| 1294 | xfunc_die(); | ||
| 1295 | } | 1325 | } |
| 1296 | 1326 | ||
| 1297 | static void syntax_error(unsigned lineno UNUSED_PARAM, const char *msg) | 1327 | static void syntax_error(unsigned lineno UNUSED_PARAM, const char *msg) |
| @@ -1300,16 +1330,20 @@ static void syntax_error(unsigned lineno UNUSED_PARAM, const char *msg) | |||
| 1300 | bb_error_msg("syntax error: %s", msg); | 1330 | bb_error_msg("syntax error: %s", msg); |
| 1301 | else | 1331 | else |
| 1302 | bb_error_msg("syntax error"); | 1332 | bb_error_msg("syntax error"); |
| 1333 | die_if_script(); | ||
| 1303 | } | 1334 | } |
| 1304 | 1335 | ||
| 1305 | static void syntax_error_at(unsigned lineno UNUSED_PARAM, const char *msg) | 1336 | static void syntax_error_at(unsigned lineno UNUSED_PARAM, const char *msg) |
| 1306 | { | 1337 | { |
| 1307 | bb_error_msg("syntax error at '%s'", msg); | 1338 | bb_error_msg("syntax error at '%s'", msg); |
| 1339 | die_if_script(); | ||
| 1308 | } | 1340 | } |
| 1309 | 1341 | ||
| 1310 | static void syntax_error_unterm_str(unsigned lineno UNUSED_PARAM, const char *s) | 1342 | static void syntax_error_unterm_str(unsigned lineno UNUSED_PARAM, const char *s) |
| 1311 | { | 1343 | { |
| 1312 | bb_error_msg("syntax error: unterminated %s", s); | 1344 | bb_error_msg("syntax error: unterminated %s", s); |
| 1345 | //? source4.tests fails: in bash, echo ${^} in script does not terminate the script | ||
| 1346 | // die_if_script(); | ||
| 1313 | } | 1347 | } |
| 1314 | 1348 | ||
| 1315 | static void syntax_error_unterm_ch(unsigned lineno, char ch) | 1349 | static void syntax_error_unterm_ch(unsigned lineno, char ch) |
| @@ -1327,17 +1361,18 @@ static void syntax_error_unexpected_ch(unsigned lineno UNUSED_PARAM, int ch) | |||
| 1327 | bb_error_msg("hush.c:%u", lineno); | 1361 | bb_error_msg("hush.c:%u", lineno); |
| 1328 | #endif | 1362 | #endif |
| 1329 | bb_error_msg("syntax error: unexpected %s", ch == EOF ? "EOF" : msg); | 1363 | bb_error_msg("syntax error: unexpected %s", ch == EOF ? "EOF" : msg); |
| 1364 | die_if_script(); | ||
| 1330 | } | 1365 | } |
| 1331 | 1366 | ||
| 1332 | #if HUSH_DEBUG < 2 | 1367 | #if HUSH_DEBUG < 2 |
| 1333 | # undef die_if_script | 1368 | # undef msg_and_die_if_script |
| 1334 | # undef syntax_error | 1369 | # undef syntax_error |
| 1335 | # undef syntax_error_at | 1370 | # undef syntax_error_at |
| 1336 | # undef syntax_error_unterm_ch | 1371 | # undef syntax_error_unterm_ch |
| 1337 | # undef syntax_error_unterm_str | 1372 | # undef syntax_error_unterm_str |
| 1338 | # undef syntax_error_unexpected_ch | 1373 | # undef syntax_error_unexpected_ch |
| 1339 | #else | 1374 | #else |
| 1340 | # define die_if_script(...) die_if_script(__LINE__, __VA_ARGS__) | 1375 | # define msg_and_die_if_script(...) msg_and_die_if_script(__LINE__, __VA_ARGS__) |
| 1341 | # define syntax_error(msg) syntax_error(__LINE__, msg) | 1376 | # define syntax_error(msg) syntax_error(__LINE__, msg) |
| 1342 | # define syntax_error_at(msg) syntax_error_at(__LINE__, msg) | 1377 | # define syntax_error_at(msg) syntax_error_at(__LINE__, msg) |
| 1343 | # define syntax_error_unterm_ch(ch) syntax_error_unterm_ch(__LINE__, ch) | 1378 | # define syntax_error_unterm_ch(ch) syntax_error_unterm_ch(__LINE__, ch) |
| @@ -1800,7 +1835,7 @@ static void restore_ttypgrp_and__exit(void) | |||
| 1800 | * echo END_OF_SCRIPT | 1835 | * echo END_OF_SCRIPT |
| 1801 | * lseeks fd in input FILE object from EOF to "e" in "echo END_OF_SCRIPT". | 1836 | * lseeks fd in input FILE object from EOF to "e" in "echo END_OF_SCRIPT". |
| 1802 | * This makes "echo END_OF_SCRIPT" executed twice. | 1837 | * This makes "echo END_OF_SCRIPT" executed twice. |
| 1803 | * Similar problems can be seen with die_if_script() -> xfunc_die() | 1838 | * Similar problems can be seen with msg_and_die_if_script() -> xfunc_die() |
| 1804 | * and in `cmd` handling. | 1839 | * and in `cmd` handling. |
| 1805 | * If set as die_func(), this makes xfunc_die() exit via _exit(), not exit(): | 1840 | * If set as die_func(), this makes xfunc_die() exit via _exit(), not exit(): |
| 1806 | */ | 1841 | */ |
| @@ -1966,6 +2001,9 @@ static int check_and_run_traps(void) | |||
| 1966 | break; | 2001 | break; |
| 1967 | #if ENABLE_HUSH_JOB | 2002 | #if ENABLE_HUSH_JOB |
| 1968 | case SIGHUP: { | 2003 | case SIGHUP: { |
| 2004 | //TODO: why are we doing this? ash and dash don't do this, | ||
| 2005 | //they have no handler for SIGHUP at all, | ||
| 2006 | //they rely on kernel to send SIGHUP+SIGCONT to orphaned process groups | ||
| 1969 | struct pipe *job; | 2007 | struct pipe *job; |
| 1970 | debug_printf_exec("%s: sig:%d default SIGHUP handler\n", __func__, sig); | 2008 | debug_printf_exec("%s: sig:%d default SIGHUP handler\n", __func__, sig); |
| 1971 | /* bash is observed to signal whole process groups, | 2009 | /* bash is observed to signal whole process groups, |
| @@ -2411,18 +2449,17 @@ static int get_user_input(struct in_str *i) | |||
| 2411 | /* buglet: SIGINT will not make new prompt to appear _at once_, | 2449 | /* buglet: SIGINT will not make new prompt to appear _at once_, |
| 2412 | * only after <Enter>. (^C works immediately) */ | 2450 | * only after <Enter>. (^C works immediately) */ |
| 2413 | r = read_line_input(G.line_input_state, prompt_str, | 2451 | r = read_line_input(G.line_input_state, prompt_str, |
| 2414 | G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1, | 2452 | G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1 |
| 2415 | /*timeout*/ -1 | ||
| 2416 | ); | 2453 | ); |
| 2417 | /* read_line_input intercepts ^C, "convert" it to SIGINT */ | 2454 | /* read_line_input intercepts ^C, "convert" it to SIGINT */ |
| 2418 | if (r == 0) { | 2455 | if (r == 0) |
| 2419 | write(STDOUT_FILENO, "^C", 2); | ||
| 2420 | raise(SIGINT); | 2456 | raise(SIGINT); |
| 2421 | } | ||
| 2422 | check_and_run_traps(); | 2457 | check_and_run_traps(); |
| 2423 | if (r != 0 && !G.flag_SIGINT) | 2458 | if (r != 0 && !G.flag_SIGINT) |
| 2424 | break; | 2459 | break; |
| 2425 | /* ^C or SIGINT: repeat */ | 2460 | /* ^C or SIGINT: repeat */ |
| 2461 | /* bash prints ^C even on real SIGINT (non-kbd generated) */ | ||
| 2462 | write(STDOUT_FILENO, "^C", 2); | ||
| 2426 | G.last_exitcode = 128 + SIGINT; | 2463 | G.last_exitcode = 128 + SIGINT; |
| 2427 | } | 2464 | } |
| 2428 | if (r < 0) { | 2465 | if (r < 0) { |
| @@ -3384,7 +3421,7 @@ static int done_command(struct parse_context *ctx) | |||
| 3384 | #if 0 /* Instead we emit error message at run time */ | 3421 | #if 0 /* Instead we emit error message at run time */ |
| 3385 | if (ctx->pending_redirect) { | 3422 | if (ctx->pending_redirect) { |
| 3386 | /* For example, "cmd >" (no filename to redirect to) */ | 3423 | /* For example, "cmd >" (no filename to redirect to) */ |
| 3387 | die_if_script("syntax error: %s", "invalid redirect"); | 3424 | syntax_error("invalid redirect"); |
| 3388 | ctx->pending_redirect = NULL; | 3425 | ctx->pending_redirect = NULL; |
| 3389 | } | 3426 | } |
| 3390 | #endif | 3427 | #endif |
| @@ -3950,7 +3987,7 @@ static int parse_redirect(struct parse_context *ctx, | |||
| 3950 | #if 0 /* Instead we emit error message at run time */ | 3987 | #if 0 /* Instead we emit error message at run time */ |
| 3951 | if (ctx->pending_redirect) { | 3988 | if (ctx->pending_redirect) { |
| 3952 | /* For example, "cmd > <file" */ | 3989 | /* For example, "cmd > <file" */ |
| 3953 | die_if_script("syntax error: %s", "invalid redirect"); | 3990 | syntax_error("invalid redirect"); |
| 3954 | } | 3991 | } |
| 3955 | #endif | 3992 | #endif |
| 3956 | /* Set ctx->pending_redirect, so we know what to do at the | 3993 | /* Set ctx->pending_redirect, so we know what to do at the |
| @@ -5022,10 +5059,16 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5022 | else | 5059 | else |
| 5023 | o_free_unsafe(&ctx.as_string); | 5060 | o_free_unsafe(&ctx.as_string); |
| 5024 | #endif | 5061 | #endif |
| 5025 | debug_leave(); | 5062 | if (ch != ';' && IS_NULL_PIPE(ctx.list_head)) { |
| 5063 | /* Example: bare "{ }", "()" */ | ||
| 5064 | G.last_exitcode = 2; /* bash compat */ | ||
| 5065 | syntax_error_unexpected_ch(ch); | ||
| 5066 | goto parse_error2; | ||
| 5067 | } | ||
| 5026 | debug_printf_parse("parse_stream return %p: " | 5068 | debug_printf_parse("parse_stream return %p: " |
| 5027 | "end_trigger char found\n", | 5069 | "end_trigger char found\n", |
| 5028 | ctx.list_head); | 5070 | ctx.list_head); |
| 5071 | debug_leave(); | ||
| 5029 | return ctx.list_head; | 5072 | return ctx.list_head; |
| 5030 | } | 5073 | } |
| 5031 | } | 5074 | } |
| @@ -5283,8 +5326,8 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5283 | /* proper use of this character is caught by end_trigger: | 5326 | /* proper use of this character is caught by end_trigger: |
| 5284 | * if we see {, we call parse_group(..., end_trigger='}') | 5327 | * if we see {, we call parse_group(..., end_trigger='}') |
| 5285 | * and it will match } earlier (not here). */ | 5328 | * and it will match } earlier (not here). */ |
| 5286 | syntax_error_unexpected_ch(ch); | ||
| 5287 | G.last_exitcode = 2; | 5329 | G.last_exitcode = 2; |
| 5330 | syntax_error_unexpected_ch(ch); | ||
| 5288 | goto parse_error2; | 5331 | goto parse_error2; |
| 5289 | default: | 5332 | default: |
| 5290 | if (HUSH_DEBUG) | 5333 | if (HUSH_DEBUG) |
| @@ -5514,7 +5557,7 @@ static arith_t expand_and_evaluate_arith(const char *arg, const char **errmsg_p) | |||
| 5514 | if (errmsg_p) | 5557 | if (errmsg_p) |
| 5515 | *errmsg_p = math_state.errmsg; | 5558 | *errmsg_p = math_state.errmsg; |
| 5516 | if (math_state.errmsg) | 5559 | if (math_state.errmsg) |
| 5517 | die_if_script(math_state.errmsg); | 5560 | msg_and_die_if_script(math_state.errmsg); |
| 5518 | return res; | 5561 | return res; |
| 5519 | } | 5562 | } |
| 5520 | #endif | 5563 | #endif |
| @@ -5781,7 +5824,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha | |||
| 5781 | /* in bash, len=-n means strlen()-n */ | 5824 | /* in bash, len=-n means strlen()-n */ |
| 5782 | len = (arith_t)strlen(val) - beg + len; | 5825 | len = (arith_t)strlen(val) - beg + len; |
| 5783 | if (len < 0) /* bash compat */ | 5826 | if (len < 0) /* bash compat */ |
| 5784 | die_if_script("%s: substring expression < 0", var); | 5827 | msg_and_die_if_script("%s: substring expression < 0", var); |
| 5785 | } | 5828 | } |
| 5786 | if (len <= 0 || !val || beg >= strlen(val)) { | 5829 | if (len <= 0 || !val || beg >= strlen(val)) { |
| 5787 | arith_err: | 5830 | arith_err: |
| @@ -5795,7 +5838,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha | |||
| 5795 | } | 5838 | } |
| 5796 | debug_printf_varexp("val:'%s'\n", val); | 5839 | debug_printf_varexp("val:'%s'\n", val); |
| 5797 | #else /* not (HUSH_SUBSTR_EXPANSION && FEATURE_SH_MATH) */ | 5840 | #else /* not (HUSH_SUBSTR_EXPANSION && FEATURE_SH_MATH) */ |
| 5798 | die_if_script("malformed ${%s:...}", var); | 5841 | msg_and_die_if_script("malformed ${%s:...}", var); |
| 5799 | val = NULL; | 5842 | val = NULL; |
| 5800 | #endif | 5843 | #endif |
| 5801 | } else { /* one of "-=+?" */ | 5844 | } else { /* one of "-=+?" */ |
| @@ -5832,7 +5875,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha | |||
| 5832 | exp_word = to_be_freed; | 5875 | exp_word = to_be_freed; |
| 5833 | if (exp_op == '?') { | 5876 | if (exp_op == '?') { |
| 5834 | /* mimic bash message */ | 5877 | /* mimic bash message */ |
| 5835 | die_if_script("%s: %s", | 5878 | msg_and_die_if_script("%s: %s", |
| 5836 | var, | 5879 | var, |
| 5837 | exp_word[0] | 5880 | exp_word[0] |
| 5838 | ? exp_word | 5881 | ? exp_word |
| @@ -5849,7 +5892,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha | |||
| 5849 | /* ${var=[word]} or ${var:=[word]} */ | 5892 | /* ${var=[word]} or ${var:=[word]} */ |
| 5850 | if (isdigit(var[0]) || var[0] == '#') { | 5893 | if (isdigit(var[0]) || var[0] == '#') { |
| 5851 | /* mimic bash message */ | 5894 | /* mimic bash message */ |
| 5852 | die_if_script("$%s: cannot assign in this way", var); | 5895 | msg_and_die_if_script("$%s: cannot assign in this way", var); |
| 5853 | val = NULL; | 5896 | val = NULL; |
| 5854 | } else { | 5897 | } else { |
| 5855 | char *new_var = xasprintf("%s=%s", var, val); | 5898 | char *new_var = xasprintf("%s=%s", var, val); |
| @@ -6698,7 +6741,8 @@ static struct squirrel *add_squirrel(struct squirrel *sq, int fd, int avoid_fd) | |||
| 6698 | int moved_to; | 6741 | int moved_to; |
| 6699 | int i; | 6742 | int i; |
| 6700 | 6743 | ||
| 6701 | if (sq) for (i = 0; sq[i].orig_fd >= 0; i++) { | 6744 | i = 0; |
| 6745 | if (sq) for (; sq[i].orig_fd >= 0; i++) { | ||
| 6702 | /* If we collide with an already moved fd... */ | 6746 | /* If we collide with an already moved fd... */ |
| 6703 | if (fd == sq[i].moved_to) { | 6747 | if (fd == sq[i].moved_to) { |
| 6704 | sq[i].moved_to = fcntl_F_DUPFD(sq[i].moved_to, avoid_fd); | 6748 | sq[i].moved_to = fcntl_F_DUPFD(sq[i].moved_to, avoid_fd); |
| @@ -6726,7 +6770,8 @@ static struct squirrel *add_squirrel_closed(struct squirrel *sq, int fd) | |||
| 6726 | { | 6770 | { |
| 6727 | int i; | 6771 | int i; |
| 6728 | 6772 | ||
| 6729 | if (sq) for (i = 0; sq[i].orig_fd >= 0; i++) { | 6773 | i = 0; |
| 6774 | if (sq) for (; sq[i].orig_fd >= 0; i++) { | ||
| 6730 | /* If we collide with an already moved fd... */ | 6775 | /* If we collide with an already moved fd... */ |
| 6731 | if (fd == sq[i].orig_fd) { | 6776 | if (fd == sq[i].orig_fd) { |
| 6732 | /* Examples: | 6777 | /* Examples: |
| @@ -6863,7 +6908,7 @@ static int setup_redirects(struct command *prog, struct squirrel **sqp) | |||
| 6863 | * "cmd >" (no filename) | 6908 | * "cmd >" (no filename) |
| 6864 | * "cmd > <file" (2nd redirect starts too early) | 6909 | * "cmd > <file" (2nd redirect starts too early) |
| 6865 | */ | 6910 | */ |
| 6866 | die_if_script("syntax error: %s", "invalid redirect"); | 6911 | syntax_error("invalid redirect"); |
| 6867 | continue; | 6912 | continue; |
| 6868 | } | 6913 | } |
| 6869 | mode = redir_table[redir->rd_type].mode; | 6914 | mode = redir_table[redir->rd_type].mode; |
| @@ -7363,8 +7408,10 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, | |||
| 7363 | */ | 7408 | */ |
| 7364 | close_saved_fds_and_FILE_fds(); | 7409 | close_saved_fds_and_FILE_fds(); |
| 7365 | //FIXME: should also close saved redir fds | 7410 | //FIXME: should also close saved redir fds |
| 7411 | /* Without this, "rm -i FILE" can't be ^C'ed: */ | ||
| 7412 | switch_off_special_sigs(G.special_sig_mask); | ||
| 7366 | debug_printf_exec("running applet '%s'\n", argv[0]); | 7413 | debug_printf_exec("running applet '%s'\n", argv[0]); |
| 7367 | run_applet_no_and_exit(a, argv[0], argv); | 7414 | run_noexec_applet_and_exit(a, argv[0], argv); |
| 7368 | } | 7415 | } |
| 7369 | # endif | 7416 | # endif |
| 7370 | /* Re-exec ourselves */ | 7417 | /* Re-exec ourselves */ |
| @@ -8045,6 +8092,24 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
| 8045 | add_vars(old_vars); | 8092 | add_vars(old_vars); |
| 8046 | /* clean_up_and_ret0: */ | 8093 | /* clean_up_and_ret0: */ |
| 8047 | restore_redirects(squirrel); | 8094 | restore_redirects(squirrel); |
| 8095 | /* | ||
| 8096 | * Try "usleep 99999999" + ^C + "echo $?" | ||
| 8097 | * with FEATURE_SH_NOFORK=y. | ||
| 8098 | */ | ||
| 8099 | if (!funcp) { | ||
| 8100 | /* It was builtin or nofork. | ||
| 8101 | * if this would be a real fork/execed program, | ||
| 8102 | * it should have died if a fatal sig was received. | ||
| 8103 | * But OTOH, there was no separate process, | ||
| 8104 | * the sig was sent to _shell_, not to non-existing | ||
| 8105 | * child. | ||
| 8106 | * Let's just handle ^C only, this one is obvious: | ||
| 8107 | * we aren't ok with exitcode 0 when ^C was pressed | ||
| 8108 | * during builtin/nofork. | ||
| 8109 | */ | ||
| 8110 | if (sigismember(&G.pending_set, SIGINT)) | ||
| 8111 | rcode = 128 + SIGINT; | ||
| 8112 | } | ||
| 8048 | clean_up_and_ret1: | 8113 | clean_up_and_ret1: |
| 8049 | free(argv_expanded); | 8114 | free(argv_expanded); |
| 8050 | IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) | 8115 | IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) |
| @@ -8060,6 +8125,14 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
| 8060 | if (rcode == 0) { | 8125 | if (rcode == 0) { |
| 8061 | debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", | 8126 | debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", |
| 8062 | argv_expanded[0], argv_expanded[1]); | 8127 | argv_expanded[0], argv_expanded[1]); |
| 8128 | /* | ||
| 8129 | * Note: signals (^C) can't interrupt here. | ||
| 8130 | * We remember them and they will be acted upon | ||
| 8131 | * after applet returns. | ||
| 8132 | * This makes applets which can run for a long time | ||
| 8133 | * and/or wait for user input ineligible for NOFORK: | ||
| 8134 | * for example, "yes" or "rm" (rm -i waits for input). | ||
| 8135 | */ | ||
| 8063 | rcode = run_nofork_applet(n, argv_expanded); | 8136 | rcode = run_nofork_applet(n, argv_expanded); |
| 8064 | } | 8137 | } |
| 8065 | goto clean_up_and_ret; | 8138 | goto clean_up_and_ret; |
| @@ -8491,7 +8564,7 @@ static int run_list(struct pipe *pi) | |||
| 8491 | G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid; | 8564 | G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid; |
| 8492 | G.last_bg_pid_exitcode = 0; | 8565 | G.last_bg_pid_exitcode = 0; |
| 8493 | debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n"); | 8566 | debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n"); |
| 8494 | /* Check pi->pi_inverted? "! sleep 1 & echo $?": bash says 1. dash and ash says 0 */ | 8567 | /* Check pi->pi_inverted? "! sleep 1 & echo $?": bash says 1. dash and ash say 0 */ |
| 8495 | rcode = EXIT_SUCCESS; | 8568 | rcode = EXIT_SUCCESS; |
| 8496 | goto check_traps; | 8569 | goto check_traps; |
| 8497 | } else { | 8570 | } else { |
| @@ -8600,6 +8673,10 @@ static void install_sighandlers(unsigned mask) | |||
| 8600 | */ | 8673 | */ |
| 8601 | if (sig == SIGCHLD) | 8674 | if (sig == SIGCHLD) |
| 8602 | continue; | 8675 | continue; |
| 8676 | /* bash re-enables SIGHUP which is SIG_IGNed on entry. | ||
| 8677 | * Try: "trap '' HUP; bash; echo RET" and type "kill -HUP $$" | ||
| 8678 | */ | ||
| 8679 | //if (sig == SIGHUP) continue; - TODO? | ||
| 8603 | if (old_handler == SIG_IGN) { | 8680 | if (old_handler == SIG_IGN) { |
| 8604 | /* oops... restore back to IGN, and record this fact */ | 8681 | /* oops... restore back to IGN, and record this fact */ |
| 8605 | install_sighandler(sig, old_handler); | 8682 | install_sighandler(sig, old_handler); |
| @@ -9381,13 +9458,20 @@ static int FAST_FUNC builtin_read(char **argv) | |||
| 9381 | char *opt_p = NULL; | 9458 | char *opt_p = NULL; |
| 9382 | char *opt_t = NULL; | 9459 | char *opt_t = NULL; |
| 9383 | char *opt_u = NULL; | 9460 | char *opt_u = NULL; |
| 9461 | char *opt_d = NULL; /* optimized out if !BASH */ | ||
| 9384 | const char *ifs; | 9462 | const char *ifs; |
| 9385 | int read_flags; | 9463 | int read_flags; |
| 9386 | 9464 | ||
| 9387 | /* "!": do not abort on errors. | 9465 | /* "!": do not abort on errors. |
| 9388 | * Option string must start with "sr" to match BUILTIN_READ_xxx | 9466 | * Option string must start with "sr" to match BUILTIN_READ_xxx |
| 9389 | */ | 9467 | */ |
| 9390 | read_flags = getopt32(argv, "!srn:p:t:u:", &opt_n, &opt_p, &opt_t, &opt_u); | 9468 | read_flags = getopt32(argv, |
| 9469 | #if BASH_READ_D | ||
| 9470 | "!srn:p:t:u:d:", &opt_n, &opt_p, &opt_t, &opt_u, &opt_d | ||
| 9471 | #else | ||
| 9472 | "!srn:p:t:u:", &opt_n, &opt_p, &opt_t, &opt_u | ||
| 9473 | #endif | ||
| 9474 | ); | ||
| 9391 | if (read_flags == (uint32_t)-1) | 9475 | if (read_flags == (uint32_t)-1) |
| 9392 | return EXIT_FAILURE; | 9476 | return EXIT_FAILURE; |
| 9393 | argv += optind; | 9477 | argv += optind; |
| @@ -9401,7 +9485,8 @@ static int FAST_FUNC builtin_read(char **argv) | |||
| 9401 | opt_n, | 9485 | opt_n, |
| 9402 | opt_p, | 9486 | opt_p, |
| 9403 | opt_t, | 9487 | opt_t, |
| 9404 | opt_u | 9488 | opt_u, |
| 9489 | opt_d | ||
| 9405 | ); | 9490 | ); |
| 9406 | 9491 | ||
| 9407 | if ((uintptr_t)r == 1 && errno == EINTR) { | 9492 | if ((uintptr_t)r == 1 && errno == EINTR) { |
| @@ -9786,6 +9871,93 @@ static int FAST_FUNC builtin_shift(char **argv) | |||
| 9786 | return EXIT_FAILURE; | 9871 | return EXIT_FAILURE; |
| 9787 | } | 9872 | } |
| 9788 | 9873 | ||
| 9874 | #if ENABLE_HUSH_GETOPTS | ||
| 9875 | static int FAST_FUNC builtin_getopts(char **argv) | ||
| 9876 | { | ||
| 9877 | /* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/getopts.html | ||
| 9878 | |||
| 9879 | TODO: | ||
| 9880 | If a required argument is not found, and getopts is not silent, | ||
| 9881 | a question mark (?) is placed in VAR, OPTARG is unset, and a | ||
| 9882 | diagnostic message is printed. If getopts is silent, then a | ||
| 9883 | colon (:) is placed in VAR and OPTARG is set to the option | ||
| 9884 | character found. | ||
| 9885 | |||
| 9886 | Test that VAR is a valid variable name? | ||
| 9887 | |||
| 9888 | "Whenever the shell is invoked, OPTIND shall be initialized to 1" | ||
| 9889 | */ | ||
| 9890 | char cbuf[2]; | ||
| 9891 | const char *cp, *optstring, *var; | ||
| 9892 | int c, exitcode; | ||
| 9893 | |||
| 9894 | optstring = *++argv; | ||
| 9895 | if (!optstring || !(var = *++argv)) { | ||
| 9896 | bb_error_msg("usage: getopts OPTSTRING VAR [ARGS]"); | ||
| 9897 | return EXIT_FAILURE; | ||
| 9898 | } | ||
| 9899 | |||
| 9900 | c = 0; | ||
| 9901 | if (optstring[0] != ':') { | ||
| 9902 | cp = get_local_var_value("OPTERR"); | ||
| 9903 | /* 0 if "OPTERR=0", 1 otherwise */ | ||
| 9904 | c = (!cp || NOT_LONE_CHAR(cp, '0')); | ||
| 9905 | } | ||
| 9906 | opterr = c; | ||
| 9907 | cp = get_local_var_value("OPTIND"); | ||
| 9908 | optind = cp ? atoi(cp) : 0; | ||
| 9909 | optarg = NULL; | ||
| 9910 | cbuf[1] = '\0'; | ||
| 9911 | |||
| 9912 | /* getopts stops on first non-option. Add "+" to force that */ | ||
| 9913 | /*if (optstring[0] != '+')*/ { | ||
| 9914 | char *s = alloca(strlen(optstring) + 2); | ||
| 9915 | sprintf(s, "+%s", optstring); | ||
| 9916 | optstring = s; | ||
| 9917 | } | ||
| 9918 | |||
| 9919 | if (argv[1]) | ||
| 9920 | argv[0] = G.global_argv[0]; /* for error messages */ | ||
| 9921 | else | ||
| 9922 | argv = G.global_argv; | ||
| 9923 | c = getopt(string_array_len(argv), argv, optstring); | ||
| 9924 | |||
| 9925 | /* Set OPTARG */ | ||
| 9926 | /* Always set or unset, never left as-is, even on exit/error: | ||
| 9927 | * "If no option was found, or if the option that was found | ||
| 9928 | * does not have an option-argument, OPTARG shall be unset." | ||
| 9929 | */ | ||
| 9930 | cp = optarg; | ||
| 9931 | if (c == '?') { | ||
| 9932 | /* If ":optstring" and unknown option is seen, | ||
| 9933 | * it is stored to OPTARG. | ||
| 9934 | */ | ||
| 9935 | if (optstring[1] == ':') { | ||
| 9936 | cbuf[0] = optopt; | ||
| 9937 | cp = cbuf; | ||
| 9938 | } | ||
| 9939 | } | ||
| 9940 | if (cp) | ||
| 9941 | set_local_var_from_halves("OPTARG", cp); | ||
| 9942 | else | ||
| 9943 | unset_local_var("OPTARG"); | ||
| 9944 | |||
| 9945 | /* Convert -1 to "?" */ | ||
| 9946 | exitcode = EXIT_SUCCESS; | ||
| 9947 | if (c < 0) { /* -1: end of options */ | ||
| 9948 | exitcode = EXIT_FAILURE; | ||
| 9949 | c = '?'; | ||
| 9950 | } | ||
| 9951 | |||
| 9952 | /* Set VAR and OPTIND */ | ||
| 9953 | cbuf[0] = c; | ||
| 9954 | set_local_var_from_halves(var, cbuf); | ||
| 9955 | set_local_var_from_halves("OPTIND", utoa(optind)); | ||
| 9956 | |||
| 9957 | return exitcode; | ||
| 9958 | } | ||
| 9959 | #endif | ||
| 9960 | |||
| 9789 | static int FAST_FUNC builtin_source(char **argv) | 9961 | static int FAST_FUNC builtin_source(char **argv) |
| 9790 | { | 9962 | { |
| 9791 | char *arg_path, *filename; | 9963 | char *arg_path, *filename; |
| @@ -10178,6 +10350,7 @@ static int wait_for_child_or_signal(struct pipe *waitfor_pipe, pid_t waitfor_pid | |||
| 10178 | /* So, did we get a signal? */ | 10350 | /* So, did we get a signal? */ |
| 10179 | sig = check_and_run_traps(); | 10351 | sig = check_and_run_traps(); |
| 10180 | if (sig /*&& sig != SIGCHLD - always true */) { | 10352 | if (sig /*&& sig != SIGCHLD - always true */) { |
| 10353 | /* Do this for any (non-ignored) signal, not only for ^C */ | ||
| 10181 | ret = 128 + sig; | 10354 | ret = 128 + sig; |
| 10182 | break; | 10355 | break; |
| 10183 | } | 10356 | } |
| @@ -10344,6 +10517,41 @@ static int FAST_FUNC builtin_return(char **argv) | |||
| 10344 | } | 10517 | } |
| 10345 | #endif | 10518 | #endif |
| 10346 | 10519 | ||
| 10520 | #if ENABLE_HUSH_TIMES | ||
| 10521 | static int FAST_FUNC builtin_times(char **argv UNUSED_PARAM) | ||
| 10522 | { | ||
| 10523 | static const uint8_t times_tbl[] ALIGN1 = { | ||
| 10524 | ' ', offsetof(struct tms, tms_utime), | ||
| 10525 | '\n', offsetof(struct tms, tms_stime), | ||
| 10526 | ' ', offsetof(struct tms, tms_cutime), | ||
| 10527 | '\n', offsetof(struct tms, tms_cstime), | ||
| 10528 | 0 | ||
| 10529 | }; | ||
| 10530 | const uint8_t *p; | ||
| 10531 | unsigned clk_tck; | ||
| 10532 | struct tms buf; | ||
| 10533 | |||
| 10534 | clk_tck = bb_clk_tck(); | ||
| 10535 | |||
| 10536 | times(&buf); | ||
| 10537 | p = times_tbl; | ||
| 10538 | do { | ||
| 10539 | unsigned sec, frac; | ||
| 10540 | unsigned long t; | ||
| 10541 | t = *(clock_t *)(((char *) &buf) + p[1]); | ||
| 10542 | sec = t / clk_tck; | ||
| 10543 | frac = t % clk_tck; | ||
| 10544 | printf("%um%u.%03us%c", | ||
| 10545 | sec / 60, sec % 60, | ||
| 10546 | (frac * 1000) / clk_tck, | ||
| 10547 | p[0]); | ||
| 10548 | p += 2; | ||
| 10549 | } while (*p); | ||
| 10550 | |||
| 10551 | return EXIT_SUCCESS; | ||
| 10552 | } | ||
| 10553 | #endif | ||
| 10554 | |||
| 10347 | #if ENABLE_HUSH_MEMLEAK | 10555 | #if ENABLE_HUSH_MEMLEAK |
| 10348 | static int FAST_FUNC builtin_memleak(char **argv UNUSED_PARAM) | 10556 | static int FAST_FUNC builtin_memleak(char **argv UNUSED_PARAM) |
| 10349 | { | 10557 | { |
diff --git a/shell/hush_test/hush-getopts/getopt_optarg.right b/shell/hush_test/hush-getopts/getopt_optarg.right new file mode 100644 index 000000000..9dbd8460e --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_optarg.right | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | *** no OPTIND, optstring:'w:et' args:-q -w e -r -t -y | ||
| 2 | ./getopt_optarg.tests: invalid option -- q | ||
| 3 | var:'?' OPTIND:2 OPTARG:'' | ||
| 4 | var:'w' OPTIND:4 OPTARG:'e' | ||
| 5 | ./getopt_optarg.tests: invalid option -- r | ||
| 6 | var:'?' OPTIND:5 OPTARG:'' | ||
| 7 | var:'t' OPTIND:6 OPTARG:'' | ||
| 8 | ./getopt_optarg.tests: invalid option -- y | ||
| 9 | var:'?' OPTIND:7 OPTARG:'' | ||
| 10 | exited: var:'?' OPTIND:7 OPTARG:'' | ||
| 11 | *** OPTIND=0, optstring:'w:et' args:-w 1 -w2 -w -e -e -t -t | ||
| 12 | var:'w' OPTIND:3 OPTARG:'1' | ||
| 13 | var:'w' OPTIND:4 OPTARG:'2' | ||
| 14 | var:'w' OPTIND:6 OPTARG:'-e' | ||
| 15 | var:'e' OPTIND:7 OPTARG:'' | ||
| 16 | var:'t' OPTIND:8 OPTARG:'' | ||
| 17 | var:'t' OPTIND:9 OPTARG:'' | ||
| 18 | exited: var:'?' OPTIND:9 OPTARG:'' | ||
diff --git a/shell/hush_test/hush-getopts/getopt_optarg.tests b/shell/hush_test/hush-getopts/getopt_optarg.tests new file mode 100755 index 000000000..881cc7884 --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_optarg.tests | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | ( | ||
| 2 | |||
| 3 | set -- -q -w e -r -t -y | ||
| 4 | echo "*** no OPTIND, optstring:'w:et' args:$*" | ||
| 5 | var=QWERTY | ||
| 6 | OPTARG=ASDFGH | ||
| 7 | while getopts "w:et" var; do | ||
| 8 | echo "var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 9 | OPTARG=ASDFGH | ||
| 10 | done | ||
| 11 | echo "exited: var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 12 | |||
| 13 | set -- -w 1 -w2 -w -e -e -t -t | ||
| 14 | echo "*** OPTIND=0, optstring:'w:et' args:$*" | ||
| 15 | OPTIND=0 | ||
| 16 | while getopts "w:et" var; do | ||
| 17 | echo "var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 18 | OPTARG=ASDFGH | ||
| 19 | done | ||
| 20 | echo "exited: var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 21 | |||
| 22 | ) 2>&1 \ | ||
| 23 | | sed -e 's/ unrecognized option: / invalid option -- /' \ | ||
| 24 | -e 's/ illegal option -- / invalid option -- /' \ | ||
diff --git a/shell/hush_test/hush-getopts/getopt_positional.right b/shell/hush_test/hush-getopts/getopt_positional.right new file mode 100644 index 000000000..f1c942476 --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_positional.right | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | *** no OPTIND, optstring:'we' args:-q -w -e r -t -y | ||
| 2 | ./getopt_positional.tests: invalid option -- q | ||
| 3 | var:'?' OPTIND:2 | ||
| 4 | var:'w' OPTIND:3 | ||
| 5 | var:'e' OPTIND:4 | ||
| 6 | exited: var:'?' OPTIND:4 | ||
diff --git a/shell/hush_test/hush-getopts/getopt_positional.tests b/shell/hush_test/hush-getopts/getopt_positional.tests new file mode 100755 index 000000000..20716bb0c --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_positional.tests | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | ( | ||
| 2 | |||
| 3 | set -- -q -w -e r -t -y | ||
| 4 | echo "*** no OPTIND, optstring:'we' args:$*" | ||
| 5 | var=QWERTY | ||
| 6 | while getopts "we" var; do | ||
| 7 | echo "var:'$var' OPTIND:$OPTIND" | ||
| 8 | done | ||
| 9 | echo "exited: var:'$var' OPTIND:$OPTIND" | ||
| 10 | |||
| 11 | ) 2>&1 \ | ||
| 12 | | sed -e 's/ unrecognized option: / invalid option -- /' \ | ||
| 13 | -e 's/ illegal option -- / invalid option -- /' \ | ||
diff --git a/shell/hush_test/hush-getopts/getopt_silent.right b/shell/hush_test/hush-getopts/getopt_silent.right new file mode 100644 index 000000000..03d4eb149 --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_silent.right | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | *** optstring:':ac' args:-a -b -c | ||
| 2 | 1 rc:0 var:'a' OPTIND:2 OPTARG:'' | ||
| 3 | 2 rc:0 var:'?' OPTIND:3 OPTARG:'b' | ||
| 4 | 3 rc:0 var:'c' OPTIND:4 OPTARG:'' | ||
| 5 | 4 rc:1 var:'?' OPTIND:4 OPTARG:'' | ||
| 6 | 5 rc:1 var:'?' OPTIND:4 OPTARG:'' | ||
diff --git a/shell/hush_test/hush-getopts/getopt_silent.tests b/shell/hush_test/hush-getopts/getopt_silent.tests new file mode 100755 index 000000000..5f255db7f --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_silent.tests | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | # Open Group Base Specifications Issue 7: | ||
| 2 | # """ | ||
| 3 | # If an unknown option is met, VAR shall be set to "?". In this case, | ||
| 4 | # if the first character in optstring is ":", OPTARG shall be set | ||
| 5 | # to the option character found, but no output shall be written | ||
| 6 | # to standard error; otherwise, the shell variable OPTARG shall be | ||
| 7 | # unset and a diagnostic message shall be written to standard error." | ||
| 8 | # ... | ||
| 9 | # If an option-argument is missing: | ||
| 10 | # If the first character of optstring is ":", VAR shall be set to ":" | ||
| 11 | # and OPTARG shall be set to the option character found. | ||
| 12 | # """ | ||
| 13 | |||
| 14 | ( | ||
| 15 | |||
| 16 | echo "*** optstring:':ac' args:-a -b -c" | ||
| 17 | getopts ":ac" var -a -b -c; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 18 | getopts ":ac" var -a -b -c; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 19 | getopts ":ac" var -a -b -c; echo "3 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 20 | getopts ":ac" var -a -b -c; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 21 | # Previous line should result in "rc:1", which is normally treated | ||
| 22 | # in getopts loops as exit condition. | ||
| 23 | # Nevertheless, let's verify that calling it yet another time doesn't do | ||
| 24 | # anything weird: | ||
| 25 | getopts ":ac" var -a -b -c; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 26 | |||
| 27 | ) 2>&1 \ | ||
| 28 | | sed -e 's/ unrecognized option: / invalid option -- /' \ | ||
| 29 | -e 's/ illegal option -- / invalid option -- /' \ | ||
diff --git a/shell/hush_test/hush-getopts/getopt_simple.right b/shell/hush_test/hush-getopts/getopt_simple.right new file mode 100644 index 000000000..b4855fa1a --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_simple.right | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | *** no OPTIND, optstring:'ab' args:-a -b c | ||
| 2 | var:'a' OPTIND:2 | ||
| 3 | var:'b' OPTIND:3 | ||
| 4 | exited: rc:0 var:'?' OPTIND:3 | ||
| 5 | *** OPTIND=1, optstring:'ab' args:-a -b c | ||
| 6 | var:'a' OPTIND:2 | ||
| 7 | var:'b' OPTIND:3 | ||
| 8 | exited: rc:0 var:'?' OPTIND:3 | ||
| 9 | *** OPTIND=0, optstring:'ab' args:-a -b c | ||
| 10 | var:'a' OPTIND:2 | ||
| 11 | var:'b' OPTIND:3 | ||
| 12 | exited: rc:0 var:'?' OPTIND:3 | ||
| 13 | *** unset OPTIND, optstring:'ab' args:-a -b c | ||
| 14 | var:'a' OPTIND:2 | ||
| 15 | var:'b' OPTIND:3 | ||
| 16 | exited: rc:0 var:'?' OPTIND:3 | ||
| 17 | *** optstring:'ab' args:-a -b c | ||
| 18 | 1 rc:0 var:'a' OPTIND:2 | ||
| 19 | 2 rc:0 var:'b' OPTIND:3 | ||
| 20 | 3 rc:1 var:'?' OPTIND:3 | ||
| 21 | *** unset OPTIND, optstring:'ab' args:-a c -c -b d | ||
| 22 | var:'a' OPTIND:2 | ||
| 23 | exited: rc:0 var:'?' OPTIND:2 | ||
| 24 | *** unset OPTIND, optstring:'ab' args:-a -c -b d | ||
| 25 | var:'a' OPTIND:2 | ||
| 26 | ./getopt_simple.tests: invalid option -- c | ||
| 27 | var:'?' OPTIND:3 | ||
| 28 | var:'b' OPTIND:4 | ||
| 29 | exited: rc:0 var:'?' OPTIND:4 | ||
| 30 | *** unset OPTIND, OPTERR=0, optstring:'ab' args:-a -c -b d | ||
| 31 | var:'a' OPTIND:2 | ||
| 32 | var:'?' OPTIND:3 | ||
| 33 | var:'b' OPTIND:4 | ||
| 34 | exited: rc:0 var:'?' OPTIND:4 | ||
diff --git a/shell/hush_test/hush-getopts/getopt_simple.tests b/shell/hush_test/hush-getopts/getopt_simple.tests new file mode 100755 index 000000000..50718cc98 --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_simple.tests | |||
| @@ -0,0 +1,81 @@ | |||
| 1 | # Simple usage cases for getopts. | ||
| 2 | # | ||
| 3 | # OPTIND is either not touched at all (first loop with getopts, | ||
| 4 | # relying on shell startup init), or getopts state is reset | ||
| 5 | # before new loop with "unset OPTIND", "OPTIND=1" or "OPTIND=0". | ||
| 6 | # | ||
| 7 | # Each option is a separate argument (no "-abc"). This conceptually | ||
| 8 | # needs only $OPTIND to hold getopts state. | ||
| 9 | # | ||
| 10 | # We check that loop does not stop on unknown option (sets "?"), | ||
| 11 | # stops on _first_ non-option argument. | ||
| 12 | |||
| 13 | ( | ||
| 14 | |||
| 15 | echo "*** no OPTIND, optstring:'ab' args:-a -b c" | ||
| 16 | var=QWERTY | ||
| 17 | while getopts "ab" var -a -b c; do | ||
| 18 | echo "var:'$var' OPTIND:$OPTIND" | ||
| 19 | done | ||
| 20 | # unfortunately, "rc:0" is shown since while's overall exitcode is "success" | ||
| 21 | echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" | ||
| 22 | |||
| 23 | # Resetting behavior =1 | ||
| 24 | echo "*** OPTIND=1, optstring:'ab' args:-a -b c" | ||
| 25 | OPTIND=1 | ||
| 26 | while getopts "ab" var -a -b c; do | ||
| 27 | echo "var:'$var' OPTIND:$OPTIND" | ||
| 28 | done | ||
| 29 | echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" | ||
| 30 | |||
| 31 | # Resetting behavior =0 | ||
| 32 | echo "*** OPTIND=0, optstring:'ab' args:-a -b c" | ||
| 33 | OPTIND=0 | ||
| 34 | while getopts "ab" var -a -b c; do | ||
| 35 | echo "var:'$var' OPTIND:$OPTIND" | ||
| 36 | done | ||
| 37 | echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" | ||
| 38 | |||
| 39 | # Resetting behavior "unset" | ||
| 40 | echo "*** unset OPTIND, optstring:'ab' args:-a -b c" | ||
| 41 | unset OPTIND | ||
| 42 | while getopts "ab" var -a -b c; do | ||
| 43 | echo "var:'$var' OPTIND:$OPTIND" | ||
| 44 | done | ||
| 45 | echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" | ||
| 46 | |||
| 47 | # What is the final exitcode? | ||
| 48 | echo "*** optstring:'ab' args:-a -b c" | ||
| 49 | unset OPTIND | ||
| 50 | getopts "ab" var -a -b c; echo "1 rc:$? var:'$var' OPTIND:$OPTIND" | ||
| 51 | getopts "ab" var -a -b c; echo "2 rc:$? var:'$var' OPTIND:$OPTIND" | ||
| 52 | getopts "ab" var -a -b c; echo "3 rc:$? var:'$var' OPTIND:$OPTIND" | ||
| 53 | |||
| 54 | # Where would it stop? c or -c? | ||
| 55 | echo "*** unset OPTIND, optstring:'ab' args:-a c -c -b d" | ||
| 56 | unset OPTIND | ||
| 57 | while getopts "ab" var -a c -c -b d; do | ||
| 58 | echo "var:'$var' OPTIND:$OPTIND" | ||
| 59 | done | ||
| 60 | echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" | ||
| 61 | |||
| 62 | # What happens on unknown option? | ||
| 63 | echo "*** unset OPTIND, optstring:'ab' args:-a -c -b d" | ||
| 64 | unset OPTIND | ||
| 65 | while getopts "ab" var -a -c -b d; do | ||
| 66 | echo "var:'$var' OPTIND:$OPTIND" | ||
| 67 | done | ||
| 68 | echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" | ||
| 69 | |||
| 70 | # ORTERR=0 suppresses error message? | ||
| 71 | echo "*** unset OPTIND, OPTERR=0, optstring:'ab' args:-a -c -b d" | ||
| 72 | unset OPTIND | ||
| 73 | OPTERR=0 | ||
| 74 | while getopts "ab" var -a -c -b d; do | ||
| 75 | echo "var:'$var' OPTIND:$OPTIND" | ||
| 76 | done | ||
| 77 | echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" | ||
| 78 | |||
| 79 | ) 2>&1 \ | ||
| 80 | | sed -e 's/ unrecognized option: / invalid option -- /' \ | ||
| 81 | -e 's/ illegal option -- / invalid option -- /' \ | ||
diff --git a/shell/hush_test/hush-getopts/getopt_test_libc_bug.right b/shell/hush_test/hush-getopts/getopt_test_libc_bug.right new file mode 100644 index 000000000..6694e8f0c --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_test_libc_bug.right | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | *** optstring:'ac' args:-a -b -c -d e | ||
| 2 | 1 rc:0 var:'a' OPTIND:2 OPTARG:'' | ||
| 3 | ./getopt_test_libc_bug.tests: invalid option -- b | ||
| 4 | 2 rc:0 var:'?' OPTIND:3 OPTARG:'' | ||
| 5 | 3 rc:0 var:'c' OPTIND:4 OPTARG:'' | ||
| 6 | ./getopt_test_libc_bug.tests: invalid option -- d | ||
| 7 | 4 rc:0 var:'?' OPTIND:5 OPTARG:'' | ||
| 8 | 5 rc:1 var:'?' OPTIND:5 OPTARG:'' | ||
| 9 | |||
| 10 | *** optstring:'ac' args:-a -b -c -d e | ||
| 11 | 1 rc:0 var:'a' OPTIND:2 OPTARG:'' | ||
| 12 | ./getopt_test_libc_bug.tests: invalid option -- b | ||
| 13 | 2 rc:0 var:'?' OPTIND:3 OPTARG:'' | ||
| 14 | 3 rc:0 var:'c' OPTIND:4 OPTARG:'' | ||
| 15 | ./getopt_test_libc_bug.tests: invalid option -- d | ||
| 16 | 4 rc:0 var:'?' OPTIND:5 OPTARG:'' | ||
| 17 | 5 rc:1 var:'?' OPTIND:5 OPTARG:'' | ||
| 18 | |||
| 19 | *** optstring:'ac' args:-a -b -c -d e | ||
| 20 | 1 rc:0 var:'a' OPTIND:2 OPTARG:'' | ||
| 21 | ./getopt_test_libc_bug.tests: invalid option -- b | ||
| 22 | 2 rc:0 var:'?' OPTIND:3 OPTARG:'' | ||
| 23 | 3 rc:0 var:'c' OPTIND:4 OPTARG:'' | ||
| 24 | ./getopt_test_libc_bug.tests: invalid option -- d | ||
| 25 | 4 rc:0 var:'?' OPTIND:5 OPTARG:'' | ||
| 26 | 5 rc:1 var:'?' OPTIND:5 OPTARG:'' | ||
diff --git a/shell/hush_test/hush-getopts/getopt_test_libc_bug.tests b/shell/hush_test/hush-getopts/getopt_test_libc_bug.tests new file mode 100755 index 000000000..ab7bc3b09 --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_test_libc_bug.tests | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | # This test can fail with libc with buggy getopt() implementation. | ||
| 2 | # If getopt() wants to parse multi-option args (-abc), | ||
| 3 | # it needs to remember a position within current arg. | ||
| 4 | # | ||
| 5 | # If this position is kept as a POINTER, not an offset, | ||
| 6 | # and if argv[] ADDRESSES (not contents!) change, it blows up. | ||
| 7 | |||
| 8 | ( | ||
| 9 | |||
| 10 | echo "*** optstring:'ac' args:-a -b -c -d e" | ||
| 11 | getopts "ac" var -a -b -c -d e; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 12 | getopts "ac" var -a -b -c -d e; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 13 | getopts "ac" var -a -b -c -d e; echo "3 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 14 | getopts "ac" var -a -b -c -d e; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 15 | getopts "ac" var -a -b -c -d e; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 16 | |||
| 17 | # Above: args are (usually) in the same locations in memory. | ||
| 18 | # Below: variable allocations change the location. | ||
| 19 | |||
| 20 | echo | ||
| 21 | echo "*** optstring:'ac' args:-a -b -c -d e" | ||
| 22 | unset OPTIND | ||
| 23 | OPTARG=QWERTY; getopts "ac" var -a -b -c -d e; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 24 | NEWVAR=NEWVAL; getopts "ac" var -a -b -c -d e; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 25 | VAR111=NEWVAL; getopts "ac" var -a -b -c -d e; echo "3 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 26 | VAR222=NEWVAL; getopts "ac" var -a -b -c -d e; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 27 | VAR333=NEWVAL; getopts "ac" var -a -b -c -d e; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 28 | |||
| 29 | # Slightly different attempts to force reallocations | ||
| 30 | |||
| 31 | echo | ||
| 32 | echo "*** optstring:'ac' args:-a -b -c -d e" | ||
| 33 | unset OPTIND | ||
| 34 | export OPTARG; getopts "ac" var -a -b -c -d e; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 35 | export NEWVAR; getopts "ac" var -a -b -c -d e; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 36 | export VAR111; getopts "ac" var -a -b -c -d e; echo "3 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 37 | export VAR222; getopts "ac" var -a -b -c -d e; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 38 | export VAR333; getopts "ac" var -a -b -c -d e; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" | ||
| 39 | |||
| 40 | # All copies of code above should generate identical output | ||
| 41 | |||
| 42 | ) 2>&1 \ | ||
| 43 | | sed -e 's/ unrecognized option: / invalid option -- /' \ | ||
| 44 | -e 's/ illegal option -- / invalid option -- /' \ | ||
diff --git a/shell/hush_test/hush-parsing/groups_and_keywords2.right b/shell/hush_test/hush-parsing/groups_and_keywords2.right new file mode 100644 index 000000000..ae74a5db9 --- /dev/null +++ b/shell/hush_test/hush-parsing/groups_and_keywords2.right | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | hush: syntax error: unexpected ) | ||
| 2 | Fail:2 | ||
| 3 | hush: syntax error: unexpected ) | ||
diff --git a/shell/hush_test/hush-parsing/groups_and_keywords2.tests b/shell/hush_test/hush-parsing/groups_and_keywords2.tests new file mode 100755 index 000000000..ab33b909f --- /dev/null +++ b/shell/hush_test/hush-parsing/groups_and_keywords2.tests | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | # This is an error | ||
| 2 | (eval 'if() { echo; }') | ||
| 3 | echo Fail:$? | ||
| 4 | # ^^^^^^ bash prints 1, but interactively it sets $? = 2 | ||
| 5 | # we print 2 | ||
| 6 | |||
| 7 | # This is an error, and it aborts in script | ||
| 8 | if() { echo; } | ||
| 9 | echo Not reached | ||
diff --git a/shell/hush_test/run-all b/shell/hush_test/run-all index 1dd0edc39..3fbc7c531 100755 --- a/shell/hush_test/run-all +++ b/shell/hush_test/run-all | |||
| @@ -80,7 +80,7 @@ do_test() | |||
| 80 | case $? in | 80 | case $? in |
| 81 | 0) echo " ok";; | 81 | 0) echo " ok";; |
| 82 | 77) echo " skip (feature disabled)";; | 82 | 77) echo " skip (feature disabled)";; |
| 83 | *) echo " fail"; tret=1;; | 83 | *) echo " fail ($?)"; tret=1;; |
| 84 | esac | 84 | esac |
| 85 | done | 85 | done |
| 86 | exit ${tret} | 86 | exit ${tret} |
diff --git a/shell/math.c b/shell/math.c index 006221b6a..f01f24362 100644 --- a/shell/math.c +++ b/shell/math.c | |||
| @@ -743,7 +743,7 @@ arith(arith_state_t *math_state, const char *expr) | |||
| 743 | * may be used to endorse or promote products derived from this software | 743 | * may be used to endorse or promote products derived from this software |
| 744 | * without specific prior written permission. | 744 | * without specific prior written permission. |
| 745 | * | 745 | * |
| 746 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | 746 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND |
| 747 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 747 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 748 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | 748 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| 749 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | 749 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
diff --git a/shell/shell_common.c b/shell/shell_common.c index 750adc5d8..bc34de404 100644 --- a/shell/shell_common.c +++ b/shell/shell_common.c | |||
| @@ -58,7 +58,8 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
| 58 | const char *opt_n, | 58 | const char *opt_n, |
| 59 | const char *opt_p, | 59 | const char *opt_p, |
| 60 | const char *opt_t, | 60 | const char *opt_t, |
| 61 | const char *opt_u | 61 | const char *opt_u, |
| 62 | const char *opt_d | ||
| 62 | ) | 63 | ) |
| 63 | { | 64 | { |
| 64 | struct pollfd pfd[1]; | 65 | struct pollfd pfd[1]; |
| @@ -68,6 +69,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
| 68 | int nchars; /* -n NUM */ | 69 | int nchars; /* -n NUM */ |
| 69 | char **pp; | 70 | char **pp; |
| 70 | char *buffer; | 71 | char *buffer; |
| 72 | char delim; | ||
| 71 | struct termios tty, old_tty; | 73 | struct termios tty, old_tty; |
| 72 | const char *retval; | 74 | const char *retval; |
| 73 | int bufpos; /* need to be able to hold -1 */ | 75 | int bufpos; /* need to be able to hold -1 */ |
| @@ -188,6 +190,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
| 188 | end_ms += (unsigned)monotonic_ms(); | 190 | end_ms += (unsigned)monotonic_ms(); |
| 189 | buffer = NULL; | 191 | buffer = NULL; |
| 190 | bufpos = 0; | 192 | bufpos = 0; |
| 193 | delim = opt_d ? *opt_d : '\n'; | ||
| 191 | do { | 194 | do { |
| 192 | char c; | 195 | char c; |
| 193 | int timeout; | 196 | int timeout; |
| @@ -243,14 +246,14 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
| 243 | continue; | 246 | continue; |
| 244 | } | 247 | } |
| 245 | } | 248 | } |
| 246 | if (c == '\n') | 249 | if (c == delim) /* '\n' or -d CHAR */ |
| 247 | break; | 250 | break; |
| 248 | 251 | ||
| 249 | /* $IFS splitting. NOT done if we run "read" | 252 | /* $IFS splitting. NOT done if we run "read" |
| 250 | * without variable names (bash compat). | 253 | * without variable names (bash compat). |
| 251 | * Thus, "read" and "read REPLY" are not the same. | 254 | * Thus, "read" and "read REPLY" are not the same. |
| 252 | */ | 255 | */ |
| 253 | if (argv[0]) { | 256 | if (!opt_d && argv[0]) { |
| 254 | /* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_05 */ | 257 | /* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_05 */ |
| 255 | const char *is_ifs = strchr(ifs, c); | 258 | const char *is_ifs = strchr(ifs, c); |
| 256 | if (startword && is_ifs) { | 259 | if (startword && is_ifs) { |
diff --git a/shell/shell_common.h b/shell/shell_common.h index a82535c86..875fd9ea7 100644 --- a/shell/shell_common.h +++ b/shell/shell_common.h | |||
| @@ -34,6 +34,11 @@ enum { | |||
| 34 | BUILTIN_READ_SILENT = 1 << 0, | 34 | BUILTIN_READ_SILENT = 1 << 0, |
| 35 | BUILTIN_READ_RAW = 1 << 1, | 35 | BUILTIN_READ_RAW = 1 << 1, |
| 36 | }; | 36 | }; |
| 37 | //TODO? do not provide bashisms if not asked for: | ||
| 38 | //#if !ENABLE_HUSH_BASH_COMPAT && !ENABLE_ASH_BASH_COMPAT | ||
| 39 | //#define shell_builtin_read(setvar,argv,ifs,read_flags,n,p,t,u,d) | ||
| 40 | // shell_builtin_read(setvar,argv,ifs,read_flags) | ||
| 41 | //#endif | ||
| 37 | const char* FAST_FUNC | 42 | const char* FAST_FUNC |
| 38 | shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | 43 | shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), |
| 39 | char **argv, | 44 | char **argv, |
| @@ -42,7 +47,8 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
| 42 | const char *opt_n, | 47 | const char *opt_n, |
| 43 | const char *opt_p, | 48 | const char *opt_p, |
| 44 | const char *opt_t, | 49 | const char *opt_t, |
| 45 | const char *opt_u | 50 | const char *opt_u, |
| 51 | const char *opt_d | ||
| 46 | ); | 52 | ); |
| 47 | 53 | ||
| 48 | int FAST_FUNC | 54 | int FAST_FUNC |
