hush: first stab at function support. argv passing is not coded yet.
authorDenis Vlasenko <vda.linux@googlemail.com>
Fri, 10 Apr 2009 19:05:43 +0000 (19:05 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Fri, 10 Apr 2009 19:05:43 +0000 (19:05 -0000)
 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

shell/hush.c

index 9adf0e127344d23f8efa632e90999ef7628827fe..3728583fc70e64862d62f032078af8d965b16170 100644 (file)
@@ -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 @@ struct command {
 #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 @@ enum {
        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 @@ struct globals {
        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 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
 #define pseudo_exec_argv(nommu_save, 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
        }
 
+#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 @@ static void pseudo_exec_argv(nommu_save_t *nommu_save,
         */
        {
                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 @@ static void pseudo_exec_argv(nommu_save_t *nommu_save,
        }
 #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 @@ static void pseudo_exec_argv(nommu_save_t *nommu_save,
        _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 @@ static int run_pipe(struct pipe *pi)
        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 @@ static int run_pipe(struct pipe *pi)
        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 @@ static int run_pipe(struct pipe *pi)
                /* 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 @@ static int run_pipe(struct pipe *pi)
                        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 @@ static int run_pipe(struct pipe *pi)
                        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 @@ static int done_word(o_string *word, struct parse_context *ctx)
                        }
                }
                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 @@ static int done_word(o_string *word, struct parse_context *ctx)
                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 @@ static int parse_group(o_string *dest, struct parse_context *ctx,
 
        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;
@@ -5310,28 +5437,6 @@ static struct pipe *parse_stream(char **pstring,
                        ) {
                                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) {
@@ -6427,11 +6532,11 @@ static int builtin_unset(char **argv)
                                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;