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
diff --git a/shell/hush.c b/shell/hush.c
index 9adf0e1..3728583 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -86,7 +86,7 @@
*/
#define HUSH_DEBUG 1
/* In progress... */
-#define ENABLE_HUSH_FUNCTIONS 0
+#define ENABLE_HUSH_FUNCTIONS 1
#if BUILD_AS_NOMMU
@@ -374,12 +374,12 @@
#define GRP_NORMAL 0
#define GRP_SUBSHELL 1
#if ENABLE_HUSH_FUNCTIONS
-#define GRP_FUNCTION 2
+# define GRP_FUNCTION 2
#endif
struct pipe {
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 stopped_cmds; /* number of commands alive, but stopped */
#if ENABLE_HUSH_JOB
@@ -452,6 +452,12 @@
BC_CONTINUE = 2,
};
+struct function {
+ struct function *next;
+ char *name;
+ struct pipe *body;
+};
+
/* "Globals" within this file */
/* Sorted roughly by size (smaller offsets == smaller code) */
@@ -504,6 +510,7 @@
const char *cwd;
struct variable *top_var; /* = &G.shell_ver (set in main()) */
struct variable shell_ver;
+ struct function *top_func;
/* Signal and trap handling */
// unsigned count_SIGCHLD;
// unsigned handled_SIGCHLD;
@@ -2585,6 +2592,35 @@
}
+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
#define pseudo_exec_argv(nommu_save, argv, assignment_cnt, argv_expanded) \
pseudo_exec_argv(argv, assignment_cnt, argv_expanded)
@@ -2627,6 +2663,11 @@
#endif
}
+#if ENABLE_FEATURE_SH_STANDALONE || BB_MMU
+ if (strchr(argv[0], '/') != NULL)
+ goto skip;
+#endif
+
/* On NOMMU, we must never block!
* Example: { sleep 99999 | read line } & echo Ok
* read builtin will block on read syscall, leaving parent blocked
@@ -2640,30 +2681,38 @@
*/
{
int rcode;
- const struct built_in_command *x;
- for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
- if (strcmp(argv[0], x->cmd) == 0) {
- debug_printf_exec("running builtin '%s'\n",
- argv[0]);
- rcode = x->function(argv);
- fflush(NULL);
- _exit(rcode);
- }
+ const struct built_in_command *x = find_builtin(argv[0]);
+ if (x) {
+ 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
#if ENABLE_FEATURE_SH_STANDALONE
/* Check if the command matches any busybox applets */
- if (strchr(argv[0], '/') == NULL) {
+ {
int a = find_applet_by_name(argv[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)) {
debug_printf_exec("running applet '%s'\n", argv[0]);
run_applet_no_and_exit(a, argv);
}
-#endif
+# endif
/* Re-exec ourselves */
debug_printf_exec("re-execing applet '%s'\n", argv[0]);
sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
@@ -2674,6 +2723,7 @@
}
#endif
+ skip:
debug_printf_exec("execing '%s'\n", argv[0]);
sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
execvp(argv[0], argv);
@@ -2681,8 +2731,6 @@
_exit(EXIT_FAILURE);
}
-static int run_list(struct pipe *pi);
-
/* Called after [v]fork() in run_pipe
*/
static void pseudo_exec(nommu_save_t *nommu_save,
@@ -3031,8 +3079,35 @@
if (command->group) {
#if ENABLE_HUSH_FUNCTIONS
if (command->grp_type == GRP_FUNCTION) {
- /* func () { list } */
- bb_error_msg("here we ought to remember function definition, and go on");
+ /* "executing" func () { list } */
+ 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;
}
#endif
@@ -3052,6 +3127,11 @@
argv = command->argv ? command->argv : (char **) &null_ptr;
{
const struct built_in_command *x;
+#if ENABLE_HUSH_FUNCTIONS
+ const struct function *funcp;
+#else
+ enum { funcp = 0 };
+#endif
char **new_env = NULL;
char **old_env = NULL;
@@ -3078,13 +3158,18 @@
/* Expand the rest into (possibly) many strings each */
argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt);
- for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
- if (strcmp(argv_expanded[0], x->cmd) != 0)
- continue;
- 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;
+ x = find_builtin(argv_expanded[0]);
+#if ENABLE_HUSH_FUNCTIONS
+ if (!x)
+ funcp = find_function(argv_expanded[0]);
+#endif
+ if (x || funcp) {
+ 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]);
/* XXX setup_redirects acts on file descriptors, not FILEs.
@@ -3095,9 +3180,18 @@
if (rcode == 0) {
new_env = expand_assignments(argv, command->assignment_cnt);
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]);
- 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
clean_up_and_ret:
@@ -3114,6 +3208,7 @@
debug_printf_exec("run_pipe return %d\n", rcode);
return rcode;
}
+
#if ENABLE_FEATURE_SH_STANDALONE
i = find_applet_by_name(argv_expanded[0]);
if (i >= 0 && APPLET_IS_NOFORK(i)) {
@@ -4081,6 +4176,10 @@
}
}
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);
}
@@ -4089,7 +4188,7 @@
if (word->o_quoted
|| !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");
return 1;
}
@@ -4462,27 +4561,55 @@
debug_printf_parse("parse_group entered\n");
#if ENABLE_HUSH_FUNCTIONS
- if (ch == 'F') { /* function definition? */
- bb_error_msg("aha '%s' is a function, parsing it...", dest->data);
- //command->fname = dest->data;
- command->grp_type = GRP_FUNCTION;
- memset(dest, 0, sizeof(*dest));
+ if (ch == '(') {
+ if (!dest->o_quoted) {
+ if (dest->length)
+ done_word(dest, ctx);
+ 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
- if (command->argv /* word [word](... */
- || dest->length /* word(... */
- || dest->o_quoted /* ""(... */
+ if (command->argv /* word [word]{... */
+ || dest->length /* word{... */
+ || dest->o_quoted /* ""{... */
) {
syntax_error(NULL);
debug_printf_parse("parse_group return 1: "
"syntax error, groups and arglists don't mix\n");
return 1;
}
+
+#if ENABLE_HUSH_FUNCTIONS
+ skip:
+#endif
endch = '}';
if (ch == '(') {
endch = ')';
command->grp_type = GRP_SUBSHELL;
}
+
{
#if !BB_MMU
char *as_string = NULL;
@@ -5311,28 +5438,6 @@
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
case '{':
if (parse_group(&dest, &ctx, input, ch) != 0) {
goto parse_error;
@@ -6427,11 +6532,11 @@
ret = EXIT_FAILURE;
}
}
-#if ENABLE_HUSH_FUNCTIONS
- else {
- unset_local_func(*argv);
- }
-#endif
+//#if ENABLE_HUSH_FUNCTIONS
+// else {
+// unset_local_func(*argv);
+// }
+//#endif
argv++;
}
return ret;