hush: first stab at function support. argv passing is not coded yet.
Only very rudimentary testing was done. With function support off, code growth is zero, with it on: function old new delta run_list 2158 2339 +181 parse_stream 1929 2044 +115 find_builtin 24 67 +43 find_function - 36 +36 file_get 244 264 +20 pseudo_exec_argv 145 160 +15 free_strings - 7 +7 free_pipe 183 181 -2 done_word 735 728 -7 expand_variables 2227 2204 -23 ------------------------------------------------------------------------------ (add/remove: 2/0 grow/shrink: 5/3 up/down: 417/-32) Total: 385 bytes
This commit is contained in:
parent
835fcfd33d
commit
b7d8c0dbbd
233
shell/hush.c
233
shell/hush.c
@ -86,7 +86,7 @@
|
|||||||
*/
|
*/
|
||||||
#define HUSH_DEBUG 1
|
#define HUSH_DEBUG 1
|
||||||
/* In progress... */
|
/* In progress... */
|
||||||
#define ENABLE_HUSH_FUNCTIONS 0
|
#define ENABLE_HUSH_FUNCTIONS 1
|
||||||
|
|
||||||
|
|
||||||
#if BUILD_AS_NOMMU
|
#if BUILD_AS_NOMMU
|
||||||
@ -374,12 +374,12 @@ struct command {
|
|||||||
#define GRP_NORMAL 0
|
#define GRP_NORMAL 0
|
||||||
#define GRP_SUBSHELL 1
|
#define GRP_SUBSHELL 1
|
||||||
#if ENABLE_HUSH_FUNCTIONS
|
#if ENABLE_HUSH_FUNCTIONS
|
||||||
#define GRP_FUNCTION 2
|
# define GRP_FUNCTION 2
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct pipe {
|
struct pipe {
|
||||||
struct pipe *next;
|
struct pipe *next;
|
||||||
int num_cmds; /* total number of commands in job */
|
int num_cmds; /* total number of commands in pipe */
|
||||||
int alive_cmds; /* number of commands running (not exited) */
|
int alive_cmds; /* number of commands running (not exited) */
|
||||||
int stopped_cmds; /* number of commands alive, but stopped */
|
int stopped_cmds; /* number of commands alive, but stopped */
|
||||||
#if ENABLE_HUSH_JOB
|
#if ENABLE_HUSH_JOB
|
||||||
@ -452,6 +452,12 @@ enum {
|
|||||||
BC_CONTINUE = 2,
|
BC_CONTINUE = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct function {
|
||||||
|
struct function *next;
|
||||||
|
char *name;
|
||||||
|
struct pipe *body;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/* "Globals" within this file */
|
/* "Globals" within this file */
|
||||||
/* Sorted roughly by size (smaller offsets == smaller code) */
|
/* Sorted roughly by size (smaller offsets == smaller code) */
|
||||||
@ -504,6 +510,7 @@ struct globals {
|
|||||||
const char *cwd;
|
const char *cwd;
|
||||||
struct variable *top_var; /* = &G.shell_ver (set in main()) */
|
struct variable *top_var; /* = &G.shell_ver (set in main()) */
|
||||||
struct variable shell_ver;
|
struct variable shell_ver;
|
||||||
|
struct function *top_func;
|
||||||
/* Signal and trap handling */
|
/* Signal and trap handling */
|
||||||
// unsigned count_SIGCHLD;
|
// unsigned count_SIGCHLD;
|
||||||
// unsigned handled_SIGCHLD;
|
// unsigned handled_SIGCHLD;
|
||||||
@ -2585,6 +2592,35 @@ static void free_pipe_list(struct pipe *head, int indent)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static const struct built_in_command* find_builtin(const char *name)
|
||||||
|
{
|
||||||
|
const struct built_in_command *x;
|
||||||
|
for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
|
||||||
|
if (strcmp(name, x->cmd) != 0)
|
||||||
|
continue;
|
||||||
|
debug_printf_exec("found builtin '%s'\n", name);
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
# if ENABLE_HUSH_FUNCTIONS
|
||||||
|
static const struct function *find_function(const char *name)
|
||||||
|
{
|
||||||
|
const struct function *funcp = G.top_func;
|
||||||
|
while (funcp) {
|
||||||
|
if (strcmp(name, funcp->name) != 0)
|
||||||
|
continue;
|
||||||
|
return funcp;
|
||||||
|
debug_printf_exec("found function '%s'\n", name);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
static int run_list(struct pipe *pi);
|
||||||
|
|
||||||
#if BB_MMU
|
#if BB_MMU
|
||||||
#define pseudo_exec_argv(nommu_save, argv, assignment_cnt, argv_expanded) \
|
#define pseudo_exec_argv(nommu_save, argv, assignment_cnt, argv_expanded) \
|
||||||
pseudo_exec_argv(argv, assignment_cnt, argv_expanded)
|
pseudo_exec_argv(argv, assignment_cnt, argv_expanded)
|
||||||
@ -2627,6 +2663,11 @@ static void pseudo_exec_argv(nommu_save_t *nommu_save,
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if ENABLE_FEATURE_SH_STANDALONE || BB_MMU
|
||||||
|
if (strchr(argv[0], '/') != NULL)
|
||||||
|
goto skip;
|
||||||
|
#endif
|
||||||
|
|
||||||
/* On NOMMU, we must never block!
|
/* On NOMMU, we must never block!
|
||||||
* Example: { sleep 99999 | read line } & echo Ok
|
* Example: { sleep 99999 | read line } & echo Ok
|
||||||
* read builtin will block on read syscall, leaving parent blocked
|
* read builtin will block on read syscall, leaving parent blocked
|
||||||
@ -2640,30 +2681,38 @@ static void pseudo_exec_argv(nommu_save_t *nommu_save,
|
|||||||
*/
|
*/
|
||||||
{
|
{
|
||||||
int rcode;
|
int rcode;
|
||||||
const struct built_in_command *x;
|
const struct built_in_command *x = find_builtin(argv[0]);
|
||||||
for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
|
if (x) {
|
||||||
if (strcmp(argv[0], x->cmd) == 0) {
|
rcode = x->function(argv);
|
||||||
debug_printf_exec("running builtin '%s'\n",
|
fflush(NULL);
|
||||||
argv[0]);
|
_exit(rcode);
|
||||||
rcode = x->function(argv);
|
|
||||||
fflush(NULL);
|
|
||||||
_exit(rcode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
# if ENABLE_HUSH_FUNCTIONS
|
||||||
|
/* Check if the command matches any functions */
|
||||||
|
{
|
||||||
|
int rcode;
|
||||||
|
const struct function *funcp = find_function(argv[0]);
|
||||||
|
if (funcp) {
|
||||||
|
rcode = run_list(funcp->body);
|
||||||
|
fflush(NULL);
|
||||||
|
_exit(rcode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if ENABLE_FEATURE_SH_STANDALONE
|
#if ENABLE_FEATURE_SH_STANDALONE
|
||||||
/* Check if the command matches any busybox applets */
|
/* Check if the command matches any busybox applets */
|
||||||
if (strchr(argv[0], '/') == NULL) {
|
{
|
||||||
int a = find_applet_by_name(argv[0]);
|
int a = find_applet_by_name(argv[0]);
|
||||||
if (a >= 0) {
|
if (a >= 0) {
|
||||||
#if BB_MMU /* see above why on NOMMU it is not allowed */
|
# if BB_MMU /* see above why on NOMMU it is not allowed */
|
||||||
if (APPLET_IS_NOEXEC(a)) {
|
if (APPLET_IS_NOEXEC(a)) {
|
||||||
debug_printf_exec("running applet '%s'\n", argv[0]);
|
debug_printf_exec("running applet '%s'\n", argv[0]);
|
||||||
run_applet_no_and_exit(a, argv);
|
run_applet_no_and_exit(a, argv);
|
||||||
}
|
}
|
||||||
#endif
|
# endif
|
||||||
/* Re-exec ourselves */
|
/* Re-exec ourselves */
|
||||||
debug_printf_exec("re-execing applet '%s'\n", argv[0]);
|
debug_printf_exec("re-execing applet '%s'\n", argv[0]);
|
||||||
sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
|
sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
|
||||||
@ -2674,6 +2723,7 @@ static void pseudo_exec_argv(nommu_save_t *nommu_save,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
skip:
|
||||||
debug_printf_exec("execing '%s'\n", argv[0]);
|
debug_printf_exec("execing '%s'\n", argv[0]);
|
||||||
sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
|
sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
|
||||||
execvp(argv[0], argv);
|
execvp(argv[0], argv);
|
||||||
@ -2681,8 +2731,6 @@ static void pseudo_exec_argv(nommu_save_t *nommu_save,
|
|||||||
_exit(EXIT_FAILURE);
|
_exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int run_list(struct pipe *pi);
|
|
||||||
|
|
||||||
/* Called after [v]fork() in run_pipe
|
/* Called after [v]fork() in run_pipe
|
||||||
*/
|
*/
|
||||||
static void pseudo_exec(nommu_save_t *nommu_save,
|
static void pseudo_exec(nommu_save_t *nommu_save,
|
||||||
@ -3031,8 +3079,35 @@ static int run_pipe(struct pipe *pi)
|
|||||||
if (command->group) {
|
if (command->group) {
|
||||||
#if ENABLE_HUSH_FUNCTIONS
|
#if ENABLE_HUSH_FUNCTIONS
|
||||||
if (command->grp_type == GRP_FUNCTION) {
|
if (command->grp_type == GRP_FUNCTION) {
|
||||||
/* func () { list } */
|
/* "executing" func () { list } */
|
||||||
bb_error_msg("here we ought to remember function definition, and go on");
|
struct function *funcp;
|
||||||
|
struct function **funcpp = &G.top_func;
|
||||||
|
|
||||||
|
while ((funcp = *funcpp) != NULL) {
|
||||||
|
if (strcmp(funcp->name, command->argv[0]) == 0) {
|
||||||
|
debug_printf_exec("replacing function '%s'", funcp->name);
|
||||||
|
free(funcp->name);
|
||||||
|
free_pipe_list(funcp->body, /* indent: */ 0);
|
||||||
|
goto skip;
|
||||||
|
}
|
||||||
|
funcpp = &funcp->next;
|
||||||
|
}
|
||||||
|
debug_printf_exec("remembering new function '%s'", funcp->name);
|
||||||
|
funcp = *funcpp = xzalloc(sizeof(*funcp));
|
||||||
|
/*funcp->next = NULL;*/
|
||||||
|
skip:
|
||||||
|
funcp->name = command->argv[0];
|
||||||
|
funcp->body = command->group;
|
||||||
|
command->group = NULL;
|
||||||
|
command->argv[0] = NULL;
|
||||||
|
free_strings(command->argv);
|
||||||
|
command->argv = NULL;
|
||||||
|
/* note: if we are in a loop, future "executions"
|
||||||
|
* of func def will see it as null command since
|
||||||
|
* command->group == NULL and command->argv == NULL */
|
||||||
|
//this isn't exactly right: while...do f1() {a;}; f1; f1 {b;}; done
|
||||||
|
//second loop will execute b!
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -3052,6 +3127,11 @@ static int run_pipe(struct pipe *pi)
|
|||||||
argv = command->argv ? command->argv : (char **) &null_ptr;
|
argv = command->argv ? command->argv : (char **) &null_ptr;
|
||||||
{
|
{
|
||||||
const struct built_in_command *x;
|
const struct built_in_command *x;
|
||||||
|
#if ENABLE_HUSH_FUNCTIONS
|
||||||
|
const struct function *funcp;
|
||||||
|
#else
|
||||||
|
enum { funcp = 0 };
|
||||||
|
#endif
|
||||||
char **new_env = NULL;
|
char **new_env = NULL;
|
||||||
char **old_env = NULL;
|
char **old_env = NULL;
|
||||||
|
|
||||||
@ -3078,13 +3158,18 @@ static int run_pipe(struct pipe *pi)
|
|||||||
/* Expand the rest into (possibly) many strings each */
|
/* Expand the rest into (possibly) many strings each */
|
||||||
argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt);
|
argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt);
|
||||||
|
|
||||||
for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
|
x = find_builtin(argv_expanded[0]);
|
||||||
if (strcmp(argv_expanded[0], x->cmd) != 0)
|
#if ENABLE_HUSH_FUNCTIONS
|
||||||
continue;
|
if (!x)
|
||||||
if (x->function == builtin_exec && argv_expanded[1] == NULL) {
|
funcp = find_function(argv_expanded[0]);
|
||||||
debug_printf("exec with redirects only\n");
|
#endif
|
||||||
rcode = setup_redirects(command, NULL);
|
if (x || funcp) {
|
||||||
goto clean_up_and_ret1;
|
if (!funcp) {
|
||||||
|
if (x->function == builtin_exec && argv_expanded[1] == NULL) {
|
||||||
|
debug_printf("exec with redirects only\n");
|
||||||
|
rcode = setup_redirects(command, NULL);
|
||||||
|
goto clean_up_and_ret1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
debug_printf("builtin inline %s\n", argv_expanded[0]);
|
debug_printf("builtin inline %s\n", argv_expanded[0]);
|
||||||
/* XXX setup_redirects acts on file descriptors, not FILEs.
|
/* XXX setup_redirects acts on file descriptors, not FILEs.
|
||||||
@ -3095,9 +3180,18 @@ static int run_pipe(struct pipe *pi)
|
|||||||
if (rcode == 0) {
|
if (rcode == 0) {
|
||||||
new_env = expand_assignments(argv, command->assignment_cnt);
|
new_env = expand_assignments(argv, command->assignment_cnt);
|
||||||
old_env = putenv_all_and_save_old(new_env);
|
old_env = putenv_all_and_save_old(new_env);
|
||||||
debug_printf_exec(": builtin '%s' '%s'...\n",
|
if (!funcp) {
|
||||||
|
debug_printf_exec(": builtin '%s' '%s'...\n",
|
||||||
x->cmd, argv_expanded[1]);
|
x->cmd, argv_expanded[1]);
|
||||||
rcode = x->function(argv_expanded) & 0xff;
|
rcode = x->function(argv_expanded) & 0xff;
|
||||||
|
}
|
||||||
|
#if ENABLE_HUSH_FUNCTIONS
|
||||||
|
else {
|
||||||
|
debug_printf_exec(": function '%s' '%s'...\n",
|
||||||
|
funcp->name, argv_expanded[1]);
|
||||||
|
rcode = run_list(funcp->body) & 0xff;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#if ENABLE_FEATURE_SH_STANDALONE
|
#if ENABLE_FEATURE_SH_STANDALONE
|
||||||
clean_up_and_ret:
|
clean_up_and_ret:
|
||||||
@ -3114,6 +3208,7 @@ static int run_pipe(struct pipe *pi)
|
|||||||
debug_printf_exec("run_pipe return %d\n", rcode);
|
debug_printf_exec("run_pipe return %d\n", rcode);
|
||||||
return rcode;
|
return rcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if ENABLE_FEATURE_SH_STANDALONE
|
#if ENABLE_FEATURE_SH_STANDALONE
|
||||||
i = find_applet_by_name(argv_expanded[0]);
|
i = find_applet_by_name(argv_expanded[0]);
|
||||||
if (i >= 0 && APPLET_IS_NOFORK(i)) {
|
if (i >= 0 && APPLET_IS_NOFORK(i)) {
|
||||||
@ -4081,6 +4176,10 @@ static int done_word(o_string *word, struct parse_context *ctx)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
command->argv = add_string_to_strings(command->argv, xstrdup(word->data));
|
command->argv = add_string_to_strings(command->argv, xstrdup(word->data));
|
||||||
|
//SEGV, but good idea.
|
||||||
|
// command->argv = add_string_to_strings(command->argv, word->data);
|
||||||
|
// word->data = NULL;
|
||||||
|
// word->length = 0;
|
||||||
debug_print_strings("word appended to argv", command->argv);
|
debug_print_strings("word appended to argv", command->argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4089,7 +4188,7 @@ static int done_word(o_string *word, struct parse_context *ctx)
|
|||||||
if (word->o_quoted
|
if (word->o_quoted
|
||||||
|| !is_well_formed_var_name(command->argv[0], '\0')
|
|| !is_well_formed_var_name(command->argv[0], '\0')
|
||||||
) {
|
) {
|
||||||
/* bash says "not a valid identifier" */
|
/* bash says just "not a valid identifier" */
|
||||||
syntax_error("not a valid identifier in for");
|
syntax_error("not a valid identifier in for");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -4462,27 +4561,55 @@ static int parse_group(o_string *dest, struct parse_context *ctx,
|
|||||||
|
|
||||||
debug_printf_parse("parse_group entered\n");
|
debug_printf_parse("parse_group entered\n");
|
||||||
#if ENABLE_HUSH_FUNCTIONS
|
#if ENABLE_HUSH_FUNCTIONS
|
||||||
if (ch == 'F') { /* function definition? */
|
if (ch == '(') {
|
||||||
bb_error_msg("aha '%s' is a function, parsing it...", dest->data);
|
if (!dest->o_quoted) {
|
||||||
//command->fname = dest->data;
|
if (dest->length)
|
||||||
command->grp_type = GRP_FUNCTION;
|
done_word(dest, ctx);
|
||||||
memset(dest, 0, sizeof(*dest));
|
if (!command->argv)
|
||||||
|
goto skip; /* (... */
|
||||||
|
if (command->argv[1]) { /* word word ... (... */
|
||||||
|
syntax_error("unexpected character (");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
/* it is "word(..." or "word (..." */
|
||||||
|
do
|
||||||
|
ch = i_getch(input);
|
||||||
|
while (ch == ' ' || ch == '\t');
|
||||||
|
if (ch != ')') {
|
||||||
|
syntax_error("unexpected character X");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
do
|
||||||
|
ch = i_getch(input);
|
||||||
|
while (ch == ' ' || ch == '\t' || ch == '\n');
|
||||||
|
if (ch != '{') {
|
||||||
|
syntax_error("unexpected character X");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
command->grp_type = GRP_FUNCTION;
|
||||||
|
goto skip;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (command->argv /* word [word](... */
|
if (command->argv /* word [word]{... */
|
||||||
|| dest->length /* word(... */
|
|| dest->length /* word{... */
|
||||||
|| dest->o_quoted /* ""(... */
|
|| dest->o_quoted /* ""{... */
|
||||||
) {
|
) {
|
||||||
syntax_error(NULL);
|
syntax_error(NULL);
|
||||||
debug_printf_parse("parse_group return 1: "
|
debug_printf_parse("parse_group return 1: "
|
||||||
"syntax error, groups and arglists don't mix\n");
|
"syntax error, groups and arglists don't mix\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if ENABLE_HUSH_FUNCTIONS
|
||||||
|
skip:
|
||||||
|
#endif
|
||||||
endch = '}';
|
endch = '}';
|
||||||
if (ch == '(') {
|
if (ch == '(') {
|
||||||
endch = ')';
|
endch = ')';
|
||||||
command->grp_type = GRP_SUBSHELL;
|
command->grp_type = GRP_SUBSHELL;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
#if !BB_MMU
|
#if !BB_MMU
|
||||||
char *as_string = NULL;
|
char *as_string = NULL;
|
||||||
@ -5310,28 +5437,6 @@ static struct pipe *parse_stream(char **pstring,
|
|||||||
) {
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
#if ENABLE_HUSH_FUNCTIONS
|
|
||||||
if (dest.length != 0 /* not just () but word() */
|
|
||||||
&& dest.o_quoted == 0 /* not a"b"c() */
|
|
||||||
&& ctx.command->argv == NULL /* it's the first word */
|
|
||||||
//TODO: "func ( ) {...}" - note spaces - is valid format too in bash
|
|
||||||
&& i_peek(input) == ')'
|
|
||||||
&& !match_reserved_word(&dest)
|
|
||||||
) {
|
|
||||||
bb_error_msg("seems like a function definition");
|
|
||||||
i_getch(input);
|
|
||||||
//if !BB_MMU o_addchr(&ctx.as_string...
|
|
||||||
do {
|
|
||||||
//TODO: do it properly.
|
|
||||||
ch = i_getch(input);
|
|
||||||
} while (ch == ' ' || ch == '\n');
|
|
||||||
if (ch != '{') {
|
|
||||||
syntax_error("was expecting {");
|
|
||||||
goto parse_error;
|
|
||||||
}
|
|
||||||
ch = 'F'; /* magic value */
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
case '{':
|
case '{':
|
||||||
if (parse_group(&dest, &ctx, input, ch) != 0) {
|
if (parse_group(&dest, &ctx, input, ch) != 0) {
|
||||||
@ -6427,11 +6532,11 @@ static int builtin_unset(char **argv)
|
|||||||
ret = EXIT_FAILURE;
|
ret = EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if ENABLE_HUSH_FUNCTIONS
|
//#if ENABLE_HUSH_FUNCTIONS
|
||||||
else {
|
// else {
|
||||||
unset_local_func(*argv);
|
// unset_local_func(*argv);
|
||||||
}
|
// }
|
||||||
#endif
|
//#endif
|
||||||
argv++;
|
argv++;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user