hush: fix job control with eval /bin/external_prog

hush: fix parsing of unterminated "str with no EOL
hush: improved make_string() (smaller, faster, needs less RAM)
hush: renamed several functions
This commit is contained in:
Denis Vlasenko 2007-05-23 13:01:10 +00:00
parent 1a7358612f
commit 170435c575
3 changed files with 96 additions and 92 deletions

View File

@ -117,6 +117,9 @@ extern char **environ; /* This is in <unistd.h>, but protected with __USE_GNU */
#define DEBUG_EXPAND 1 #define DEBUG_EXPAND 1
#endif #endif
/* Keep unconditionally on for now */
#define ENABLE_HUSH_DEBUG 1
#ifndef debug_printf_clean #ifndef debug_printf_clean
/* broken, of course, but OK for testing */ /* broken, of course, but OK for testing */
static const char *indenter(int i) static const char *indenter(int i)
@ -497,13 +500,12 @@ static int process_command_subs(o_string *dest, struct p_context *ctx, struct in
#endif #endif
static int parse_group(o_string *dest, struct p_context *ctx, struct in_str *input, int ch); static int parse_group(o_string *dest, struct p_context *ctx, struct in_str *input, int ch);
static const char *lookup_param(const char *src); static const char *lookup_param(const char *src);
static char *make_string(char **inp);
static int handle_dollar(o_string *dest, struct p_context *ctx, struct in_str *input); static int handle_dollar(o_string *dest, struct p_context *ctx, struct in_str *input);
static int parse_stream(o_string *dest, struct p_context *ctx, struct in_str *input0, const char *end_trigger); static int parse_stream(o_string *dest, struct p_context *ctx, struct in_str *input0, const char *end_trigger);
/* setup: */ /* setup: */
static int parse_stream_outer(struct in_str *inp, int parse_flag); static int parse_and_run_stream(struct in_str *inp, int parse_flag);
static int parse_string_outer(const char *s, int parse_flag); static int parse_and_run_string(const char *s, int parse_flag);
static int parse_file_outer(FILE *f); static int parse_and_run_file(FILE *f);
/* job management: */ /* job management: */
static int checkjobs(struct pipe* fg_pipe); static int checkjobs(struct pipe* fg_pipe);
#if ENABLE_HUSH_JOB #if ENABLE_HUSH_JOB
@ -515,9 +517,11 @@ static void delete_finished_bg_job(struct pipe *pi);
int checkjobs_and_fg_shell(struct pipe* fg_pipe); /* never called */ int checkjobs_and_fg_shell(struct pipe* fg_pipe); /* never called */
#endif #endif
/* local variable support */ /* local variable support */
static char **expand_variables_to_list(char **argv); static char **expand_strvec_to_strvec(char **argv);
/* used for eval */
static char *expand_strvec_to_string(char **argv);
/* used for expansion of right hand of assignments */ /* used for expansion of right hand of assignments */
static char *expand_variables_to_string(const char *str); static char *expand_string_to_string(const char *str);
static const char *get_local_var(const char *var); static const char *get_local_var(const char *var);
static int set_local_var(const char *s, int flg_export); static int set_local_var(const char *s, int flg_export);
static void unset_local_var(const char *name); static void unset_local_var(const char *name);
@ -716,12 +720,11 @@ static const char *set_cwd(void)
/* built-in 'eval' handler */ /* built-in 'eval' handler */
static int builtin_eval(char **argv) static int builtin_eval(char **argv)
{ {
char *str = NULL;
int rcode = EXIT_SUCCESS; int rcode = EXIT_SUCCESS;
if (argv[1]) { if (argv[1]) {
str = make_string(argv + 1); char *str = expand_strvec_to_string(argv + 1);
parse_string_outer(str, PARSEFLAG_EXIT_FROM_LOOP | parse_and_run_string(str, PARSEFLAG_EXIT_FROM_LOOP |
PARSEFLAG_SEMICOLON); PARSEFLAG_SEMICOLON);
free(str); free(str);
rcode = last_return_code; rcode = last_return_code;
@ -732,9 +735,9 @@ static int builtin_eval(char **argv)
/* built-in 'cd <path>' handler */ /* built-in 'cd <path>' handler */
static int builtin_cd(char **argv) static int builtin_cd(char **argv)
{ {
char *newdir; const char *newdir;
if (argv[1] == NULL) if (argv[1] == NULL)
newdir = getenv("HOME"); newdir = getenv("HOME") ? : "/";
else else
newdir = argv[1]; newdir = argv[1];
if (chdir(newdir)) { if (chdir(newdir)) {
@ -999,7 +1002,7 @@ static int builtin_source(char **argv)
* (pointer only is OK!) on this stack frame, * (pointer only is OK!) on this stack frame,
* set global_argv=argv+1, recurse, and restore. */ * set global_argv=argv+1, recurse, and restore. */
mark_open(fileno(input)); mark_open(fileno(input));
status = parse_file_outer(input); status = parse_and_run_file(input);
mark_closed(fileno(input)); mark_closed(fileno(input));
fclose(input); fclose(input);
return status; return status;
@ -1146,12 +1149,10 @@ static void get_user_input(struct in_str *i)
prompt_str = setup_prompt_string(i->promptmode); prompt_str = setup_prompt_string(i->promptmode);
#if ENABLE_FEATURE_EDITING #if ENABLE_FEATURE_EDITING
/* /* Enable command line editing only while a command line
** enable command line editing only while a command line * is actually being read; otherwise, we'll end up bequeathing
** is actually being read; otherwise, we'll end up bequeathing * atexit() handlers and other unwanted stuff to our
** atexit() handlers and other unwanted stuff to our * child processes (rob@sysgo.de) */
** child processes (rob@sysgo.de)
*/
r = read_line_input(prompt_str, user_input_buf, BUFSIZ-1, line_input_state); r = read_line_input(prompt_str, user_input_buf, BUFSIZ-1, line_input_state);
i->eof_flag = (r < 0); i->eof_flag = (r < 0);
if (i->eof_flag) { /* EOF/error detected */ if (i->eof_flag) { /* EOF/error detected */
@ -1343,8 +1344,8 @@ static void pseudo_exec_argv(char **argv)
debug_printf_exec("pid %d environment modification: %s\n", debug_printf_exec("pid %d environment modification: %s\n",
getpid(), argv[i]); getpid(), argv[i]);
// FIXME: vfork case?? // FIXME: vfork case??
p = expand_variables_to_string(argv[i]); p = expand_string_to_string(argv[i]);
putenv(p == argv[i] ? xstrdup(p) : p); putenv(p);
} }
argv += i; argv += i;
/* If a variable is assigned in a forest, and nobody listens, /* If a variable is assigned in a forest, and nobody listens,
@ -1354,7 +1355,7 @@ static void pseudo_exec_argv(char **argv)
_exit(EXIT_SUCCESS); _exit(EXIT_SUCCESS);
} }
argv = expand_variables_to_list(argv); argv = expand_strvec_to_strvec(argv);
/* /*
* Check if the command matches any of the builtins. * Check if the command matches any of the builtins.
@ -1652,8 +1653,8 @@ static int checkjobs_and_fg_shell(struct pipe* fg_pipe)
pid_t p; pid_t p;
int rcode = checkjobs(fg_pipe); int rcode = checkjobs(fg_pipe);
/* Job finished, move the shell to the foreground */ /* Job finished, move the shell to the foreground */
p = getpgid(0); p = getpgid(0); /* pgid of our process */
debug_printf("fg'ing ourself: getpgid(0)=%d\n", (int)p); debug_printf_jobs("fg'ing ourself: getpgid(0)=%d\n", (int)p);
if (tcsetpgrp(interactive_fd, p) && errno != ENOTTY) if (tcsetpgrp(interactive_fd, p) && errno != ENOTTY)
bb_perror_msg("tcsetpgrp-4a"); bb_perror_msg("tcsetpgrp-4a");
return rcode; return rcode;
@ -1675,6 +1676,9 @@ static int checkjobs_and_fg_shell(struct pipe* fg_pipe)
* subshell, when that is in fact necessary. The subshell process * subshell, when that is in fact necessary. The subshell process
* now has its stdout directed to the input of the appropriate pipe, * now has its stdout directed to the input of the appropriate pipe,
* so this routine is noticeably simpler. * so this routine is noticeably simpler.
*
* Returns -1 only if started some children. IOW: we have to
* mask out retvals of builtins etc with 0xff!
*/ */
static int run_pipe_real(struct pipe *pi) static int run_pipe_real(struct pipe *pi)
{ {
@ -1710,7 +1714,7 @@ static int run_pipe_real(struct pipe *pi)
rcode = run_list_real(child->group); rcode = run_list_real(child->group);
restore_redirects(squirrel); restore_redirects(squirrel);
debug_printf_exec("run_pipe_real return %d\n", rcode); debug_printf_exec("run_pipe_real return %d\n", rcode);
return rcode; return rcode; // do we need to add '... & 0xff' ?
} }
if (single_fg && child->argv != NULL) { if (single_fg && child->argv != NULL) {
@ -1739,21 +1743,16 @@ static int run_pipe_real(struct pipe *pi)
export_me = 1; export_me = 1;
} }
free(name); free(name);
p = expand_variables_to_string(argv[i]); p = expand_string_to_string(argv[i]);
set_local_var(p, export_me); set_local_var(p, export_me);
if (p != argv[i]) free(p);
free(p);
} }
return EXIT_SUCCESS; /* don't worry about errors in set_local_var() yet */ return EXIT_SUCCESS; /* don't worry about errors in set_local_var() yet */
} }
for (i = 0; is_assignment(argv[i]); i++) { for (i = 0; is_assignment(argv[i]); i++) {
p = expand_variables_to_string(argv[i]); p = expand_string_to_string(argv[i]);
if (p != argv[i]) { //sp: child->sp--;
//sp: child->sp--; putenv(p);
putenv(p);
} else {
putenv(xstrdup(p));
}
} }
for (x = bltins; x->cmd; x++) { for (x = bltins; x->cmd; x++) {
if (strcmp(argv[i], x->cmd) == 0) { if (strcmp(argv[i], x->cmd) == 0) {
@ -1770,8 +1769,8 @@ static int run_pipe_real(struct pipe *pi)
setup_redirects(child, squirrel); setup_redirects(child, squirrel);
debug_printf_exec(": builtin '%s' '%s'...\n", x->cmd, argv[i+1]); debug_printf_exec(": builtin '%s' '%s'...\n", x->cmd, argv[i+1]);
//sp: if (child->sp) /* btw we can do it unconditionally... */ //sp: if (child->sp) /* btw we can do it unconditionally... */
argv_expanded = expand_variables_to_list(argv + i); argv_expanded = expand_strvec_to_strvec(argv + i);
rcode = x->function(argv_expanded); rcode = x->function(argv_expanded) & 0xff;
free(argv_expanded); free(argv_expanded);
restore_redirects(squirrel); restore_redirects(squirrel);
debug_printf_exec("run_pipe_real return %d\n", rcode); debug_printf_exec("run_pipe_real return %d\n", rcode);
@ -1786,9 +1785,9 @@ static int run_pipe_real(struct pipe *pi)
save_nofork_data(&nofork_save); save_nofork_data(&nofork_save);
argv_expanded = argv + i; argv_expanded = argv + i;
//sp: if (child->sp) //sp: if (child->sp)
argv_expanded = expand_variables_to_list(argv + i); argv_expanded = expand_strvec_to_strvec(argv + i);
debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", argv_expanded[0], argv_expanded[1]); debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", argv_expanded[0], argv_expanded[1]);
rcode = run_nofork_applet_prime(&nofork_save, a, argv_expanded); rcode = run_nofork_applet_prime(&nofork_save, a, argv_expanded) & 0xff;
free(argv_expanded); free(argv_expanded);
restore_redirects(squirrel); restore_redirects(squirrel);
debug_printf_exec("run_pipe_real return %d\n", rcode); debug_printf_exec("run_pipe_real return %d\n", rcode);
@ -1832,7 +1831,7 @@ static int run_pipe_real(struct pipe *pi)
/* Every child adds itself to new process group /* Every child adds itself to new process group
* with pgid == pid of first child in pipe */ * with pgid == pid of first child in pipe */
#if ENABLE_HUSH_JOB #if ENABLE_HUSH_JOB
if (interactive_fd) { if (run_list_level == 1 && interactive_fd) {
/* Don't do pgrp restore anymore on fatal signals */ /* Don't do pgrp restore anymore on fatal signals */
set_fatal_sighandler(SIG_DFL); set_fatal_sighandler(SIG_DFL);
if (pi->pgrp < 0) /* true for 1st process only */ if (pi->pgrp < 0) /* true for 1st process only */
@ -2078,7 +2077,7 @@ static int run_list_real(struct pipe *pi)
if (!pi->next->progs->argv) if (!pi->next->progs->argv)
continue; continue;
/* create list of variable values */ /* create list of variable values */
for_list = expand_variables_to_list(pi->next->progs->argv); for_list = expand_strvec_to_strvec(pi->next->progs->argv);
for_lcur = for_list; for_lcur = for_list;
for_varname = pi->progs->argv[0]; for_varname = pi->progs->argv[0];
pi->progs->argv[0] = NULL; pi->progs->argv[0] = NULL;
@ -2122,24 +2121,24 @@ static int run_list_real(struct pipe *pi)
* of run_pipe_real(), and we don't need to wait for anything. */ * of run_pipe_real(), and we don't need to wait for anything. */
} else if (pi->followup == PIPE_BG) { } else if (pi->followup == PIPE_BG) {
/* What does bash do with attempts to background builtins? */ /* What does bash do with attempts to background builtins? */
/* Even bash 3.2 doesn't do that well with nested bg: /* Even bash 3.2 doesn't do that well with nested bg:
* try "{ { sleep 10; echo DEEP; } & echo HERE; } &". * try "{ { sleep 10; echo DEEP; } & echo HERE; } &".
* I'm considering NOT treating inner bgs as jobs - * I'm NOT treating inner &'s as jobs */
* thus maybe "if (run_list_level == 1 && pi->followup == PIPE_BG)"
* above? */
#if ENABLE_HUSH_JOB #if ENABLE_HUSH_JOB
insert_bg_job(pi); if (run_list_level == 1)
insert_bg_job(pi);
#endif #endif
rcode = EXIT_SUCCESS; rcode = EXIT_SUCCESS;
} else { } else {
#if ENABLE_HUSH_JOB #if ENABLE_HUSH_JOB
/* Paranoia, just "interactive_fd" should be enough */ /* Paranoia, just "interactive_fd" should be enough? */
if (run_list_level == 1 && interactive_fd) { if (run_list_level == 1 && interactive_fd) {
/* waits for completion, then fg's main shell */
rcode = checkjobs_and_fg_shell(pi); rcode = checkjobs_and_fg_shell(pi);
} else } else
#endif #endif
{ {
/* this one just waits for completion */
rcode = checkjobs(pi); rcode = checkjobs(pi);
} }
debug_printf_exec(": checkjobs returned %d\n", rcode); debug_printf_exec(": checkjobs returned %d\n", rcode);
@ -2343,7 +2342,7 @@ static int xglob(o_string *dest, int flags, glob_t *pglob)
return gr; return gr;
} }
/* expand_variables_to_list() takes a list of strings, expands /* expand_strvec_to_strvec() takes a list of strings, expands
* all variable references within and returns a pointer to * all variable references within and returns a pointer to
* a list of expanded strings, possibly with larger number * a list of expanded strings, possibly with larger number
* of strings. (Think VAR="a b"; echo $VAR). * of strings. (Think VAR="a b"; echo $VAR).
@ -2629,29 +2628,51 @@ static char **expand_variables(char **argv, char or_mask)
debug_printf_expand("used_space=%d\n", pos - (char*)list); debug_printf_expand("used_space=%d\n", pos - (char*)list);
} }
#endif #endif
/* To be removed / made conditional later. */ if (ENABLE_HUSH_DEBUG)
if (pos - (char*)list > len) if (pos - (char*)list > len)
bb_error_msg_and_die("BUG in varexp"); bb_error_msg_and_die("BUG in varexp");
return list; return list;
} }
static char **expand_variables_to_list(char **argv) static char **expand_strvec_to_strvec(char **argv)
{ {
return expand_variables(argv, 0); return expand_variables(argv, 0);
} }
static char *expand_variables_to_string(const char *str) static char *expand_string_to_string(const char *str)
{ {
char *argv[2], **list; char *argv[2], **list;
argv[0] = (char*)str; argv[0] = (char*)str;
argv[1] = NULL; argv[1] = NULL;
list = expand_variables(argv, 0x80); /* 0x80: make one-element expansion */ list = expand_variables(argv, 0x80); /* 0x80: make one-element expansion */
/* To be removed / made conditional later. */ if (ENABLE_HUSH_DEBUG)
if (!list[0] || list[1]) if (!list[0] || list[1])
bb_error_msg_and_die("BUG in varexp"); bb_error_msg_and_die("BUG in varexp2");
/* actually, just move string 2*sizeof(char*) bytes back */ /* actually, just move string 2*sizeof(char*) bytes back */
strcpy((char*)list, list[0]); strcpy((char*)list, list[0]);
debug_printf_expand("string_to_string='%s'\n", (char*)list);
return (char*)list;
}
static char* expand_strvec_to_string(char **argv)
{
char **list;
list = expand_variables(argv, 0x80);
/* Convert all NULs to spaces */
if (list[0]) {
int n = 1;
while (list[n]) {
if (ENABLE_HUSH_DEBUG)
if (list[n-1] + strlen(list[n-1]) + 1 != list[n])
bb_error_msg_and_die("BUG in varexp3");
list[n][-1] = ' '; /* TODO: or to ifs[0]? */
n++;
}
}
strcpy((char*)list, list[0]);
debug_printf_expand("strvec_to_string='%s'\n", (char*)list);
return (char*)list; return (char*)list;
} }
@ -2713,9 +2734,9 @@ static int set_local_var(const char *s, int flg_export)
} }
cur = xzalloc(sizeof(*cur)); cur = xzalloc(sizeof(*cur));
/*cur->next = 0;*/
cur->name = xstrdup(name); cur->name = xstrdup(name);
cur->value = xstrdup(value); cur->value = xstrdup(value);
/*cur->next = 0;*/
cur->flg_export = flg_export; cur->flg_export = flg_export;
/*cur->flg_read_only = 0;*/ /*cur->flg_read_only = 0;*/
{ {
@ -3242,31 +3263,6 @@ static const char *lookup_param(const char *src)
return p; return p;
} }
/* Make new string for parser */
static char* make_string(char **inp)
{
char *p;
char *str = NULL;
int n;
int val_len;
int len = 0;
for (n = 0; inp[n]; n++) {
p = expand_variables_to_string(inp[n]);
val_len = strlen(p);
str = xrealloc(str, len + val_len + 3); /* +3: space, '\n', <nul>*/
str[len++] = ' ';
strcpy(str + len, p);
len += val_len;
if (p != inp[n]) free(p);
}
/* We do not check for case where loop had no iterations at all
* - cannot happen? */
str[len] = '\n';
str[len+1] = '\0';
return str;
}
/* return code: 0 for OK, 1 for syntax error */ /* return code: 0 for OK, 1 for syntax error */
static int handle_dollar(o_string *dest, struct p_context *ctx, struct in_str *input) static int handle_dollar(o_string *dest, struct p_context *ctx, struct in_str *input)
{ {
@ -3358,9 +3354,9 @@ static int parse_stream(o_string *dest, struct p_context *ctx,
debug_printf_parse("parse_stream entered, end_trigger='%s'\n", end_trigger); debug_printf_parse("parse_stream entered, end_trigger='%s'\n", end_trigger);
while (1) { while (1) {
ch = b_getch(input);
m = CHAR_IFS; m = CHAR_IFS;
next = '\0'; next = '\0';
ch = b_getch(input);
if (ch != EOF) { if (ch != EOF) {
m = charmap[ch]; m = charmap[ch];
if (ch != '\n') if (ch != '\n')
@ -3371,6 +3367,11 @@ static int parse_stream(o_string *dest, struct p_context *ctx,
if (m == CHAR_ORDINARY if (m == CHAR_ORDINARY
|| (m != CHAR_SPECIAL && dest->quote) || (m != CHAR_SPECIAL && dest->quote)
) { ) {
if (ch == EOF) {
syntax();
debug_printf_parse("parse_stream return 1: unterminated \"\n");
return 1;
}
b_addqchr(dest, ch, dest->quote); b_addqchr(dest, ch, dest->quote);
continue; continue;
} }
@ -3564,8 +3565,8 @@ static void update_charmap(void)
} }
/* most recursion does not come through here, the exception is /* most recursion does not come through here, the exception is
* from builtin_source() */ * from builtin_source() and builtin_eval() */
static int parse_stream_outer(struct in_str *inp, int parse_flag) static int parse_and_run_stream(struct in_str *inp, int parse_flag)
{ {
struct p_context ctx; struct p_context ctx;
o_string temp = NULL_O_STRING; o_string temp = NULL_O_STRING;
@ -3607,19 +3608,19 @@ static int parse_stream_outer(struct in_str *inp, int parse_flag)
return 0; return 0;
} }
static int parse_string_outer(const char *s, int parse_flag) static int parse_and_run_string(const char *s, int parse_flag)
{ {
struct in_str input; struct in_str input;
setup_string_in_str(&input, s); setup_string_in_str(&input, s);
return parse_stream_outer(&input, parse_flag); return parse_and_run_stream(&input, parse_flag);
} }
static int parse_file_outer(FILE *f) static int parse_and_run_file(FILE *f)
{ {
int rcode; int rcode;
struct in_str input; struct in_str input;
setup_file_in_str(&input, f); setup_file_in_str(&input, f);
rcode = parse_stream_outer(&input, PARSEFLAG_SEMICOLON); rcode = parse_and_run_stream(&input, PARSEFLAG_SEMICOLON);
return rcode; return rcode;
} }
@ -3698,7 +3699,7 @@ int hush_main(int argc, char **argv)
input = fopen("/etc/profile", "r"); input = fopen("/etc/profile", "r");
if (input != NULL) { if (input != NULL) {
mark_open(fileno(input)); mark_open(fileno(input));
parse_file_outer(input); parse_and_run_file(input);
mark_closed(fileno(input)); mark_closed(fileno(input));
fclose(input); fclose(input);
} }
@ -3710,7 +3711,7 @@ int hush_main(int argc, char **argv)
case 'c': case 'c':
global_argv = argv + optind; global_argv = argv + optind;
global_argc = argc - optind; global_argc = argc - optind;
opt = parse_string_outer(optarg, PARSEFLAG_SEMICOLON); opt = parse_and_run_string(optarg, PARSEFLAG_SEMICOLON);
goto final_return; goto final_return;
case 'i': case 'i':
/* Well, we cannot just declare interactiveness, /* Well, we cannot just declare interactiveness,
@ -3791,7 +3792,7 @@ int hush_main(int argc, char **argv)
#endif #endif
if (argv[optind] == NULL) { if (argv[optind] == NULL) {
opt = parse_file_outer(stdin); opt = parse_and_run_file(stdin);
goto final_return; goto final_return;
} }
@ -3799,7 +3800,7 @@ int hush_main(int argc, char **argv)
global_argv = argv + optind; global_argv = argv + optind;
global_argc = argc - optind; global_argc = argc - optind;
input = xfopen(argv[optind], "r"); input = xfopen(argv[optind], "r");
opt = parse_file_outer(input); opt = parse_and_run_file(input);
#if ENABLE_FEATURE_CLEAN_UP #if ENABLE_FEATURE_CLEAN_UP
fclose(input); fclose(input);

View File

@ -0,0 +1 @@
hush: syntax error hush.c:3370

View File

@ -0,0 +1,2 @@
# last line has no EOL!
echo "unterminated