hush: massive renaming of ill-named structures and fields
authorDenis Vlasenko <vda.linux@googlemail.com>
Thu, 9 Oct 2008 12:54:58 +0000 (12:54 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Thu, 9 Oct 2008 12:54:58 +0000 (12:54 -0000)
hush: error out on constructs like:
 $ abc(def) - was working as if it was (abcdef)
 $ case b in abc(a|(b) echo YES; esac - was ignoring 'abc' and extra '('

shell/hush.c

index 6677f893abaf78fcc4bdd4ed7fde0fc9c7e021fe..c26b9e420fea78ecfbf77764f630e8263910abd4 100644 (file)
  *      aliases
  *      Arithmetic Expansion
  *      <(list) and >(list) Process Substitution
- *      reserved words: case, esac, select, function
+ *      reserved words: select, function
  *      Here Documents ( << word )
  *      Functions
  * Major bugs:
  *      job handling woefully incomplete and buggy (improved --vda)
- *      reserved word execution woefully incomplete and buggy
  * to-do:
  *      port selected bugfixes from post-0.49 busybox lash - done?
  *      change { and } from special chars to reserved words
@@ -291,23 +290,6 @@ typedef enum reserved_style {
        RES_SNTX
 } reserved_style;
 
-/* This holds pointers to the various results of parsing */
-struct p_context {
-       struct child_prog *child;
-       struct pipe *list_head;
-       struct pipe *pipe;
-       struct redir_struct *pending_redirect;
-#if HAS_KEYWORDS
-       smallint ctx_res_w;
-       smallint ctx_inverted; /* "! cmd | cmd" */
-#if ENABLE_HUSH_CASE
-       smallint ctx_dsemicolon; /* ";;" seen */
-#endif
-       int old_flag; /* bitmask of FLAG_xxx, for figuring out valid reserved words */
-       struct p_context *stack;
-#endif
-};
-
 struct redir_struct {
        struct redir_struct *next;
        char *rd_filename;          /* filename */
@@ -316,14 +298,14 @@ struct redir_struct {
        smallint /*enum redir_type*/ rd_type;
 };
 
-struct child_prog {
+struct command {
        pid_t pid;                  /* 0 if exited */
        int assignment_cnt;         /* how many argv[i] are assignments? */
-       smallint is_stopped;        /* is the program currently running? */
+       smallint is_stopped;        /* is the command currently running? */
        smallint subshell;          /* flag, non-zero if group must be forked */
        struct pipe *group;         /* if non-NULL, this "prog" is {} group,
                                     * subshell, or a compound statement */
-       char **argv;                /* program name and arguments */
+       char **argv;                /* command name and arguments */
        struct redir_struct *redirects; /* I/O redirections */
 };
 /* argv vector may contain variable references (^Cvar^C, ^C0^C etc)
@@ -335,20 +317,37 @@ struct child_prog {
 
 struct pipe {
        struct pipe *next;
-       int num_progs;              /* total number of programs in job */
-       int alive_progs;            /* number of programs running (not exited) */
-       int stopped_progs;          /* number of programs alive, but stopped */
+       int num_cmds;               /* total number of commands in job */
+       int alive_cmds;             /* number of commands running (not exited) */
+       int stopped_cmds;           /* number of commands alive, but stopped */
 #if ENABLE_HUSH_JOB
        int jobid;                  /* job number */
        pid_t pgrp;                 /* process group ID for the job */
        char *cmdtext;              /* name of job */
 #endif
-       struct child_prog *progs;   /* array of commands in pipe */
+       struct command *cmds;       /* array of commands in pipe */
        smallint followup;          /* PIPE_BG, PIPE_SEQ, PIPE_OR, PIPE_AND */
        IF_HAS_KEYWORDS(smallint pi_inverted;) /* "! cmd | cmd" */
        IF_HAS_KEYWORDS(smallint res_word;) /* needed for if, for, while, until... */
 };
 
+/* This holds pointers to the various results of parsing */
+struct parse_context {
+       struct command *command;
+       struct pipe *list_head;
+       struct pipe *pipe;
+       struct redir_struct *pending_redirect;
+#if HAS_KEYWORDS
+       smallint ctx_res_w;
+       smallint ctx_inverted; /* "! cmd | cmd" */
+#if ENABLE_HUSH_CASE
+       smallint ctx_dsemicolon; /* ";;" seen */
+#endif
+       int old_flag; /* bitmask of FLAG_xxx, for figuring out valid reserved words */
+       struct parse_context *stack;
+#endif
+};
+
 /* On program start, environ points to initial environment.
  * putenv adds new pointers into it, unsetenv removes them.
  * Neither of these (de)allocates the strings.
@@ -523,23 +522,23 @@ static void setup_string_in_str(struct in_str *i, const char *s);
 static int free_pipe_list(struct pipe *head, int indent);
 static int free_pipe(struct pipe *pi, int indent);
 /*  really run the final data structures: */
-static int setup_redirects(struct child_prog *prog, int squirrel[]);
+static int setup_redirects(struct command *prog, int squirrel[]);
 static int run_list(struct pipe *pi);
 #if BB_MMU
 #define pseudo_exec_argv(ptrs2free, argv, assignment_cnt, argv_expanded) \
        pseudo_exec_argv(argv, assignment_cnt, argv_expanded)
-#define pseudo_exec(ptrs2free, child, argv_expanded) \
-       pseudo_exec(child, argv_expanded)
+#define pseudo_exec(ptrs2free, command, argv_expanded) \
+       pseudo_exec(command, argv_expanded)
 #endif
 static void pseudo_exec_argv(char **ptrs2free, char **argv, int assignment_cnt, char **argv_expanded) NORETURN;
-static void pseudo_exec(char **ptrs2free, struct child_prog *child, char **argv_expanded) NORETURN;
+static void pseudo_exec(char **ptrs2free, struct command *command, char **argv_expanded) NORETURN;
 static int run_pipe(struct pipe *pi);
 /*   data structure manipulation: */
-static int setup_redirect(struct p_context *ctx, int fd, redir_type style, struct in_str *input);
-static void initialize_context(struct p_context *ctx);
-static int done_word(o_string *dest, struct p_context *ctx);
-static int done_command(struct p_context *ctx);
-static void done_pipe(struct p_context *ctx, pipe_style type);
+static int setup_redirect(struct parse_context *ctx, int fd, redir_type style, struct in_str *input);
+static void initialize_context(struct parse_context *ctx);
+static int done_word(o_string *dest, struct parse_context *ctx);
+static int done_command(struct parse_context *ctx);
+static void done_pipe(struct parse_context *ctx, pipe_style type);
 /*   primary string parsing: */
 static int redirect_dup_num(struct in_str *input);
 static int redirect_opt_num(o_string *o);
@@ -547,11 +546,11 @@ static int redirect_opt_num(o_string *o);
 static int process_command_subs(o_string *dest,
                struct in_str *input, const char *subst_end);
 #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 parse_context *ctx, struct in_str *input, int ch);
 static const char *lookup_param(const char *src);
 static int handle_dollar(o_string *dest,
                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 parse_context *ctx, struct in_str *input0, const char *end_trigger);
 /*   setup: */
 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);
@@ -837,7 +836,7 @@ static void handler_ctrl_z(int sig UNUSED_PARAM)
        /* parent */
        /* finish filling up pipe info */
        G.toplevel_list->pgrp = pid; /* child is in its own pgrp */
-       G.toplevel_list->progs[0].pid = pid;
+       G.toplevel_list->cmds[0].pid = pid;
        /* parent needs to longjmp out of running nofork.
         * we will "return" exitcode 0, with child put in background */
 // as usual we can have all kinds of nasty problems with leaked malloc data here
@@ -1334,7 +1333,7 @@ static void setup_string_in_str(struct in_str *i, const char *s)
 
 /* squirrel != NULL means we squirrel away copies of stdin, stdout,
  * and stderr if they are redirected. */
-static int setup_redirects(struct child_prog *prog, int squirrel[])
+static int setup_redirects(struct command *prog, int squirrel[])
 {
        int openfd, mode;
        struct redir_struct *redir;
@@ -1467,18 +1466,18 @@ static void pseudo_exec_argv(char **ptrs2free, char **argv, int assignment_cnt,
 
 /* Called after [v]fork() in run_pipe()
  */
-static void pseudo_exec(char **ptrs2free, struct child_prog *child, char **argv_expanded)
+static void pseudo_exec(char **ptrs2free, struct command *command, char **argv_expanded)
 {
-       if (child->argv)
-               pseudo_exec_argv(ptrs2free, child->argv, child->assignment_cnt, argv_expanded);
+       if (command->argv)
+               pseudo_exec_argv(ptrs2free, command->argv, command->assignment_cnt, argv_expanded);
 
-       if (child->group) {
+       if (command->group) {
 #if !BB_MMU
                bb_error_msg_and_die("nested lists are not supported on NOMMU");
 #else
                int rcode;
                debug_printf_exec("pseudo_exec: run_list\n");
-               rcode = run_list(child->group);
+               rcode = run_list(command->group);
                /* OK to leak memory by not calling free_pipe_list,
                 * since this process is about to exit */
                _exit(rcode);
@@ -1502,7 +1501,7 @@ static const char *get_cmdtext(struct pipe *pi)
         * On subsequent bg argv is trashed, but we won't use it */
        if (pi->cmdtext)
                return pi->cmdtext;
-       argv = pi->progs[0].argv;
+       argv = pi->cmds[0].argv;
        if (!argv || !argv[0]) {
                pi->cmdtext = xzalloc(1);
                return pi->cmdtext;
@@ -1511,7 +1510,7 @@ static const char *get_cmdtext(struct pipe *pi)
        len = 0;
        do len += strlen(*argv) + 1; while (*++argv);
        pi->cmdtext = p = xmalloc(len);
-       argv = pi->progs[0].argv;
+       argv = pi->cmds[0].argv;
        do {
                len = strlen(*argv);
                memcpy(p, *argv, len);
@@ -1545,12 +1544,12 @@ static void insert_bg_job(struct pipe *pi)
 
        /* Physically copy the struct job */
        memcpy(thejob, pi, sizeof(struct pipe));
-       thejob->progs = xzalloc(sizeof(pi->progs[0]) * pi->num_progs);
-       /* We cannot copy entire pi->progs[] vector! Double free()s will happen */
-       for (i = 0; i < pi->num_progs; i++) {
+       thejob->cmds = xzalloc(sizeof(pi->cmds[0]) * pi->num_cmds);
+       /* We cannot copy entire pi->cmds[] vector! Double free()s will happen */
+       for (i = 0; i < pi->num_cmds; i++) {
 // TODO: do we really need to have so many fields which are just dead weight
 // at execution stage?
-               thejob->progs[i].pid = pi->progs[i].pid;
+               thejob->cmds[i].pid = pi->cmds[i].pid;
                /* all other fields are not used and stay zero */
        }
        thejob->next = NULL;
@@ -1558,8 +1557,8 @@ static void insert_bg_job(struct pipe *pi)
 
        /* We don't wait for background thejobs to return -- append it
           to the list of backgrounded thejobs and leave it alone */
-       printf("[%d] %d %s\n", thejob->jobid, thejob->progs[0].pid, thejob->cmdtext);
-       G.last_bg_pid = thejob->progs[0].pid;
+       printf("[%d] %d %s\n", thejob->jobid, thejob->cmds[0].pid, thejob->cmdtext);
+       G.last_bg_pid = thejob->cmds[0].pid;
        G.last_jobid = thejob->jobid;
 }
 
@@ -1585,7 +1584,7 @@ static void remove_bg_job(struct pipe *pi)
 static void delete_finished_bg_job(struct pipe *pi)
 {
        remove_bg_job(pi);
-       pi->stopped_progs = 0;
+       pi->stopped_cmds = 0;
        free_pipe(pi, 0);
        free(pi);
 }
@@ -1637,29 +1636,29 @@ static int checkjobs(struct pipe* fg_pipe)
 #endif
                /* Were we asked to wait for fg pipe? */
                if (fg_pipe) {
-                       for (i = 0; i < fg_pipe->num_progs; i++) {
-                               debug_printf_jobs("check pid %d\n", fg_pipe->progs[i].pid);
-                               if (fg_pipe->progs[i].pid != childpid)
+                       for (i = 0; i < fg_pipe->num_cmds; i++) {
+                               debug_printf_jobs("check pid %d\n", fg_pipe->cmds[i].pid);
+                               if (fg_pipe->cmds[i].pid != childpid)
                                        continue;
                                /* printf("process %d exit %d\n", i, WEXITSTATUS(status)); */
                                if (dead) {
-                                       fg_pipe->progs[i].pid = 0;
-                                       fg_pipe->alive_progs--;
-                                       if (i == fg_pipe->num_progs - 1) {
+                                       fg_pipe->cmds[i].pid = 0;
+                                       fg_pipe->alive_cmds--;
+                                       if (i == fg_pipe->num_cmds - 1) {
                                                /* last process gives overall exitstatus */
                                                rcode = WEXITSTATUS(status);
                                                IF_HAS_KEYWORDS(if (fg_pipe->pi_inverted) rcode = !rcode;)
                                        }
                                } else {
-                                       fg_pipe->progs[i].is_stopped = 1;
-                                       fg_pipe->stopped_progs++;
+                                       fg_pipe->cmds[i].is_stopped = 1;
+                                       fg_pipe->stopped_cmds++;
                                }
-                               debug_printf_jobs("fg_pipe: alive_progs %d stopped_progs %d\n",
-                                               fg_pipe->alive_progs, fg_pipe->stopped_progs);
-                               if (fg_pipe->alive_progs - fg_pipe->stopped_progs <= 0) {
+                               debug_printf_jobs("fg_pipe: alive_cmds %d stopped_cmds %d\n",
+                                               fg_pipe->alive_cmds, fg_pipe->stopped_cmds);
+                               if (fg_pipe->alive_cmds - fg_pipe->stopped_cmds <= 0) {
                                        /* All processes in fg pipe have exited/stopped */
 #if ENABLE_HUSH_JOB
-                                       if (fg_pipe->alive_progs)
+                                       if (fg_pipe->alive_cmds)
                                                insert_bg_job(fg_pipe);
 #endif
                                        return rcode;
@@ -1674,8 +1673,8 @@ static int checkjobs(struct pipe* fg_pipe)
                /* We asked to wait for bg or orphaned children */
                /* No need to remember exitcode in this case */
                for (pi = G.job_list; pi; pi = pi->next) {
-                       for (i = 0; i < pi->num_progs; i++) {
-                               if (pi->progs[i].pid == childpid)
+                       for (i = 0; i < pi->num_cmds; i++) {
+                               if (pi->cmds[i].pid == childpid)
                                        goto found_pi_and_prognum;
                        }
                }
@@ -1686,17 +1685,17 @@ static int checkjobs(struct pipe* fg_pipe)
  found_pi_and_prognum:
                if (dead) {
                        /* child exited */
-                       pi->progs[i].pid = 0;
-                       pi->alive_progs--;
-                       if (!pi->alive_progs) {
+                       pi->cmds[i].pid = 0;
+                       pi->alive_cmds--;
+                       if (!pi->alive_cmds) {
                                printf(JOB_STATUS_FORMAT, pi->jobid,
                                                "Done", pi->cmdtext);
                                delete_finished_bg_job(pi);
                        }
                } else {
                        /* child stopped */
-                       pi->progs[i].is_stopped = 1;
-                       pi->stopped_progs++;
+                       pi->cmds[i].is_stopped = 1;
+                       pi->stopped_cmds++;
                }
 #endif
        } /* while (waitpid succeeds)... */
@@ -1745,7 +1744,7 @@ static int run_pipe(struct pipe *pi)
        int i;
        int nextin;
        int pipefds[2];         /* pipefds[0] is for reading */
-       struct child_prog *child;
+       struct command *command;
        char **argv_expanded = NULL;
        char **argv;
        const struct built_in_command *x;
@@ -1753,36 +1752,36 @@ static int run_pipe(struct pipe *pi)
        /* it is not always needed, but we aim to smaller code */
        int squirrel[] = { -1, -1, -1 };
        int rcode;
-       const int single_and_fg = (pi->num_progs == 1 && pi->followup != PIPE_BG);
+       const int single_and_fg = (pi->num_cmds == 1 && pi->followup != PIPE_BG);
 
        debug_printf_exec("run_pipe start: single_and_fg=%d\n", single_and_fg);
 
 #if ENABLE_HUSH_JOB
        pi->pgrp = -1;
 #endif
-       pi->alive_progs = 1;
-       pi->stopped_progs = 0;
+       pi->alive_cmds = 1;
+       pi->stopped_cmds = 0;
 
        /* Check if this is a simple builtin (not part of a pipe).
         * Builtins within pipes have to fork anyway, and are handled in
         * pseudo_exec.  "echo foo | read bar" doesn't work on bash, either.
         */
-       child = &(pi->progs[0]);
-       if (single_and_fg && child->group && child->subshell == 0) {
+       command = &(pi->cmds[0]);
+       if (single_and_fg && command->group && command->subshell == 0) {
                debug_printf("non-subshell grouping\n");
-               setup_redirects(child, squirrel);
+               setup_redirects(command, squirrel);
                debug_printf_exec(": run_list\n");
-               rcode = run_list(child->group) & 0xff;
+               rcode = run_list(command->group) & 0xff;
                restore_redirects(squirrel);
                debug_printf_exec("run_pipe return %d\n", rcode);
                IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;)
                return rcode;
        }
 
-       argv = child->argv;
+       argv = command->argv;
 
        if (single_and_fg && argv != NULL) {
-               i = child->assignment_cnt;
+               i = command->assignment_cnt;
                if (i != 0 && argv[i] == NULL) {
                        /* assignments, but no command: set local environment */
                        for (i = 0; argv[i] != NULL; i++) {
@@ -1794,7 +1793,7 @@ static int run_pipe(struct pipe *pi)
                }
 
                /* Expand assignments into one string each */
-               for (i = 0; i < child->assignment_cnt; i++) {
+               for (i = 0; i < command->assignment_cnt; i++) {
                        p = expand_string_to_string(argv[i]);
                        putenv(p);
 //FIXME: do we leak p?!
@@ -1807,7 +1806,7 @@ static int run_pipe(struct pipe *pi)
                        if (strcmp(argv_expanded[0], x->cmd) == 0) {
                                if (x->function == builtin_exec && argv_expanded[1] == NULL) {
                                        debug_printf("magic exec\n");
-                                       setup_redirects(child, NULL);
+                                       setup_redirects(command, NULL);
                                        return EXIT_SUCCESS;
                                }
                                debug_printf("builtin inline %s\n", argv_expanded[0]);
@@ -1815,7 +1814,7 @@ static int run_pipe(struct pipe *pi)
                                 * This is perfect for work that comes after exec().
                                 * Is it really safe for inline use?  Experimentally,
                                 * things seem to work with glibc. */
-                               setup_redirects(child, squirrel);
+                               setup_redirects(command, squirrel);
                                debug_printf_exec(": builtin '%s' '%s'...\n", x->cmd, argv_expanded[1]);
                                rcode = x->function(argv_expanded) & 0xff;
                                free(argv_expanded);
@@ -1829,7 +1828,7 @@ static int run_pipe(struct pipe *pi)
                {
                        int a = find_applet_by_name(argv_expanded[0]);
                        if (a >= 0 && APPLET_IS_NOFORK(a)) {
-                               setup_redirects(child, squirrel);
+                               setup_redirects(command, squirrel);
                                save_nofork_data(&G.nofork_save);
                                debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", argv_expanded[0], argv_expanded[1]);
                                rcode = run_nofork_applet_prime(&G.nofork_save, a, argv_expanded);
@@ -1852,18 +1851,18 @@ static int run_pipe(struct pipe *pi)
        set_jobctrl_sighandler(SIG_IGN);
 
        /* Going to fork a child per each pipe member */
-       pi->alive_progs = 0;
+       pi->alive_cmds = 0;
        nextin = 0;
 
-       for (i = 0; i < pi->num_progs; i++) {
+       for (i = 0; i < pi->num_cmds; i++) {
 #if !BB_MMU
                char **ptrs2free = NULL;
 #endif
-               child = &(pi->progs[i]);
-               if (child->argv) {
-                       debug_printf_exec(": pipe member '%s' '%s'...\n", child->argv[0], child->argv[1]);
+               command = &(pi->cmds[i]);
+               if (command->argv) {
+                       debug_printf_exec(": pipe member '%s' '%s'...\n", command->argv[0], command->argv[1]);
 #if !BB_MMU
-                       ptrs2free = alloc_ptrs(child->argv);
+                       ptrs2free = alloc_ptrs(command->argv);
 #endif
                } else
                        debug_printf_exec(": pipe member with no argv\n");
@@ -1871,11 +1870,11 @@ static int run_pipe(struct pipe *pi)
                /* pipes are inserted between pairs of commands */
                pipefds[0] = 0;
                pipefds[1] = 1;
-               if ((i + 1) < pi->num_progs)
+               if ((i + 1) < pi->num_cmds)
                        xpipe(pipefds);
 
-               child->pid = BB_MMU ? fork() : vfork();
-               if (!child->pid) { /* child */
+               command->pid = BB_MMU ? fork() : vfork();
+               if (!command->pid) { /* child */
                        if (ENABLE_HUSH_JOB)
                                die_sleep = 0; /* let nofork's xfuncs die */
 #if ENABLE_HUSH_JOB
@@ -1901,45 +1900,45 @@ static int run_pipe(struct pipe *pi)
                                close(pipefds[0]); /* read end */
                        /* Like bash, explicit redirects override pipes,
                         * and the pipe fd is available for dup'ing. */
-                       setup_redirects(child, NULL);
+                       setup_redirects(command, NULL);
 
                        /* Restore default handlers just prior to exec */
                        set_jobctrl_sighandler(SIG_DFL);
                        set_misc_sighandler(SIG_DFL);
                        signal(SIGCHLD, SIG_DFL);
-                       pseudo_exec(ptrs2free, child, argv_expanded); /* does not return */
+                       pseudo_exec(ptrs2free, command, argv_expanded); /* does not return */
                }
                free(argv_expanded);
                argv_expanded = NULL;
 #if !BB_MMU
                free_strings(ptrs2free);
 #endif
-               if (child->pid < 0) { /* [v]fork failed */
+               if (command->pid < 0) { /* [v]fork failed */
                        /* Clearly indicate, was it fork or vfork */
                        bb_perror_msg(BB_MMU ? "fork" : "vfork");
                } else {
-                       pi->alive_progs++;
+                       pi->alive_cmds++;
 #if ENABLE_HUSH_JOB
                        /* Second and next children need to know pid of first one */
                        if (pi->pgrp < 0)
-                               pi->pgrp = child->pid;
+                               pi->pgrp = command->pid;
 #endif
                }
 
                if (i)
                        close(nextin);
-               if ((i + 1) < pi->num_progs)
+               if ((i + 1) < pi->num_cmds)
                        close(pipefds[1]); /* write end */
                /* Pass read (output) pipe end to next iteration */
                nextin = pipefds[0];
        }
 
-       if (!pi->alive_progs) {
+       if (!pi->alive_cmds) {
                debug_printf_exec("run_pipe return 1 (all forks failed, no children)\n");
                return 1;
        }
 
-       debug_printf_exec("run_pipe return -1 (%u children started)\n", pi->alive_progs);
+       debug_printf_exec("run_pipe return -1 (%u children started)\n", pi->alive_cmds);
        return -1;
 }
 
@@ -1988,16 +1987,16 @@ static void debug_print_tree(struct pipe *pi, int lvl)
                fprintf(stderr, "%*spipe %d res_word=%s followup=%d %s\n", lvl*2, "",
                                pin, RES[pi->res_word], pi->followup, PIPE[pi->followup]);
                prn = 0;
-               while (prn < pi->num_progs) {
-                       struct child_prog *child = &pi->progs[prn];
-                       char **argv = child->argv;
+               while (prn < pi->num_cmds) {
+                       struct command *command = &pi->cmds[prn];
+                       char **argv = command->argv;
 
-                       fprintf(stderr, "%*s prog %d assignment_cnt:%d", lvl*2, "", prn, child->assignment_cnt);
-                       if (child->group) {
+                       fprintf(stderr, "%*s prog %d assignment_cnt:%d", lvl*2, "", prn, command->assignment_cnt);
+                       if (command->group) {
                                fprintf(stderr, " group %s: (argv=%p)\n",
-                                               (child->subshell ? "()" : "{}"),
+                                               (command->subshell ? "()" : "{}"),
                                                argv);
-                               debug_print_tree(child->group, lvl+1);
+                               debug_print_tree(command->group, lvl+1);
                                prn++;
                                continue;
                        }
@@ -2151,7 +2150,7 @@ static int run_list(struct pipe *pi)
                }
 #endif
 #if ENABLE_HUSH_LOOPS
-               if (rword == RES_FOR) { /* && pi->num_progs - always == 1 */
+               if (rword == RES_FOR) { /* && pi->num_cmds - always == 1 */
                        if (!for_lcur) {
                                /* first loop through for */
 
@@ -2166,31 +2165,31 @@ static int run_list(struct pipe *pi)
                                vals = (char**)encoded_dollar_at_argv;
                                if (pi->next->res_word == RES_IN) {
                                        /* if no variable values after "in" we skip "for" */
-                                       if (!pi->next->progs[0].argv)
+                                       if (!pi->next->cmds[0].argv)
                                                break;
-                                       vals = pi->next->progs[0].argv;
+                                       vals = pi->next->cmds[0].argv;
                                } /* else: "for var; do..." -> assume "$@" list */
                                /* create list of variable values */
                                debug_print_strings("for_list made from", vals);
                                for_list = expand_strvec_to_strvec(vals);
                                for_lcur = for_list;
                                debug_print_strings("for_list", for_list);
-                               for_varname = pi->progs[0].argv[0];
-                               pi->progs[0].argv[0] = NULL;
+                               for_varname = pi->cmds[0].argv[0];
+                               pi->cmds[0].argv[0] = NULL;
                        }
-                       free(pi->progs[0].argv[0]);
+                       free(pi->cmds[0].argv[0]);
                        if (!*for_lcur) {
                                /* "for" loop is over, clean up */
                                free(for_list);
                                for_list = NULL;
                                for_lcur = NULL;
-                               pi->progs[0].argv[0] = for_varname;
+                               pi->cmds[0].argv[0] = for_varname;
                                break;
                        }
                        /* insert next value from for_lcur */
 //TODO: does it need escaping?
-                       pi->progs[0].argv[0] = xasprintf("%s=%s", for_varname, *for_lcur++);
-                       pi->progs[0].assignment_cnt = 1;
+                       pi->cmds[0].argv[0] = xasprintf("%s=%s", for_varname, *for_lcur++);
+                       pi->cmds[0].assignment_cnt = 1;
                }
                if (rword == RES_IN) /* "for v IN list;..." - "in" has no cmds anyway */
                        continue;
@@ -2200,7 +2199,7 @@ static int run_list(struct pipe *pi)
 #endif
 #if ENABLE_HUSH_CASE
                if (rword == RES_CASE) {
-                       case_word = expand_strvec_to_string(pi->progs->argv);
+                       case_word = expand_strvec_to_string(pi->cmds->argv);
                        continue;
                }
                if (rword == RES_MATCH) {
@@ -2209,7 +2208,7 @@ static int run_list(struct pipe *pi)
                        if (!case_word) /* "case ... matched_word) ... WORD)": we executed selected branch, stop */
                                break;
                        /* all prev words didn't match, does this one match? */
-                       argv = pi->progs->argv;
+                       argv = pi->cmds->argv;
                        while (*argv) {
                                char *pattern = expand_string_to_string(*argv);
                                /* TODO: which FNM_xxx flags to use? */
@@ -2229,14 +2228,14 @@ static int run_list(struct pipe *pi)
                                continue; /* not matched yet, skip this pipe */
                }
 #endif
-               if (pi->num_progs == 0)
+               if (pi->num_cmds == 0)
                        continue;
 
                /* After analyzing all keywords and conditions, we decided
                 * to execute this pipe. NB: has to do checkjobs(NULL)
                 * after run_pipe() to collect any background children,
                 * even if list execution is to be stopped. */
-               debug_printf_exec(": run_pipe with %d members\n", pi->num_progs);
+               debug_printf_exec(": run_pipe with %d members\n", pi->num_cmds);
                {
                        int r;
 #if ENABLE_HUSH_LOOPS
@@ -2349,30 +2348,30 @@ static int run_list(struct pipe *pi)
 static int free_pipe(struct pipe *pi, int indent)
 {
        char **p;
-       struct child_prog *child;
+       struct command *command;
        struct redir_struct *r, *rnext;
        int a, i, ret_code = 0;
 
-       if (pi->stopped_progs > 0)
+       if (pi->stopped_cmds > 0)
                return ret_code;
        debug_printf_clean("%s run pipe: (pid %d)\n", indenter(indent), getpid());
-       for (i = 0; i < pi->num_progs; i++) {
-               child = &pi->progs[i];
+       for (i = 0; i < pi->num_cmds; i++) {
+               command = &pi->cmds[i];
                debug_printf_clean("%s  command %d:\n", indenter(indent), i);
-               if (child->argv) {
-                       for (a = 0, p = child->argv; *p; a++, p++) {
+               if (command->argv) {
+                       for (a = 0, p = command->argv; *p; a++, p++) {
                                debug_printf_clean("%s   argv[%d] = %s\n", indenter(indent), a, *p);
                        }
-                       free_strings(child->argv);
-                       child->argv = NULL;
-               } else if (child->group) {
-                       debug_printf_clean("%s   begin group (subshell:%d)\n", indenter(indent), child->subshell);
-                       ret_code = free_pipe_list(child->group, indent+3);
+                       free_strings(command->argv);
+                       command->argv = NULL;
+               } else if (command->group) {
+                       debug_printf_clean("%s   begin group (subshell:%d)\n", indenter(indent), command->subshell);
+                       ret_code = free_pipe_list(command->group, indent+3);
                        debug_printf_clean("%s   end group\n", indenter(indent));
                } else {
                        debug_printf_clean("%s   (nil)\n", indenter(indent));
                }
-               for (r = child->redirects; r; r = rnext) {
+               for (r = command->redirects; r; r = rnext) {
                        debug_printf_clean("%s   redirect %d%s", indenter(indent), r->fd, redir_table[r->rd_type].descrip);
                        if (r->dup == -1) {
                                /* guard against the case >$FOO, where foo is unset or blank */
@@ -2387,10 +2386,10 @@ static int free_pipe(struct pipe *pi, int indent)
                        rnext = r->next;
                        free(r);
                }
-               child->redirects = NULL;
+               command->redirects = NULL;
        }
-       free(pi->progs);   /* children are an array, they get freed all at once */
-       pi->progs = NULL;
+       free(pi->cmds);   /* children are an array, they get freed all at once */
+       pi->cmds = NULL;
 #if ENABLE_HUSH_JOB
        free(pi->cmdtext);
        pi->cmdtext = NULL;
@@ -2422,7 +2421,7 @@ static int run_and_free_list(struct pipe *pi)
        int rcode = 0;
        debug_printf_exec("run_and_free_list entered\n");
        if (!G.fake_mode) {
-               debug_printf_exec(": run_list with %d members\n", pi->num_progs);
+               debug_printf_exec(": run_list with %d members\n", pi->num_cmds);
                rcode = run_list(pi);
        }
        /* free_pipe_list has the side effect of clearing memory.
@@ -2833,11 +2832,11 @@ static void unset_local_var(const char *name)
  * for file descriptor duplication, e.g., "2>&1".
  * Return code is 0 normally, 1 if a syntax error is detected in src.
  * Resource errors (in xmalloc) cause the process to exit */
-static int setup_redirect(struct p_context *ctx, int fd, redir_type style,
+static int setup_redirect(struct parse_context *ctx, int fd, redir_type style,
        struct in_str *input)
 {
-       struct child_prog *child = ctx->child;
-       struct redir_struct *redir = child->redirects;
+       struct command *command = ctx->command;
+       struct redir_struct *redir = command->redirects;
        struct redir_struct *last_redir = NULL;
 
        /* Create a new redir_struct and drop it onto the end of the linked list */
@@ -2851,7 +2850,7 @@ static int setup_redirect(struct p_context *ctx, int fd, redir_type style,
        if (last_redir) {
                last_redir->next = redir;
        } else {
-               child->redirects = redir;
+               command->redirects = redir;
        }
 
        redir->rd_type = style;
@@ -2887,13 +2886,13 @@ static struct pipe *new_pipe(void)
        return pi;
 }
 
-static void initialize_context(struct p_context *ctx)
+static void initialize_context(struct parse_context *ctx)
 {
        memset(ctx, 0, sizeof(*ctx));
        ctx->pipe = ctx->list_head = new_pipe();
-       /* Create the memory for child, roughly:
-        * ctx->pipe->progs = new struct child_prog;
-        * ctx->child = &ctx->pipe->progs[0];
+       /* Create the memory for command, roughly:
+        * ctx->pipe->cmds = new struct command;
+        * ctx->command = &ctx->pipe->cmds[0];
         */
        done_command(ctx);
 }
@@ -2904,7 +2903,7 @@ static void initialize_context(struct p_context *ctx)
  * case, function, and select are obnoxious, save those for later.
  */
 #if HAS_KEYWORDS
-static int reserved_word(o_string *word, struct p_context *ctx)
+static int reserved_word(o_string *word, struct parse_context *ctx)
 {
        struct reserved_combo {
                char literal[6];
@@ -2988,7 +2987,7 @@ static int reserved_word(o_string *word, struct p_context *ctx)
                        return 1;
                }
                if (r->flag & FLAG_START) {
-                       struct p_context *new;
+                       struct parse_context *new;
                        debug_printf("push stack\n");
                        new = xmalloc(sizeof(*new));
                        *new = *ctx;   /* physical copy */
@@ -3002,12 +3001,12 @@ static int reserved_word(o_string *word, struct p_context *ctx)
                ctx->ctx_res_w = r->res;
                ctx->old_flag = r->flag;
                if (ctx->old_flag & FLAG_END) {
-                       struct p_context *old;
+                       struct parse_context *old;
                        debug_printf("pop stack\n");
                        done_pipe(ctx, PIPE_SEQ);
                        old = ctx->stack;
-                       old->child->group = ctx->list_head;
-                       old->child->subshell = 0;
+                       old->command->group = ctx->list_head;
+                       old->command->subshell = 0;
                        *ctx = *old;   /* physical copy */
                        free(old);
                }
@@ -3020,11 +3019,11 @@ static int reserved_word(o_string *word, struct p_context *ctx)
 
 /* Word is complete, look at it and update parsing context.
  * Normal return is 0. Syntax errors return 1. */
-static int done_word(o_string *word, struct p_context *ctx)
+static int done_word(o_string *word, struct parse_context *ctx)
 {
-       struct child_prog *child = ctx->child;
+       struct command *command = ctx->command;
 
-       debug_printf_parse("done_word entered: '%s' %p\n", word->data, child);
+       debug_printf_parse("done_word entered: '%s' %p\n", word->data, command);
        if (word->length == 0 && word->nonnull == 0) {
                debug_printf_parse("done_word return 0: true null, ignored\n");
                return 0;
@@ -3037,7 +3036,7 @@ static int done_word(o_string *word, struct p_context *ctx)
                word->o_assignment = NOT_ASSIGNMENT;
        } else {
                if (word->o_assignment == DEFINITELY_ASSIGNMENT)
-                       child->assignment_cnt++;
+                       command->assignment_cnt++;
                word->o_assignment = MAYBE_ASSIGNMENT;
        }
 
@@ -3050,7 +3049,7 @@ static int done_word(o_string *word, struct p_context *ctx)
        } else {
                /* "{ echo foo; } echo bar" - bad */
                /* NB: bash allows e.g. "if true; then { echo foo; } fi". TODO? */
-               if (child->group) {
+               if (command->group) {
                        syntax(NULL);
                        debug_printf_parse("done_word return 1: syntax error, groups and arglists don't mix\n");
                        return 1;
@@ -3066,7 +3065,7 @@ static int done_word(o_string *word, struct p_context *ctx)
                } else
 #endif
 
-               if (!child->argv /* if it's the first word... */
+               if (!command->argv /* if it's the first word... */
 #if ENABLE_HUSH_LOOPS
                 && ctx->ctx_res_w != RES_FOR /* ...not after FOR or IN */
                 && ctx->ctx_res_w != RES_IN
@@ -3102,8 +3101,8 @@ static int done_word(o_string *word, struct p_context *ctx)
                                o_addchr(word, SPECIAL_VAR_SYMBOL);
                        }
                }
-               child->argv = add_malloced_string_to_strings(child->argv, xstrdup(word->data));
-               debug_print_strings("word appended to argv", child->argv);
+               command->argv = add_malloced_string_to_strings(command->argv, xstrdup(word->data));
+               debug_print_strings("word appended to argv", command->argv);
        }
 
        o_reset(word);
@@ -3115,7 +3114,7 @@ static int done_word(o_string *word, struct p_context *ctx)
         * as it is "for v; in ...". FOR and IN become two pipe structs
         * in parse tree. */
        if (ctx->ctx_res_w == RES_FOR) {
-//TODO: check that child->argv[0] is a valid variable name!
+//TODO: check that command->argv[0] is a valid variable name!
                done_pipe(ctx, PIPE_SEQ);
        }
 #endif
@@ -3131,40 +3130,40 @@ static int done_word(o_string *word, struct p_context *ctx)
 
 /* Command (member of a pipe) is complete. The only possible error here
  * is out of memory, in which case xmalloc exits. */
-static int done_command(struct p_context *ctx)
+static int done_command(struct parse_context *ctx)
 {
-       /* The child is really already in the pipe structure, so
-        * advance the pipe counter and make a new, null child. */
+       /* The command is really already in the pipe structure, so
+        * advance the pipe counter and make a new, null command. */
        struct pipe *pi = ctx->pipe;
-       struct child_prog *child = ctx->child;
+       struct command *command = ctx->command;
 
-       if (child) {
-               if (child->group == NULL
-                && child->argv == NULL
-                && child->redirects == NULL
+       if (command) {
+               if (command->group == NULL
+                && command->argv == NULL
+                && command->redirects == NULL
                ) {
-                       debug_printf_parse("done_command: skipping null cmd, num_progs=%d\n", pi->num_progs);
-                       return pi->num_progs;
+                       debug_printf_parse("done_command: skipping null cmd, num_cmds=%d\n", pi->num_cmds);
+                       return pi->num_cmds;
                }
-               pi->num_progs++;
-               debug_printf_parse("done_command: ++num_progs=%d\n", pi->num_progs);
+               pi->num_cmds++;
+               debug_printf_parse("done_command: ++num_cmds=%d\n", pi->num_cmds);
        } else {
-               debug_printf_parse("done_command: initializing, num_progs=%d\n", pi->num_progs);
+               debug_printf_parse("done_command: initializing, num_cmds=%d\n", pi->num_cmds);
        }
 
        /* Only real trickiness here is that the uncommitted
-        * child structure is not counted in pi->num_progs. */
-       pi->progs = xrealloc(pi->progs, sizeof(*pi->progs) * (pi->num_progs+1));
-       child = &pi->progs[pi->num_progs];
-       memset(child, 0, sizeof(*child));
+        * command structure is not counted in pi->num_cmds. */
+       pi->cmds = xrealloc(pi->cmds, sizeof(*pi->cmds) * (pi->num_cmds+1));
+       command = &pi->cmds[pi->num_cmds];
+       memset(command, 0, sizeof(*command));
 
-       ctx->child = child;
+       ctx->command = command;
        /* but ctx->pipe and ctx->list_head remain unchanged */
 
-       return pi->num_progs; /* used only for 0/nonzero check */
+       return pi->num_cmds; /* used only for 0/nonzero check */
 }
 
-static void done_pipe(struct p_context *ctx, pipe_style type)
+static void done_pipe(struct parse_context *ctx, pipe_style type)
 {
        int not_null;
 
@@ -3189,7 +3188,7 @@ static void done_pipe(struct p_context *ctx, pipe_style type)
                new_p = new_pipe();
                ctx->pipe->next = new_p;
                ctx->pipe = new_p;
-               ctx->child = NULL; /* needed! */
+               ctx->command = NULL; /* needed! */
                /* RES_THEN, RES_DO etc are "sticky" -
                 * they remain set for commands inside if/while.
                 * This is used to control execution.
@@ -3205,9 +3204,9 @@ static void done_pipe(struct p_context *ctx, pipe_style type)
                if (ctx->ctx_res_w == RES_MATCH)
                        ctx->ctx_res_w = RES_CASEI;
 #endif
-               /* Create the memory for child, roughly:
-                * ctx->pipe->progs = new struct child_prog;
-                * ctx->child = &ctx->pipe->progs[0];
+               /* Create the memory for command, roughly:
+                * ctx->pipe->cmds = new struct command;
+                * ctx->command = &ctx->pipe->cmds[0];
                 */
                done_command(ctx);
        }
@@ -3320,7 +3319,7 @@ static int process_command_subs(o_string *dest,
 {
        int retcode, ch, eol_cnt;
        o_string result = NULL_O_STRING;
-       struct p_context inner;
+       struct parse_context inner;
        FILE *p;
        struct in_str pipe_str;
 
@@ -3367,7 +3366,7 @@ static int process_command_subs(o_string *dest,
 }
 #endif
 
-static int parse_group(o_string *dest, struct p_context *ctx,
+static int parse_group(o_string *dest, struct parse_context *ctx,
        struct in_str *input, int ch)
 {
        /* NB: parse_group may create and use its own o_string,
@@ -3375,11 +3374,14 @@ static int parse_group(o_string *dest, struct p_context *ctx,
         * if we (ab)use caller's one. */
        int rcode;
        const char *endch = NULL;
-       struct p_context sub;
-       struct child_prog *child = ctx->child;
+       struct parse_context sub;
+       struct command *command = ctx->command;
 
        debug_printf_parse("parse_group entered\n");
-       if (child->argv) {
+       if (command->argv /* word [word](... */
+        || dest->length /* word(... */
+        || dest->nonnull /* ""(... */
+       ) {
                syntax(NULL);
                debug_printf_parse("parse_group return 1: syntax error, groups and arglists don't mix\n");
                return 1;
@@ -3388,17 +3390,17 @@ static int parse_group(o_string *dest, struct p_context *ctx,
        endch = "}";
        if (ch == '(') {
                endch = ")";
-               child->subshell = 1;
+               command->subshell = 1;
        }
        rcode = parse_stream(dest, &sub, input, endch);
        if (rcode == 0) {
                done_word(dest, &sub); /* finish off the final word in the subcontext */
                done_pipe(&sub, PIPE_SEQ);  /* and the final command there, too */
-               child->group = sub.list_head;
+               command->group = sub.list_head;
        }
        debug_printf_parse("parse_group return %d\n", rcode);
        return rcode;
-       /* child remains "open", available for possible redirects */
+       /* command remains "open", available for possible redirects */
 }
 
 /* Basically useful version until someone wants to get fancier,
@@ -3617,7 +3619,7 @@ static int handle_dollar(o_string *dest, struct in_str *input)
  * Return code is 0 if end_trigger char is met,
  * -1 on EOF (but if end_trigger == NULL then return 0),
  * 1 for syntax error */
-static int parse_stream(o_string *dest, struct p_context *ctx,
+static int parse_stream(o_string *dest, struct parse_context *ctx,
        struct in_str *input, const char *end_trigger)
 {
        int ch, m;
@@ -3883,8 +3885,10 @@ static int parse_stream(o_string *dest, struct p_context *ctx,
                case '(':
 #if ENABLE_HUSH_CASE
                        /* "case... in [(]word)..." - skip '(' */
-                       if (dest->length == 0 // && argv[0] == NULL
+                       if (dest->length == 0 /* not word(... */
+                        && dest->nonnull == 0 /* not ""(... */
                         && ctx->ctx_res_w == RES_MATCH
+                        && ctx->command->argv == NULL /* not (word|(... */
                        ) {
                                continue;
                        }
@@ -3947,7 +3951,7 @@ static void update_charmap(void)
  * from builtin_source() and builtin_eval() */
 static int parse_and_run_stream(struct in_str *inp, int parse_flag)
 {
-       struct p_context ctx;
+       struct parse_context ctx;
        o_string temp = NULL_O_STRING;
        int rcode;
 
@@ -4408,12 +4412,12 @@ static int builtin_fg_bg(char **argv)
        }
 
        /* Restart the processes in the job */
-       debug_printf_jobs("reviving %d procs, pgrp %d\n", pi->num_progs, pi->pgrp);
-       for (i = 0; i < pi->num_progs; i++) {
-               debug_printf_jobs("reviving pid %d\n", pi->progs[i].pid);
-               pi->progs[i].is_stopped = 0;
+       debug_printf_jobs("reviving %d procs, pgrp %d\n", pi->num_cmds, pi->pgrp);
+       for (i = 0; i < pi->num_cmds; i++) {
+               debug_printf_jobs("reviving pid %d\n", pi->cmds[i].pid);
+               pi->cmds[i].is_stopped = 0;
        }
-       pi->stopped_progs = 0;
+       pi->stopped_cmds = 0;
 
        i = kill(- pi->pgrp, SIGCONT);
        if (i < 0) {
@@ -4455,7 +4459,7 @@ static int builtin_jobs(char **argv UNUSED_PARAM)
        const char *status_string;
 
        for (job = G.job_list; job; job = job->next) {
-               if (job->alive_progs == job->stopped_progs)
+               if (job->alive_cmds == job->stopped_cmds)
                        status_string = "Stopped";
                else
                        status_string = "Running";