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
This commit is contained in:
Denis Vlasenko 2007-05-11 12:56:43 +00:00
parent e0a336747c
commit 3e9aaae5dc
6 changed files with 46 additions and 118 deletions

View File

@ -317,10 +317,10 @@ typedef struct {
/* I can almost use ordinary FILE *. Is open_memstream() universally /* I can almost use ordinary FILE *. Is open_memstream() universally
* available? Where is it documented? */ * available? Where is it documented? */
struct in_str { struct in_str {
union {
const char *p; const char *p;
int cached_ch; /* 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 #if ENABLE_HUSH_INTERACTIVE
int __promptme; int __promptme;
int promptmode; 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) 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)) if (b_check_space(o, 1))
return B_NOSPAC; return B_NOSPAC;
o->data[o->length] = ch; o->data[o->length] = ch;
@ -1079,12 +1079,13 @@ static const char* setup_prompt_string(int promptmode)
static line_input_t *line_input_state; static line_input_t *line_input_state;
#endif #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]; static char the_command[ENABLE_FEATURE_EDITING ? BUFSIZ : 2];
int r; int r;
const char *prompt_str; const char *prompt_str;
prompt_str = setup_prompt_string(i->promptmode); prompt_str = setup_prompt_string(i->promptmode);
#if ENABLE_FEATURE_EDITING #if ENABLE_FEATURE_EDITING
/* /*
@ -1094,15 +1095,19 @@ static int get_user_input(struct in_str *i)
** child processes (rob@sysgo.de) ** child processes (rob@sysgo.de)
*/ */
r = read_line_input(prompt_str, the_command, BUFSIZ-1, line_input_state); 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 #else
fputs(prompt_str, stdout); fputs(prompt_str, stdout);
fflush(stdout); fflush(stdout);
the_command[0] = r = fgetc(i->file); the_command[0] = r = fgetc(i->file);
/*the_command[1] = '\0'; - already is and never changed */ /*the_command[1] = '\0'; - already is and never changed */
i->eof_flag = (r == EOF);
#endif #endif
fflush(stdout);
i->p = the_command; i->p = the_command;
return r; /* < 0 == EOF. Not meaningful otherwise */
} }
#endif /* INTERACTIVE */ #endif /* INTERACTIVE */
@ -1112,33 +1117,30 @@ static int file_get(struct in_str *i)
{ {
int ch; int ch;
ch = 0;
/* If there is data waiting, eat it up */ /* If there is data waiting, eat it up */
if (i->cached_ch) { if (i->p && *i->p) {
ch = i->cached_ch ^ 0x100; take_cached:
if (ch != EOF) ch = *i->p++;
i->cached_ch = 0; if (i->eof_flag && !*i->p)
ch = EOF;
} else { } else {
/* need to double check i->file because we might be doing something /* need to double check i->file because we might be doing something
* more complicated by now, like sourcing or substituting. */ * more complicated by now, like sourcing or substituting. */
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_INTERACTIVE
if (interactive_fd && i->__promptme && i->file == stdin) { if (interactive_fd && i->__promptme && i->file == stdin) {
while (!i->p || !(interactive_fd && i->p[0])) { do {
if (get_user_input(i) < 0) get_user_input(i);
return EOF; } while (!*i->p); /* need non-empty line */
}
i->promptmode = 2; i->promptmode = 2;
i->__promptme = 0; i->__promptme = 0;
if (i->p && *i->p) { goto take_cached;
ch = *i->p++;
}
} else } else
#endif #endif
{ {
ch = fgetc(i->file); 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 ENABLE_HUSH_INTERACTIVE
if (ch == '\n') if (ch == '\n')
i->__promptme = 1; i->__promptme = 1;
@ -1152,12 +1154,17 @@ static int file_get(struct in_str *i)
static int file_peek(struct in_str *i) static int file_peek(struct in_str *i)
{ {
int ch; int ch;
if (i->cached_ch) { if (i->p && *i->p) {
return i->cached_ch ^ 0x100; if (i->eof_flag && !i->p[1])
return EOF;
return *i->p;
} }
ch = fgetc(i->file); ch = fgetc(i->file);
i->cached_ch = ch ^ 0x100; /* ^ 0x100 so that it is never 0 */ i->eof_flag = (ch == EOF);
debug_printf("file_peek: got a %d '%c'\n", ch, ch); 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; return ch;
} }
@ -1182,6 +1189,7 @@ static void setup_string_in_str(struct in_str *i, const char *s)
i->promptmode = 1; i->promptmode = 1;
#endif #endif
i->p = s; i->p = s;
i->eof_flag = 0;
} }
static void mark_open(int fd) 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, static int process_command_subs(o_string *dest, struct p_context *ctx,
struct in_str *input, const char *subst_end) struct in_str *input, const char *subst_end)
{ {
int retcode; int retcode, ch, eol_cnt;
o_string result = NULL_O_STRING; o_string result = NULL_O_STRING;
struct p_context inner; struct p_context inner;
FILE *p; FILE *p;
struct in_str pipe_str; struct in_str pipe_str;
initialize_context(&inner); initialize_context(&inner);
/* recursion to generate command */ /* 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); p = generate_stream_from_list(inner.list_head);
if (p == NULL) return 1; if (p == NULL) return 1;
mark_open(fileno(p)); 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); setup_file_in_str(&pipe_str, p);
/* now send results of command back into original context */ /* now send results of command back into original context */
// FIXME: must not do quote parsing of the output! eol_cnt = 0;
// Example: echo "TEST`echo '$(echo ZZ)'`BEST" while ((ch = b_getch(&pipe_str)) != EOF) {
// must produce TEST$(echo ZZ)BEST, not TESTZZBEST. if (ch == '\n') {
// Example: echo "TEST`echo "'"`BEST" eol_cnt++;
// must produce TEST'BEST continue;
// (maybe by setting all chars flagged as literals in map[]?) }
while (eol_cnt) {
retcode = parse_stream(dest, ctx, &pipe_str, NULL); b_addqchr(dest, '\n', dest->quote);
/* XXX In case of a syntax error, should we try to kill the child? eol_cnt--;
* That would be tough to do right, so just read until EOF. */ }
if (retcode == 1) { b_addqchr(dest, ch, dest->quote);
while (b_getch(&pipe_str) != EOF)
/* discard */;
} }
debug_printf("done reading from pipe, pclose()ing\n"); debug_printf("done reading from pipe, pclose()ing\n");

View File

@ -1 +0,0 @@
HELLO

View File

@ -1,2 +0,0 @@
# next line has no EOL!
echo HELLO

View File

@ -1,4 +0,0 @@
http://busybox.net
http://busybox.net_abc
1
0

View File

@ -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 $?; }

View File

@ -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