From 3e9aaae5dc384ae070c49507a92b1375397954cd Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Fri, 11 May 2007 12:56:43 +0000 Subject: hush: fix bug in interactive shell introduced yesterday hush: fix `process subst` (2 bugs) NB: will delete and re-add hush_test in order to change file modes --- shell/hush.c | 88 +++++++++++++++++--------------- shell/hush_test/hush-parsing/noeol.right | 1 - shell/hush_test/hush-parsing/noeol.tests | 2 - shell/hush_test/hush-vars/var.right | 4 -- shell/hush_test/hush-vars/var.tests | 10 ---- shell/hush_test/run-all | 59 --------------------- 6 files changed, 46 insertions(+), 118 deletions(-) delete mode 100644 shell/hush_test/hush-parsing/noeol.right delete mode 100644 shell/hush_test/hush-parsing/noeol.tests delete mode 100644 shell/hush_test/hush-vars/var.right delete mode 100644 shell/hush_test/hush-vars/var.tests delete mode 100644 shell/hush_test/run-all (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 32cd65c72..04fb00dc7 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -317,10 +317,10 @@ typedef struct { /* I can almost use ordinary FILE *. Is open_memstream() universally * available? Where is it documented? */ struct in_str { - union { - const char *p; - int cached_ch; - }; + const char *p; + /* eof_flag=1: last char in ->p is really an EOF */ + char eof_flag; /* meaningless if ->p == NULL */ + char peek_buf[2]; #if ENABLE_HUSH_INTERACTIVE int __promptme; int promptmode; @@ -976,7 +976,7 @@ static int b_check_space(o_string *o, int len) static int b_addchr(o_string *o, int ch) { - debug_printf("b_addchr: %c %d %p\n", ch, o->length, o); + debug_printf("b_addchr: '%c' o->lengtt=%d o=%p\n", ch, o->length, o); if (b_check_space(o, 1)) return B_NOSPAC; o->data[o->length] = ch; @@ -1079,12 +1079,13 @@ static const char* setup_prompt_string(int promptmode) static line_input_t *line_input_state; #endif -static int get_user_input(struct in_str *i) +static void get_user_input(struct in_str *i) { static char the_command[ENABLE_FEATURE_EDITING ? BUFSIZ : 2]; int r; const char *prompt_str; + prompt_str = setup_prompt_string(i->promptmode); #if ENABLE_FEATURE_EDITING /* @@ -1094,15 +1095,19 @@ static int get_user_input(struct in_str *i) ** child processes (rob@sysgo.de) */ r = read_line_input(prompt_str, the_command, BUFSIZ-1, line_input_state); + i->eof_flag = (r < 0); + if (i->eof_flag) { /* EOF/error detected */ + the_command[0] = EOF; /* yes, it will be truncated, it's ok */ + the_command[1] = '\0'; + } #else fputs(prompt_str, stdout); fflush(stdout); the_command[0] = r = fgetc(i->file); /*the_command[1] = '\0'; - already is and never changed */ + i->eof_flag = (r == EOF); #endif - fflush(stdout); i->p = the_command; - return r; /* < 0 == EOF. Not meaningful otherwise */ } #endif /* INTERACTIVE */ @@ -1112,33 +1117,30 @@ static int file_get(struct in_str *i) { int ch; - ch = 0; /* If there is data waiting, eat it up */ - if (i->cached_ch) { - ch = i->cached_ch ^ 0x100; - if (ch != EOF) - i->cached_ch = 0; + if (i->p && *i->p) { + take_cached: + ch = *i->p++; + if (i->eof_flag && !*i->p) + ch = EOF; } else { /* need to double check i->file because we might be doing something * more complicated by now, like sourcing or substituting. */ #if ENABLE_HUSH_INTERACTIVE if (interactive_fd && i->__promptme && i->file == stdin) { - while (!i->p || !(interactive_fd && i->p[0])) { - if (get_user_input(i) < 0) - return EOF; - } + do { + get_user_input(i); + } while (!*i->p); /* need non-empty line */ i->promptmode = 2; i->__promptme = 0; - if (i->p && *i->p) { - ch = *i->p++; - } + goto take_cached; } else #endif { ch = fgetc(i->file); } - debug_printf("file_get: got a %d\n", ch); } + debug_printf("file_get: got a '%c' %d\n", ch, ch); #if ENABLE_HUSH_INTERACTIVE if (ch == '\n') i->__promptme = 1; @@ -1152,12 +1154,17 @@ static int file_get(struct in_str *i) static int file_peek(struct in_str *i) { int ch; - if (i->cached_ch) { - return i->cached_ch ^ 0x100; + if (i->p && *i->p) { + if (i->eof_flag && !i->p[1]) + return EOF; + return *i->p; } ch = fgetc(i->file); - i->cached_ch = ch ^ 0x100; /* ^ 0x100 so that it is never 0 */ - debug_printf("file_peek: got a %d '%c'\n", ch, ch); + i->eof_flag = (ch == EOF); + i->peek_buf[0] = ch; + i->peek_buf[1] = '\0'; + i->p = i->peek_buf; + debug_printf("file_peek: got a '%c' %d\n", *i->p, *i->p); return ch; } @@ -1182,6 +1189,7 @@ static void setup_string_in_str(struct in_str *i, const char *s) i->promptmode = 1; #endif i->p = s; + i->eof_flag = 0; } static void mark_open(int fd) @@ -2846,11 +2854,12 @@ static FILE *generate_stream_from_list(struct pipe *head) static int process_command_subs(o_string *dest, struct p_context *ctx, struct in_str *input, const char *subst_end) { - int retcode; + int retcode, ch, eol_cnt; o_string result = NULL_O_STRING; struct p_context inner; FILE *p; struct in_str pipe_str; + initialize_context(&inner); /* recursion to generate command */ @@ -2863,25 +2872,20 @@ static int process_command_subs(o_string *dest, struct p_context *ctx, p = generate_stream_from_list(inner.list_head); if (p == NULL) return 1; mark_open(fileno(p)); -// FIXME: need to flag pipe_str to somehow discard all trailing newlines. -// Example: echo "TEST`date;echo;echo`BEST" -// must produce one line: TESTBEST setup_file_in_str(&pipe_str, p); /* now send results of command back into original context */ -// FIXME: must not do quote parsing of the output! -// Example: echo "TEST`echo '$(echo ZZ)'`BEST" -// must produce TEST$(echo ZZ)BEST, not TESTZZBEST. -// Example: echo "TEST`echo "'"`BEST" -// must produce TEST'BEST -// (maybe by setting all chars flagged as literals in map[]?) - - retcode = parse_stream(dest, ctx, &pipe_str, NULL); - /* XXX In case of a syntax error, should we try to kill the child? - * That would be tough to do right, so just read until EOF. */ - if (retcode == 1) { - while (b_getch(&pipe_str) != EOF) - /* discard */; + eol_cnt = 0; + while ((ch = b_getch(&pipe_str)) != EOF) { + if (ch == '\n') { + eol_cnt++; + continue; + } + while (eol_cnt) { + b_addqchr(dest, '\n', dest->quote); + eol_cnt--; + } + b_addqchr(dest, ch, dest->quote); } debug_printf("done reading from pipe, pclose()ing\n"); diff --git a/shell/hush_test/hush-parsing/noeol.right b/shell/hush_test/hush-parsing/noeol.right deleted file mode 100644 index e427984d4..000000000 --- a/shell/hush_test/hush-parsing/noeol.right +++ /dev/null @@ -1 +0,0 @@ -HELLO diff --git a/shell/hush_test/hush-parsing/noeol.tests b/shell/hush_test/hush-parsing/noeol.tests deleted file mode 100644 index a93113a03..000000000 --- a/shell/hush_test/hush-parsing/noeol.tests +++ /dev/null @@ -1,2 +0,0 @@ -# next line has no EOL! -echo HELLO \ No newline at end of file diff --git a/shell/hush_test/hush-vars/var.right b/shell/hush_test/hush-vars/var.right deleted file mode 100644 index c13b98e38..000000000 --- a/shell/hush_test/hush-vars/var.right +++ /dev/null @@ -1,4 +0,0 @@ -http://busybox.net -http://busybox.net_abc -1 -0 diff --git a/shell/hush_test/hush-vars/var.tests b/shell/hush_test/hush-vars/var.tests deleted file mode 100644 index b0637ea6b..000000000 --- a/shell/hush_test/hush-vars/var.tests +++ /dev/null @@ -1,10 +0,0 @@ -URL=http://busybox.net - -echo $URL -echo ${URL}_abc - -true -false; echo $? -true -# BUG: prints 0, must be 1 -{ false; echo $?; } diff --git a/shell/hush_test/run-all b/shell/hush_test/run-all deleted file mode 100644 index 2c2bac6d2..000000000 --- a/shell/hush_test/run-all +++ /dev/null @@ -1,59 +0,0 @@ -#!/bin/sh - -test -x hush || { echo "No ./hush?!"; exit; } - -PATH="$PWD:$PATH" # for hush and recho/zecho/printenv -export PATH - -THIS_SH="$PWD/hush" -export THIS_SH - -do_test() -{ - test -d "$1" || return 0 - ( - cd "$1" || { echo "cannot cd $1!"; exit 1; } - for x in run-*; do - test -f "$x" || continue - case "$x" in - "$0"|run-minimal|run-gprof) ;; - *.orig|*~) ;; - #*) echo $x ; sh $x ;; - *) - sh "$x" >"../$1-$x.fail" 2>&1 && \ - { echo "$1/$x: ok"; rm "../$1-$x.fail"; } || echo "$1/$x: fail"; - ;; - esac - done - # Many bash run-XXX scripts just do this, - # no point in duplication it all over the place - for x in *.tests; do - test -x "$x" || continue - name="${x%%.tests}" - test -f "$name.right" || continue - { - "$THIS_SH" "./$x" >"$name.xx" 2>&1 - diff -u "$name.xx" "$name.right" >"../$1-$x.fail" && rm -f "$name.xx" "../$1-$x.fail" - } && echo "$1/$x: ok" || echo "$1/$x: fail" - done - ) -} - -# main part of this script -# Usage: run-all [directories] - -if [ $# -lt 1 ]; then - # All sub directories - modules=`ls -d hush-*` - - for module in $modules; do - do_test $module - done -else - while [ $# -ge 1 ]; do - if [ -d $1 ]; then - do_test $1 - fi - shift - done -fi -- cgit v1.2.3