aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDenis Vlasenko <vda.linux@googlemail.com>2009-04-07 02:29:51 +0000
committerDenis Vlasenko <vda.linux@googlemail.com>2009-04-07 02:29:51 +0000
commit6c9be7f4518bf5594f5b9aaf981ed5dcc4a6939c (patch)
tree67f6d56a1d7c7fe251bb2dd41cd5b0c0118a0a10
parentab2b06434240bb11f5bef7201f9d01027df79896 (diff)
downloadbusybox-w32-6c9be7f4518bf5594f5b9aaf981ed5dcc4a6939c.tar.gz
busybox-w32-6c9be7f4518bf5594f5b9aaf981ed5dcc4a6939c.tar.bz2
busybox-w32-6c9be7f4518bf5594f5b9aaf981ed5dcc4a6939c.zip
hush: heredoc support, based on patch by Mike Frysinger (vapier AT gentoo.org)
some TODOs are to be attacked later function old new delta parse_stream 1461 1866 +405 parse_redirect - 265 +265 setup_heredoc - 116 +116 setup_redirects 151 196 +45 builtin_exit 49 47 -2 redirect_opt_num 61 55 -6 setup_redirect 212 - -212 ------------------------------------------------------------------------------ (add/remove: 2/1 grow/shrink: 2/2 up/down: 831/-220) Total: 611 bytes
-rw-r--r--shell/hush.c398
-rwxr-xr-xshell/hush_test/hush-misc/exec.tests22
-rw-r--r--shell/hush_test/hush-misc/heredoc1.right5
-rwxr-xr-xshell/hush_test/hush-misc/heredoc1.tests9
4 files changed, 319 insertions, 115 deletions
diff --git a/shell/hush.c b/shell/hush.c
index 14ca52ec0..792886944 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -21,8 +21,8 @@
21 * rewrites. 21 * rewrites.
22 * 22 *
23 * Other credits: 23 * Other credits:
24 * o_addchr() derived from similar w_addchar function in glibc-2.2. 24 * o_addchr derived from similar w_addchar function in glibc-2.2.
25 * setup_redirect(), redirect_opt_num(), and big chunks of main() 25 * setup_redirect, redirect_opt_num, and big chunks of main
26 * and many builtins derived from contributions by Erik Andersen. 26 * and many builtins derived from contributions by Erik Andersen.
27 * Miscellaneous bugfixes from Matt Kraai. 27 * Miscellaneous bugfixes from Matt Kraai.
28 * 28 *
@@ -249,11 +249,13 @@ static const char hush_version_str[] ALIGN1 = "HUSH_VERSION="BB_VER;
249#define SPECIAL_VAR_SYMBOL 3 249#define SPECIAL_VAR_SYMBOL 3
250 250
251typedef enum redir_type { 251typedef enum redir_type {
252 REDIRECT_INVALID = 0,
252 REDIRECT_INPUT = 1, 253 REDIRECT_INPUT = 1,
253 REDIRECT_OVERWRITE = 2, 254 REDIRECT_OVERWRITE = 2,
254 REDIRECT_APPEND = 3, 255 REDIRECT_APPEND = 3,
255 REDIRECT_HEREIS = 4, 256 REDIRECT_HEREDOC = 4,
256 REDIRECT_IO = 5 257 REDIRECT_IO = 5,
258 REDIRECT_HEREDOC2 = 6, /* REDIRECT_HEREDOC after heredoc load */
257} redir_type; 259} redir_type;
258 260
259/* The descrip member of this structure is only used to make 261/* The descrip member of this structure is only used to make
@@ -263,12 +265,14 @@ static const struct {
263 signed char default_fd; 265 signed char default_fd;
264 char descrip[3]; 266 char descrip[3];
265} redir_table[] = { 267} redir_table[] = {
266 { 0, 0, "()" }, 268 { 0, 0, "??" },
267 { O_RDONLY, 0, "<" }, 269 { O_RDONLY, 0, "<" },
268 { O_CREAT|O_TRUNC|O_WRONLY, 1, ">" }, 270 { O_CREAT|O_TRUNC|O_WRONLY, 1, ">" },
269 { O_CREAT|O_APPEND|O_WRONLY, 1, ">>" }, 271 { O_CREAT|O_APPEND|O_WRONLY, 1, ">>" },
270 { O_RDONLY, -1, "<<" }, 272 { O_RDONLY, 0, "<<" },
271 { O_RDWR, 1, "<>" } 273 { O_CREAT|O_RDWR, 1, "<>" },
274/* Should not be needed. Bogus default_fd helps in debugging */
275/* { O_RDONLY, 77, "<<" }, */
272}; 276};
273 277
274typedef enum reserved_style { 278typedef enum reserved_style {
@@ -2030,20 +2034,56 @@ static char **expand_assignments(char **argv, int count)
2030} 2034}
2031 2035
2032 2036
2037//TODO: fix big string case!
2038static void setup_heredoc(int fd, const char *heredoc)
2039{
2040 struct fd_pair pair;
2041 pid_t pid;
2042
2043 xpiped_pair(pair);
2044 pid = vfork();
2045 if (pid < 0)
2046 bb_perror_msg_and_die("vfork");
2047 if (pid == 0) { /* child */
2048 die_sleep = 0;
2049 close(pair.rd);
2050 xwrite_str(pair.wr, heredoc);
2051 _exit(0);
2052 }
2053 /* parent */
2054 die_sleep = -1;
2055 close(pair.wr);
2056 xmove_fd(pair.rd, fd);
2057}
2058
2033/* squirrel != NULL means we squirrel away copies of stdin, stdout, 2059/* squirrel != NULL means we squirrel away copies of stdin, stdout,
2034 * and stderr if they are redirected. */ 2060 * and stderr if they are redirected. */
2035static int setup_redirects(struct command *prog, int squirrel[]) 2061static int setup_redirects(struct command *prog, int squirrel[])
2036{ 2062{
2063//TODO: no callers ever check return value - ?!
2037 int openfd, mode; 2064 int openfd, mode;
2038 struct redir_struct *redir; 2065 struct redir_struct *redir;
2039 2066
2040 for (redir = prog->redirects; redir; redir = redir->next) { 2067 for (redir = prog->redirects; redir; redir = redir->next) {
2041 if (redir->dup == -1 && redir->rd_filename == NULL) { 2068 if (redir->rd_type == REDIRECT_HEREDOC2) {
2042 /* something went wrong in the parse. Pretend it didn't happen */ 2069 if (squirrel && redir->fd < 3) {
2070 squirrel[redir->fd] = dup(redir->fd);
2071 }
2072 /* for REDIRECT_HEREDOC2, rd_filename holds _contents_
2073 * of the heredoc */
2074 debug_printf_parse("set heredoc '%s'\n",
2075 redir->rd_filename);
2076 setup_heredoc(redir->fd, redir->rd_filename);
2043 continue; 2077 continue;
2044 } 2078 }
2079
2045 if (redir->dup == -1) { 2080 if (redir->dup == -1) {
2046 char *p; 2081 char *p;
2082 if (redir->rd_filename == NULL) {
2083 /* Something went wrong in the parse.
2084 * Pretend it didn't happen */
2085 continue;
2086 }
2047 mode = redir_table[redir->rd_type].mode; 2087 mode = redir_table[redir->rd_type].mode;
2048//TODO: check redir for names like '\\' 2088//TODO: check redir for names like '\\'
2049 p = expand_string_to_string(redir->rd_filename); 2089 p = expand_string_to_string(redir->rd_filename);
@@ -2051,7 +2091,8 @@ static int setup_redirects(struct command *prog, int squirrel[])
2051 free(p); 2091 free(p);
2052 if (openfd < 0) { 2092 if (openfd < 0) {
2053 /* this could get lost if stderr has been redirected, but 2093 /* this could get lost if stderr has been redirected, but
2054 bash and ash both lose it as well (though zsh doesn't!) */ 2094 * bash and ash both lose it as well (though zsh doesn't!) */
2095//what the above comment tries to say?
2055 return 1; 2096 return 1;
2056 } 2097 }
2057 } else { 2098 } else {
@@ -3407,83 +3448,6 @@ static int run_and_free_list(struct pipe *pi)
3407} 3448}
3408 3449
3409 3450
3410/* Peek ahead in the in_str to find out if we have a "&n" construct,
3411 * as in "2>&1", that represents duplicating a file descriptor.
3412 * Return either -2 (syntax error), -1 (no &), or the number found.
3413 */
3414static int redirect_dup_num(struct in_str *input)
3415{
3416 int ch, d = 0, ok = 0;
3417 ch = i_peek(input);
3418 if (ch != '&') return -1;
3419
3420 i_getch(input); /* get the & */
3421 ch = i_peek(input);
3422 if (ch == '-') {
3423 i_getch(input);
3424 return -3; /* "-" represents "close me" */
3425 }
3426 while (isdigit(ch)) {
3427 d = d*10 + (ch-'0');
3428 ok = 1;
3429 i_getch(input);
3430 ch = i_peek(input);
3431 }
3432 if (ok) return d;
3433
3434 bb_error_msg("ambiguous redirect");
3435 return -2;
3436}
3437
3438/* The src parameter allows us to peek forward to a possible &n syntax
3439 * for file descriptor duplication, e.g., "2>&1".
3440 * Return code is 0 normally, 1 if a syntax error is detected in src.
3441 * Resource errors (in xmalloc) cause the process to exit */
3442static int setup_redirect(struct parse_context *ctx,
3443 int fd,
3444 redir_type style,
3445 struct in_str *input)
3446{
3447 struct command *command = ctx->command;
3448 struct redir_struct *redir;
3449 struct redir_struct **redirp;
3450 int dup_num;
3451
3452 /* Check for a '2>&1' type redirect */
3453 dup_num = redirect_dup_num(input);
3454 if (dup_num == -2)
3455 return 1; /* syntax error */
3456
3457 /* Create a new redir_struct and drop it onto the end of the linked list */
3458 redirp = &command->redirects;
3459 while ((redir = *redirp) != NULL) {
3460 redirp = &(redir->next);
3461 }
3462 *redirp = redir = xzalloc(sizeof(*redir));
3463 /* redir->next = NULL; */
3464 /* redir->rd_filename = NULL; */
3465 redir->rd_type = style;
3466 redir->fd = (fd == -1) ? redir_table[style].default_fd : fd;
3467
3468 debug_printf("Redirect type %d%s\n", redir->fd, redir_table[style].descrip);
3469
3470 redir->dup = dup_num;
3471 if (dup_num != -1) {
3472 /* Erik had a check here that the file descriptor in question
3473 * is legit; I postpone that to "run time"
3474 * A "-" representation of "close me" shows up as a -3 here */
3475 debug_printf("Duplicating redirect '%d>&%d'\n", redir->fd, redir->dup);
3476 } else {
3477 /* We do _not_ try to open the file that src points to,
3478 * since we need to return and let src be expanded first.
3479 * Set ctx->pending_redirect, so we know what to do at the
3480 * end of the next parsed word. */
3481 ctx->pending_redirect = redir;
3482 }
3483 return 0;
3484}
3485
3486
3487static struct pipe *new_pipe(void) 3451static struct pipe *new_pipe(void)
3488{ 3452{
3489 struct pipe *pi; 3453 struct pipe *pi;
@@ -3602,7 +3566,6 @@ static void initialize_context(struct parse_context *ctx)
3602 done_command(ctx); 3566 done_command(ctx);
3603} 3567}
3604 3568
3605
3606/* If a reserved word is found and processed, parse context is modified 3569/* If a reserved word is found and processed, parse context is modified
3607 * and 1 is returned. 3570 * and 1 is returned.
3608 */ 3571 */
@@ -3766,14 +3729,26 @@ static int done_word(o_string *word, struct parse_context *ctx)
3766 if (ctx->pending_redirect) { 3729 if (ctx->pending_redirect) {
3767 /* We do not glob in e.g. >*.tmp case. bash seems to glob here 3730 /* We do not glob in e.g. >*.tmp case. bash seems to glob here
3768 * only if run as "bash", not "sh" */ 3731 * only if run as "bash", not "sh" */
3732 /* http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html
3733 * "2.7 Redirection
3734 * ...the word that follows the redirection operator
3735 * shall be subjected to tilde expansion, parameter expansion,
3736 * command substitution, arithmetic expansion, and quote
3737 * removal. Pathname expansion shall not be performed
3738 * on the word by a non-interactive shell; an interactive
3739 * shell may perform it, but shall do so only when
3740 * the expansion would result in one word."
3741 */
3769 ctx->pending_redirect->rd_filename = xstrdup(word->data); 3742 ctx->pending_redirect->rd_filename = xstrdup(word->data);
3770 word->o_assignment = NOT_ASSIGNMENT; 3743 word->o_assignment = NOT_ASSIGNMENT;
3771 debug_printf("word stored in rd_filename: '%s'\n", word->data); 3744 debug_printf_parse("word stored in rd_filename: '%s'\n", word->data);
3772 } else { 3745 } else {
3773 /* "{ echo foo; } echo bar" - bad */ 3746 /* "{ echo foo; } echo bar" - bad */
3774 /* NB: bash allows e.g.: 3747 /* NB: bash allows e.g.:
3775 * if true; then { echo foo; } fi 3748 * if true; then { echo foo; } fi
3776 * while if false; then false; fi do break; done 3749 * while if false; then false; fi do break; done
3750 * and disallows:
3751 * while if false; then false; fi; do; break; done
3777 * TODO? */ 3752 * TODO? */
3778 if (command->group) { 3753 if (command->group) {
3779 syntax(word->data); 3754 syntax(word->data);
@@ -3855,6 +3830,117 @@ static int done_word(o_string *word, struct parse_context *ctx)
3855 return 0; 3830 return 0;
3856} 3831}
3857 3832
3833
3834/* Peek ahead in the input to find out if we have a "&n" construct,
3835 * as in "2>&1", that represents duplicating a file descriptor.
3836 * Return: -3 if >&- "close fd" construct is seen,
3837 * -2 (syntax error), -1 if no & was seen, or the number found.
3838 */
3839#if BB_MMU
3840#define redirect_dup_num(as_string, input) \
3841 redirect_dup_num(input)
3842#endif
3843static int redirect_dup_num(o_string *as_string, struct in_str *input)
3844{
3845 int ch, d, ok;
3846
3847 ch = i_peek(input);
3848 if (ch != '&')
3849 return -1;
3850
3851 ch = i_getch(input); /* get the & */
3852#if !BB_MMU
3853 o_addchr(as_string, ch);
3854#endif
3855 ch = i_peek(input);
3856 if (ch == '-') {
3857 ch = i_getch(input);
3858#if !BB_MMU
3859 o_addchr(as_string, ch);
3860#endif
3861 return -3; /* "-" represents "close me" */
3862 }
3863 d = 0;
3864 ok = 0;
3865 while (ch != EOF && isdigit(ch)) {
3866 d = d*10 + (ch-'0');
3867 ok = 1;
3868 ch = i_getch(input);
3869#if !BB_MMU
3870 o_addchr(as_string, ch);
3871#endif
3872 ch = i_peek(input);
3873 }
3874 if (ok) return d;
3875
3876//TODO: this is the place to catch ">&file" bashism (redirect both fd 1 and 2)
3877
3878 bb_error_msg("ambiguous redirect");
3879 return -2;
3880}
3881
3882/* Return code is 0 normally, 1 if a syntax error is detected
3883 */
3884static int parse_redirect(struct parse_context *ctx,
3885 int fd,
3886 redir_type style,
3887 struct in_str *input)
3888{
3889 struct command *command = ctx->command;
3890 struct redir_struct *redir;
3891 struct redir_struct **redirp;
3892 int dup_num;
3893
3894 dup_num = -1;
3895 if (style != REDIRECT_HEREDOC) {
3896 /* Check for a '2>&1' type redirect */
3897 dup_num = redirect_dup_num(&ctx->as_string, input);
3898 if (dup_num == -2)
3899 return 1; /* syntax error */
3900 }
3901//TODO: else { check for <<-word }
3902
3903 if (style == REDIRECT_OVERWRITE && dup_num == -1) {
3904 int ch = i_peek(input);
3905 if (ch == '|') {
3906 /* >|FILE redirect ("clobbering" >).
3907 * Since we do not support "set -o noclobber" yet,
3908 * >| and > are the same for now. Just eat |.
3909 */
3910 ch = i_getch(input);
3911#if !BB_MMU
3912 o_addchr(&ctx->as_string, ch);
3913#endif
3914 }
3915 }
3916
3917 /* Create a new redir_struct and append it to the linked list */
3918 redirp = &command->redirects;
3919 while ((redir = *redirp) != NULL) {
3920 redirp = &(redir->next);
3921 }
3922 *redirp = redir = xzalloc(sizeof(*redir));
3923 /* redir->next = NULL; */
3924 /* redir->rd_filename = NULL; */
3925 redir->rd_type = style;
3926 redir->fd = (fd == -1) ? redir_table[style].default_fd : fd;
3927
3928 debug_printf_parse("redirect type %d %s\n", redir->fd, redir_table[style].descrip);
3929
3930 redir->dup = dup_num;
3931 if (dup_num != -1) {
3932 /* Erik had a check here that the file descriptor in question
3933 * is legit; I postpone that to "run time"
3934 * A "-" representation of "close me" shows up as a -3 here */
3935 debug_printf_parse("duplicating redirect '%d>&%d'\n", redir->fd, redir->dup);
3936 } else {
3937 /* Set ctx->pending_redirect, so we know what to do at the
3938 * end of the next parsed word. */
3939 ctx->pending_redirect = redir;
3940 }
3941 return 0;
3942}
3943
3858/* If a redirect is immediately preceded by a number, that number is 3944/* If a redirect is immediately preceded by a number, that number is
3859 * supposed to tell which file descriptor to redirect. This routine 3945 * supposed to tell which file descriptor to redirect. This routine
3860 * looks for such preceding numbers. In an ideal world this routine 3946 * looks for such preceding numbers. In an ideal world this routine
@@ -3863,25 +3949,97 @@ static int done_word(o_string *word, struct parse_context *ctx)
3863 * echo 49>foo # redirects fd 49 to file "foo", nothing passed to echo 3949 * echo 49>foo # redirects fd 49 to file "foo", nothing passed to echo
3864 * echo -2>foo # redirects fd 1 to file "foo", "-2" passed to echo 3950 * echo -2>foo # redirects fd 1 to file "foo", "-2" passed to echo
3865 * echo 49x>foo # redirects fd 1 to file "foo", "49x" passed to echo 3951 * echo 49x>foo # redirects fd 1 to file "foo", "49x" passed to echo
3866 * A -1 output from this program means no valid number was found, so the 3952 *
3867 * caller should use the appropriate default for this redirection. 3953 * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html
3954 * "2.7 Redirection
3955 * ... If n is quoted, the number shall not be recognized as part of
3956 * the redirection expression. For example:
3957 * echo \2>a
3958 * writes the character 2 into file a"
3959 * I am not sure we do it right (and not sure we care)
3960 *
3961 * A -1 return means no valid number was found,
3962 * the caller should use the appropriate default for this redirection.
3868 */ 3963 */
3869static int redirect_opt_num(o_string *o) 3964static int redirect_opt_num(o_string *o)
3870{ 3965{
3871 int num; 3966 int num;
3872 3967
3873 if (o->length == 0) 3968 if (o->data == NULL)
3969 return -1;
3970 num = bb_strtou(o->data, NULL, 10);
3971 if (errno || num < 0)
3874 return -1; 3972 return -1;
3875 for (num = 0; num < o->length; num++) {
3876 if (!isdigit(o->data[num])) {
3877 return -1;
3878 }
3879 }
3880 num = atoi(o->data);
3881 o_reset(o); 3973 o_reset(o);
3882 return num; 3974 return num;
3883} 3975}
3884 3976
3977//TODO: add NOMMU as_string fill
3978static char *fetch_till_str(struct in_str *input, const char *word)
3979{
3980 o_string heredoc = NULL_O_STRING;
3981 int past_EOL = 0;
3982 int ch;
3983
3984 while (1) {
3985 ch = i_getch(input);
3986 if (ch == EOF) {
3987 o_free_unsafe(&heredoc);
3988 return NULL;
3989 }
3990 if (ch == '\n') {
3991 if (strcmp(heredoc.data + past_EOL, word) == 0) {
3992 heredoc.data[past_EOL] = '\0';
3993 debug_printf_parse("parsed heredoc '%s'\n", heredoc.data);
3994 return heredoc.data;
3995 }
3996 past_EOL = heredoc.length + 1;
3997 }
3998 o_addchr(&heredoc, ch);
3999 }
4000}
4001
4002static int fetch_heredocs(int heredoc_cnt, struct parse_context *ctx, struct in_str *input)
4003{
4004 struct pipe *pi = ctx->list_head;
4005
4006 while (pi) {
4007 int i;
4008 struct command *cmd = pi->cmds;
4009
4010 debug_printf_parse("fetch_heredocs: num_cmds:%d cmd argv0:'%s'\n",
4011 pi->num_cmds,
4012 cmd->argv ? cmd->argv[0] : "NONE");
4013 for (i = 0; i < pi->num_cmds; i++) {
4014 struct redir_struct *redirect = cmd->redirects;
4015
4016 debug_printf_parse("fetch_heredocs: %d cmd argv0:'%s'\n",
4017 i, cmd->argv ? cmd->argv[0] : "NONE");
4018 while (redirect) {
4019 if (redirect->rd_type == REDIRECT_HEREDOC) {
4020 char *p;
4021
4022 if (heredoc_cnt <= 0)
4023 return 1; /* error */
4024 redirect->rd_type = REDIRECT_HEREDOC2;
4025 p = fetch_till_str(input, redirect->rd_filename);
4026 if (!p)
4027 return 1; /* unexpected EOF */
4028 free(redirect->rd_filename);
4029 redirect->rd_filename = p;
4030 heredoc_cnt--;
4031 }
4032 redirect = redirect->next;
4033 }
4034 cmd++;
4035 }
4036 pi = pi->next;
4037 }
4038 /* Should be 0. If it isn't, it's a parse error */
4039 return heredoc_cnt;
4040}
4041
4042
3885#if BB_MMU 4043#if BB_MMU
3886#define parse_stream(pstring, input, end_trigger) \ 4044#define parse_stream(pstring, input, end_trigger) \
3887 parse_stream(input, end_trigger) 4045 parse_stream(input, end_trigger)
@@ -4391,8 +4549,7 @@ static int parse_stream_dquoted(o_string *as_string,
4391 again: 4549 again:
4392 ch = i_getch(input); 4550 ch = i_getch(input);
4393#if !BB_MMU 4551#if !BB_MMU
4394 if (as_string && ch != EOF) 4552 if (as_string && ch != EOF) o_addchr(as_string, ch);
4395 o_addchr(as_string, ch);
4396#endif 4553#endif
4397 if (ch == dquote_end) { /* may be only '"' or EOF */ 4554 if (ch == dquote_end) { /* may be only '"' or EOF */
4398 dest->nonnull = 1; 4555 dest->nonnull = 1;
@@ -4479,6 +4636,7 @@ static struct pipe *parse_stream(char **pstring,
4479 struct parse_context ctx; 4636 struct parse_context ctx;
4480 o_string dest = NULL_O_STRING; 4637 o_string dest = NULL_O_STRING;
4481 int is_in_dquote; 4638 int is_in_dquote;
4639 int heredoc_cnt;
4482 4640
4483 /* Double-quote state is handled in the state variable is_in_dquote. 4641 /* Double-quote state is handled in the state variable is_in_dquote.
4484 * A single-quote triggers a bypass of the main loop until its mate is 4642 * A single-quote triggers a bypass of the main loop until its mate is
@@ -4498,6 +4656,7 @@ static struct pipe *parse_stream(char **pstring,
4498 /* dest.o_assignment = MAYBE_ASSIGNMENT; - already is */ 4656 /* dest.o_assignment = MAYBE_ASSIGNMENT; - already is */
4499 initialize_context(&ctx); 4657 initialize_context(&ctx);
4500 is_in_dquote = 0; 4658 is_in_dquote = 0;
4659 heredoc_cnt = 0;
4501 while (1) { 4660 while (1) {
4502 const char *is_ifs; 4661 const char *is_ifs;
4503 const char *is_special; 4662 const char *is_special;
@@ -4518,6 +4677,11 @@ static struct pipe *parse_stream(char **pstring,
4518 ch, ch, dest.o_escape); 4677 ch, ch, dest.o_escape);
4519 if (ch == EOF) { 4678 if (ch == EOF) {
4520 struct pipe *pi; 4679 struct pipe *pi;
4680
4681 if (heredoc_cnt) {
4682 syntax("unterminated here document");
4683 goto parse_error;
4684 }
4521 if (done_word(&dest, &ctx)) { 4685 if (done_word(&dest, &ctx)) {
4522 goto parse_error; 4686 goto parse_error;
4523 } 4687 }
@@ -4578,14 +4742,36 @@ static struct pipe *parse_stream(char **pstring,
4578#endif 4742#endif
4579 /* Treat newline as a command separator. */ 4743 /* Treat newline as a command separator. */
4580 done_pipe(&ctx, PIPE_SEQ); 4744 done_pipe(&ctx, PIPE_SEQ);
4745 debug_printf_parse("heredoc_cnt:%d\n", heredoc_cnt);
4746 if (heredoc_cnt) {
4747 if (fetch_heredocs(heredoc_cnt, &ctx, input))
4748 goto parse_error;
4749 heredoc_cnt = 0;
4750 }
4581 dest.o_assignment = MAYBE_ASSIGNMENT; 4751 dest.o_assignment = MAYBE_ASSIGNMENT;
4582 ch = ';'; 4752 ch = ';';
4583 /* note: if (is_ifs) continue; 4753 /* note: if (is_ifs) continue;
4584 * will still trigger for us */ 4754 * will still trigger for us */
4585 } 4755 }
4586 } 4756 }
4587 if (end_trigger && end_trigger == ch) { 4757 if (end_trigger && end_trigger == ch
4758 && (heredoc_cnt == 0 || end_trigger != ';')
4759 ) {
4588//TODO: disallow "{ cmd }" without semicolon 4760//TODO: disallow "{ cmd }" without semicolon
4761 if (heredoc_cnt) {
4762 /* This is technically valid:
4763 * { cat <<HERE; }; echo Ok
4764 * heredoc
4765 * heredoc
4766 * heredoc
4767 * HERE
4768 * but we don't support this.
4769 * We require heredoc to be in enclosing {}/(),
4770 * if any.
4771 */
4772 syntax("unterminated here document");
4773 goto parse_error;
4774 }
4589 if (done_word(&dest, &ctx)) { 4775 if (done_word(&dest, &ctx)) {
4590 goto parse_error; 4776 goto parse_error;
4591 } 4777 }
@@ -4715,7 +4901,8 @@ static struct pipe *parse_stream(char **pstring,
4715 goto parse_error; 4901 goto parse_error;
4716 } 4902 }
4717#endif 4903#endif
4718 setup_redirect(&ctx, redir_fd, redir_style, input); 4904 if (parse_redirect(&ctx, redir_fd, redir_style, input))
4905 goto parse_error;
4719 break; 4906 break;
4720 case '<': 4907 case '<':
4721 redir_fd = redirect_opt_num(&dest); 4908 redir_fd = redirect_opt_num(&dest);
@@ -4724,7 +4911,9 @@ static struct pipe *parse_stream(char **pstring,
4724 } 4911 }
4725 redir_style = REDIRECT_INPUT; 4912 redir_style = REDIRECT_INPUT;
4726 if (next == '<') { 4913 if (next == '<') {
4727 redir_style = REDIRECT_HEREIS; 4914 redir_style = REDIRECT_HEREDOC;
4915 heredoc_cnt++;
4916 debug_printf_parse("++heredoc_cnt=%d\n", heredoc_cnt);
4728 ch = i_getch(input); 4917 ch = i_getch(input);
4729#if !BB_MMU 4918#if !BB_MMU
4730 o_addchr(&ctx.as_string, ch); 4919 o_addchr(&ctx.as_string, ch);
@@ -4742,7 +4931,8 @@ static struct pipe *parse_stream(char **pstring,
4742 goto parse_error; 4931 goto parse_error;
4743 } 4932 }
4744#endif 4933#endif
4745 setup_redirect(&ctx, redir_fd, redir_style, input); 4934 if (parse_redirect(&ctx, redir_fd, redir_style, input))
4935 goto parse_error;
4746 break; 4936 break;
4747 case ';': 4937 case ';':
4748#if ENABLE_HUSH_CASE 4938#if ENABLE_HUSH_CASE
diff --git a/shell/hush_test/hush-misc/exec.tests b/shell/hush_test/hush-misc/exec.tests
index 01a7f591c..6de50fa7b 100755
--- a/shell/hush_test/hush-misc/exec.tests
+++ b/shell/hush_test/hush-misc/exec.tests
@@ -1,30 +1,30 @@
1# make sure we have a way of checking these things 1# make sure we have a way of checking these things
2[ ! -e /dev/fd ] && exit 1 2cd /proc/$$/fd || cd /dev/fd || exit 1
3 3
4[ -e /dev/fd/44 ] && exit 1 4[ -e 44 ] && exit 1
5exec 44>/dev/null 5exec 44>/dev/null
6[ -e /dev/fd/44 ] || exit 1 6[ -e 44 ] || exit 1
7echo pass fd out open 7echo pass fd out open
8 8
9[ -e /dev/fd/55 ] && exit 1 9[ -e 55 ] && exit 1
10exec 55>&44 10exec 55>&44
11[ -e /dev/fd/55 ] || exit 1 11[ -e 55 ] || exit 1
12echo pass fd out dup 12echo pass fd out dup
13 13
14exec 44>&- 14exec 44>&-
15[ -e /dev/fd/44 ] && exit 1 15[ -e 44 ] && exit 1
16echo pass fd out close 16echo pass fd out close
17 17
18[ -e /dev/fd/66 ] && exit 1 18[ -e 66 ] && exit 1
19exec 66</dev/null 19exec 66</dev/null
20[ -e /dev/fd/66 ] || exit 1 20[ -e 66 ] || exit 1
21echo pass fd in open 21echo pass fd in open
22 22
23[ -e /dev/fd/77 ] && exit 1 23[ -e 77 ] && exit 1
24exec 77<&66 24exec 77<&66
25[ -e /dev/fd/77 ] || exit 1 25[ -e 77 ] || exit 1
26echo pass fd in dup 26echo pass fd in dup
27 27
28exec 66<&- 28exec 66<&-
29[ -e /dev/fd/66 ] && exit 1 29[ -e 66 ] && exit 1
30echo pass fd in close 30echo pass fd in close
diff --git a/shell/hush_test/hush-misc/heredoc1.right b/shell/hush_test/hush-misc/heredoc1.right
new file mode 100644
index 000000000..7fc68f3e1
--- /dev/null
+++ b/shell/hush_test/hush-misc/heredoc1.right
@@ -0,0 +1,5 @@
1qwe
2asd
3123
4456
5Ok
diff --git a/shell/hush_test/hush-misc/heredoc1.tests b/shell/hush_test/hush-misc/heredoc1.tests
new file mode 100755
index 000000000..2eeb4726b
--- /dev/null
+++ b/shell/hush_test/hush-misc/heredoc1.tests
@@ -0,0 +1,9 @@
1cat <<000; cat <<www; cat <<eee
2000
3qwe
4asd
5www
6123
7456
8eee
9echo Ok