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:
parent
1a7358612f
commit
170435c575
185
shell/hush.c
185
shell/hush.c
@ -117,6 +117,9 @@ extern char **environ; /* This is in <unistd.h>, but protected with __USE_GNU */
|
||||
#define DEBUG_EXPAND 1
|
||||
#endif
|
||||
|
||||
/* Keep unconditionally on for now */
|
||||
#define ENABLE_HUSH_DEBUG 1
|
||||
|
||||
#ifndef debug_printf_clean
|
||||
/* broken, of course, but OK for testing */
|
||||
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
|
||||
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 char *make_string(char **inp);
|
||||
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);
|
||||
/* setup: */
|
||||
static int parse_stream_outer(struct in_str *inp, int parse_flag);
|
||||
static int parse_string_outer(const char *s, int parse_flag);
|
||||
static int parse_file_outer(FILE *f);
|
||||
static int parse_and_run_stream(struct in_str *inp, int parse_flag);
|
||||
static int parse_and_run_string(const char *s, int parse_flag);
|
||||
static int parse_and_run_file(FILE *f);
|
||||
/* job management: */
|
||||
static int checkjobs(struct pipe* fg_pipe);
|
||||
#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 */
|
||||
#endif
|
||||
/* 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 */
|
||||
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 int set_local_var(const char *s, int flg_export);
|
||||
static void unset_local_var(const char *name);
|
||||
@ -716,12 +720,11 @@ static const char *set_cwd(void)
|
||||
/* built-in 'eval' handler */
|
||||
static int builtin_eval(char **argv)
|
||||
{
|
||||
char *str = NULL;
|
||||
int rcode = EXIT_SUCCESS;
|
||||
|
||||
if (argv[1]) {
|
||||
str = make_string(argv + 1);
|
||||
parse_string_outer(str, PARSEFLAG_EXIT_FROM_LOOP |
|
||||
char *str = expand_strvec_to_string(argv + 1);
|
||||
parse_and_run_string(str, PARSEFLAG_EXIT_FROM_LOOP |
|
||||
PARSEFLAG_SEMICOLON);
|
||||
free(str);
|
||||
rcode = last_return_code;
|
||||
@ -732,9 +735,9 @@ static int builtin_eval(char **argv)
|
||||
/* built-in 'cd <path>' handler */
|
||||
static int builtin_cd(char **argv)
|
||||
{
|
||||
char *newdir;
|
||||
const char *newdir;
|
||||
if (argv[1] == NULL)
|
||||
newdir = getenv("HOME");
|
||||
newdir = getenv("HOME") ? : "/";
|
||||
else
|
||||
newdir = argv[1];
|
||||
if (chdir(newdir)) {
|
||||
@ -999,7 +1002,7 @@ static int builtin_source(char **argv)
|
||||
* (pointer only is OK!) on this stack frame,
|
||||
* set global_argv=argv+1, recurse, and restore. */
|
||||
mark_open(fileno(input));
|
||||
status = parse_file_outer(input);
|
||||
status = parse_and_run_file(input);
|
||||
mark_closed(fileno(input));
|
||||
fclose(input);
|
||||
return status;
|
||||
@ -1146,12 +1149,10 @@ static void get_user_input(struct in_str *i)
|
||||
|
||||
prompt_str = setup_prompt_string(i->promptmode);
|
||||
#if ENABLE_FEATURE_EDITING
|
||||
/*
|
||||
** enable command line editing only while a command line
|
||||
** is actually being read; otherwise, we'll end up bequeathing
|
||||
** atexit() handlers and other unwanted stuff to our
|
||||
** child processes (rob@sysgo.de)
|
||||
*/
|
||||
/* Enable command line editing only while a command line
|
||||
* is actually being read; otherwise, we'll end up bequeathing
|
||||
* atexit() handlers and other unwanted stuff to our
|
||||
* child processes (rob@sysgo.de) */
|
||||
r = read_line_input(prompt_str, user_input_buf, BUFSIZ-1, line_input_state);
|
||||
i->eof_flag = (r < 0);
|
||||
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",
|
||||
getpid(), argv[i]);
|
||||
// FIXME: vfork case??
|
||||
p = expand_variables_to_string(argv[i]);
|
||||
putenv(p == argv[i] ? xstrdup(p) : p);
|
||||
p = expand_string_to_string(argv[i]);
|
||||
putenv(p);
|
||||
}
|
||||
argv += i;
|
||||
/* 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);
|
||||
}
|
||||
|
||||
argv = expand_variables_to_list(argv);
|
||||
argv = expand_strvec_to_strvec(argv);
|
||||
|
||||
/*
|
||||
* 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;
|
||||
int rcode = checkjobs(fg_pipe);
|
||||
/* Job finished, move the shell to the foreground */
|
||||
p = getpgid(0);
|
||||
debug_printf("fg'ing ourself: getpgid(0)=%d\n", (int)p);
|
||||
p = getpgid(0); /* pgid of our process */
|
||||
debug_printf_jobs("fg'ing ourself: getpgid(0)=%d\n", (int)p);
|
||||
if (tcsetpgrp(interactive_fd, p) && errno != ENOTTY)
|
||||
bb_perror_msg("tcsetpgrp-4a");
|
||||
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
|
||||
* now has its stdout directed to the input of the appropriate pipe,
|
||||
* 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)
|
||||
{
|
||||
@ -1710,7 +1714,7 @@ static int run_pipe_real(struct pipe *pi)
|
||||
rcode = run_list_real(child->group);
|
||||
restore_redirects(squirrel);
|
||||
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) {
|
||||
@ -1739,21 +1743,16 @@ static int run_pipe_real(struct pipe *pi)
|
||||
export_me = 1;
|
||||
}
|
||||
free(name);
|
||||
p = expand_variables_to_string(argv[i]);
|
||||
p = expand_string_to_string(argv[i]);
|
||||
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 */
|
||||
}
|
||||
for (i = 0; is_assignment(argv[i]); i++) {
|
||||
p = expand_variables_to_string(argv[i]);
|
||||
if (p != argv[i]) {
|
||||
//sp: child->sp--;
|
||||
putenv(p);
|
||||
} else {
|
||||
putenv(xstrdup(p));
|
||||
}
|
||||
p = expand_string_to_string(argv[i]);
|
||||
//sp: child->sp--;
|
||||
putenv(p);
|
||||
}
|
||||
for (x = bltins; x->cmd; x++) {
|
||||
if (strcmp(argv[i], x->cmd) == 0) {
|
||||
@ -1770,8 +1769,8 @@ static int run_pipe_real(struct pipe *pi)
|
||||
setup_redirects(child, squirrel);
|
||||
debug_printf_exec(": builtin '%s' '%s'...\n", x->cmd, argv[i+1]);
|
||||
//sp: if (child->sp) /* btw we can do it unconditionally... */
|
||||
argv_expanded = expand_variables_to_list(argv + i);
|
||||
rcode = x->function(argv_expanded);
|
||||
argv_expanded = expand_strvec_to_strvec(argv + i);
|
||||
rcode = x->function(argv_expanded) & 0xff;
|
||||
free(argv_expanded);
|
||||
restore_redirects(squirrel);
|
||||
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);
|
||||
argv_expanded = argv + i;
|
||||
//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]);
|
||||
rcode = run_nofork_applet_prime(&nofork_save, a, argv_expanded);
|
||||
rcode = run_nofork_applet_prime(&nofork_save, a, argv_expanded) & 0xff;
|
||||
free(argv_expanded);
|
||||
restore_redirects(squirrel);
|
||||
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
|
||||
* with pgid == pid of first child in pipe */
|
||||
#if ENABLE_HUSH_JOB
|
||||
if (interactive_fd) {
|
||||
if (run_list_level == 1 && interactive_fd) {
|
||||
/* Don't do pgrp restore anymore on fatal signals */
|
||||
set_fatal_sighandler(SIG_DFL);
|
||||
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)
|
||||
continue;
|
||||
/* 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_varname = pi->progs->argv[0];
|
||||
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. */
|
||||
} else if (pi->followup == PIPE_BG) {
|
||||
/* What does bash do with attempts to background builtins? */
|
||||
|
||||
/* Even bash 3.2 doesn't do that well with nested bg:
|
||||
* try "{ { sleep 10; echo DEEP; } & echo HERE; } &".
|
||||
* I'm considering NOT treating inner bgs as jobs -
|
||||
* thus maybe "if (run_list_level == 1 && pi->followup == PIPE_BG)"
|
||||
* above? */
|
||||
* I'm NOT treating inner &'s as jobs */
|
||||
#if ENABLE_HUSH_JOB
|
||||
insert_bg_job(pi);
|
||||
if (run_list_level == 1)
|
||||
insert_bg_job(pi);
|
||||
#endif
|
||||
rcode = EXIT_SUCCESS;
|
||||
} else {
|
||||
#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) {
|
||||
/* waits for completion, then fg's main shell */
|
||||
rcode = checkjobs_and_fg_shell(pi);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
/* this one just waits for completion */
|
||||
rcode = checkjobs(pi);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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
|
||||
* a list of expanded strings, possibly with larger number
|
||||
* 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);
|
||||
}
|
||||
#endif
|
||||
/* To be removed / made conditional later. */
|
||||
if (pos - (char*)list > len)
|
||||
bb_error_msg_and_die("BUG in varexp");
|
||||
if (ENABLE_HUSH_DEBUG)
|
||||
if (pos - (char*)list > len)
|
||||
bb_error_msg_and_die("BUG in varexp");
|
||||
return list;
|
||||
}
|
||||
|
||||
static char **expand_variables_to_list(char **argv)
|
||||
static char **expand_strvec_to_strvec(char **argv)
|
||||
{
|
||||
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;
|
||||
|
||||
argv[0] = (char*)str;
|
||||
argv[1] = NULL;
|
||||
list = expand_variables(argv, 0x80); /* 0x80: make one-element expansion */
|
||||
/* To be removed / made conditional later. */
|
||||
if (!list[0] || list[1])
|
||||
bb_error_msg_and_die("BUG in varexp");
|
||||
if (ENABLE_HUSH_DEBUG)
|
||||
if (!list[0] || list[1])
|
||||
bb_error_msg_and_die("BUG in varexp2");
|
||||
/* actually, just move string 2*sizeof(char*) bytes back */
|
||||
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;
|
||||
}
|
||||
|
||||
@ -2713,9 +2734,9 @@ static int set_local_var(const char *s, int flg_export)
|
||||
}
|
||||
|
||||
cur = xzalloc(sizeof(*cur));
|
||||
/*cur->next = 0;*/
|
||||
cur->name = xstrdup(name);
|
||||
cur->value = xstrdup(value);
|
||||
/*cur->next = 0;*/
|
||||
cur->flg_export = flg_export;
|
||||
/*cur->flg_read_only = 0;*/
|
||||
{
|
||||
@ -3242,31 +3263,6 @@ static const char *lookup_param(const char *src)
|
||||
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 */
|
||||
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);
|
||||
|
||||
while (1) {
|
||||
ch = b_getch(input);
|
||||
m = CHAR_IFS;
|
||||
next = '\0';
|
||||
ch = b_getch(input);
|
||||
if (ch != EOF) {
|
||||
m = charmap[ch];
|
||||
if (ch != '\n')
|
||||
@ -3371,6 +3367,11 @@ static int parse_stream(o_string *dest, struct p_context *ctx,
|
||||
if (m == CHAR_ORDINARY
|
||||
|| (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);
|
||||
continue;
|
||||
}
|
||||
@ -3564,8 +3565,8 @@ static void update_charmap(void)
|
||||
}
|
||||
|
||||
/* most recursion does not come through here, the exception is
|
||||
* from builtin_source() */
|
||||
static int parse_stream_outer(struct in_str *inp, int parse_flag)
|
||||
* from builtin_source() and builtin_eval() */
|
||||
static int parse_and_run_stream(struct in_str *inp, int parse_flag)
|
||||
{
|
||||
struct p_context ctx;
|
||||
o_string temp = NULL_O_STRING;
|
||||
@ -3607,19 +3608,19 @@ static int parse_stream_outer(struct in_str *inp, int parse_flag)
|
||||
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;
|
||||
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;
|
||||
struct in_str input;
|
||||
setup_file_in_str(&input, f);
|
||||
rcode = parse_stream_outer(&input, PARSEFLAG_SEMICOLON);
|
||||
rcode = parse_and_run_stream(&input, PARSEFLAG_SEMICOLON);
|
||||
return rcode;
|
||||
}
|
||||
|
||||
@ -3698,7 +3699,7 @@ int hush_main(int argc, char **argv)
|
||||
input = fopen("/etc/profile", "r");
|
||||
if (input != NULL) {
|
||||
mark_open(fileno(input));
|
||||
parse_file_outer(input);
|
||||
parse_and_run_file(input);
|
||||
mark_closed(fileno(input));
|
||||
fclose(input);
|
||||
}
|
||||
@ -3710,7 +3711,7 @@ int hush_main(int argc, char **argv)
|
||||
case 'c':
|
||||
global_argv = argv + optind;
|
||||
global_argc = argc - optind;
|
||||
opt = parse_string_outer(optarg, PARSEFLAG_SEMICOLON);
|
||||
opt = parse_and_run_string(optarg, PARSEFLAG_SEMICOLON);
|
||||
goto final_return;
|
||||
case 'i':
|
||||
/* Well, we cannot just declare interactiveness,
|
||||
@ -3791,7 +3792,7 @@ int hush_main(int argc, char **argv)
|
||||
#endif
|
||||
|
||||
if (argv[optind] == NULL) {
|
||||
opt = parse_file_outer(stdin);
|
||||
opt = parse_and_run_file(stdin);
|
||||
goto final_return;
|
||||
}
|
||||
|
||||
@ -3799,7 +3800,7 @@ int hush_main(int argc, char **argv)
|
||||
global_argv = argv + optind;
|
||||
global_argc = argc - optind;
|
||||
input = xfopen(argv[optind], "r");
|
||||
opt = parse_file_outer(input);
|
||||
opt = parse_and_run_file(input);
|
||||
|
||||
#if ENABLE_FEATURE_CLEAN_UP
|
||||
fclose(input);
|
||||
|
1
shell/hush_test/hush-bugs/noeol3.right
Normal file
1
shell/hush_test/hush-bugs/noeol3.right
Normal file
@ -0,0 +1 @@
|
||||
hush: syntax error hush.c:3370
|
2
shell/hush_test/hush-bugs/noeol3.tests
Executable file
2
shell/hush_test/hush-bugs/noeol3.tests
Executable file
@ -0,0 +1,2 @@
|
||||
# last line has no EOL!
|
||||
echo "unterminated
|
Loading…
Reference in New Issue
Block a user