hush: fix bug in interactive shell introduced yesterday
authorDenis Vlasenko <vda.linux@googlemail.com>
Fri, 11 May 2007 12:56:43 +0000 (12:56 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Fri, 11 May 2007 12:56:43 +0000 (12:56 -0000)
hush: fix `process subst` (2 bugs)
NB: will delete and re-add hush_test in order to change file modes

shell/hush.c
shell/hush_test/hush-parsing/noeol.right [deleted file]
shell/hush_test/hush-parsing/noeol.tests [deleted file]
shell/hush_test/hush-vars/var.right [deleted file]
shell/hush_test/hush-vars/var.tests [deleted file]
shell/hush_test/run-all [deleted file]

index 32cd65c725f3016b4b6d3efaf9e10988b1ff3f0e..04fb00dc7efeb90e90b1769476016c6ce2a48503 100644 (file)
@@ -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: TEST<date>BEST
        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 (file)
index e427984..0000000
+++ /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 (file)
index a93113a..0000000
+++ /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 (file)
index c13b98e..0000000
+++ /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 (file)
index b0637ea..0000000
+++ /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 (file)
index 2c2bac6..0000000
+++ /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