hush: cleanup pass, the biggest is - moved builtins to the end of the file,
authorDenis Vlasenko <vda.linux@googlemail.com>
Tue, 17 Jun 2008 05:43:38 +0000 (05:43 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Tue, 17 Jun 2008 05:43:38 +0000 (05:43 -0000)
 they really annoy in the middle of parser code. no real code changes.

shell/hush.c
shell/hush_leaktool.sh

index 0b92c29d4f7f726cbdd775ea7c17796758cf483c..e64cc476d16b236bcbe9953a2485eaed1ff84870 100644 (file)
@@ -244,7 +244,6 @@ typedef enum {
        PIPE_BG  = 4,
 } pipe_style;
 
-/* might eventually control execution */
 typedef enum {
        RES_NONE  = 0,
 #if ENABLE_HUSH_IF
@@ -265,6 +264,7 @@ typedef enum {
        RES_XXXX  = 12,
        RES_SNTX  = 13
 } reserved_style;
+
 /* This holds pointers to the various results of parsing */
 struct p_context {
        struct child_prog *child;
@@ -298,6 +298,7 @@ struct child_prog {
  * and on execution these are substituted with their values.
  * Substitution can make _several_ words out of one argv[n]!
  * Example: argv[0]=='.^C*^C.' here: echo .$*.
+ * References of the form ^C`cmd arg^C are `cmd arg` substitutions.
  */
 
 struct pipe {
@@ -341,8 +342,8 @@ typedef struct {
        smallint nonnull;
        smallint has_empty_slot;
 } o_string;
+/* Used for initialization: o_string foo = NULL_O_STRING; */
 #define NULL_O_STRING { NULL }
-/* used for initialization: o_string foo = NULL_O_STRING; */
 
 /* I can almost use ordinary FILE *.  Is open_memstream() universally
  * available?  Where is it documented? */
@@ -572,7 +573,7 @@ static int glob_needed(const char *s)
        return 0;
 }
 
-static char **add_strings_to_strings(int need_xstrdup, char **strings, char **add)
+static char **add_malloced_strings_to_strings(char **strings, char **add)
 {
        int i;
        unsigned count1;
@@ -597,19 +598,18 @@ static char **add_strings_to_strings(int need_xstrdup, char **strings, char **ad
        v[count1 + count2] = NULL;
        i = count2;
        while (--i >= 0)
-               v[count1 + i] = need_xstrdup ? xstrdup(add[i]) : add[i];
+               v[count1 + i] = add[i];
        return v;
 }
 
-/* 'add' should be a malloced pointer */
-static char **add_string_to_strings(char **strings, char *add)
+static char **add_malloced_string_to_strings(char **strings, char *add)
 {
        char *v[2];
 
        v[0] = add;
        v[1] = NULL;
 
-       return add_strings_to_strings(0, strings, v);
+       return add_malloced_strings_to_strings(strings, v);
 }
 
 static void free_strings(char **strings)
@@ -713,6 +713,7 @@ static const struct built_in_command bltins[] = {
 #endif
 };
 
+
 /* Signals are grouped, we handle them in batches */
 static void set_misc_sighandler(void (*handler)(int))
 {
@@ -846,636 +847,310 @@ static const char *set_cwd(void)
 }
 
 
-/* built-in 'true' handler */
-static int builtin_true(char **argv ATTRIBUTE_UNUSED)
+/*
+ * o_string support
+ */
+#define B_CHUNK  (32 * sizeof(char*))
+
+static void o_reset(o_string *o)
 {
-       return 0;
+       o->length = 0;
+       o->nonnull = 0;
+       if (o->data)
+               o->data[0] = '\0';
 }
 
-/* built-in 'test' handler */
-static int builtin_test(char **argv)
+static void o_free(o_string *o)
 {
-       int argc = 0;
-       while (*argv) {
-               argc++;
-               argv++;
-       }
-       return test_main(argc, argv - argc);
+       free(o->data);
+       memset(o, 0, sizeof(*o));
 }
 
-/* built-in 'test' handler */
-static int builtin_echo(char **argv)
+static void o_grow_by(o_string *o, int len)
 {
-       int argc = 0;
-       while (*argv) {
-               argc++;
-               argv++;
+       if (o->length + len > o->maxlen) {
+               o->maxlen += (2*len > B_CHUNK ? 2*len : B_CHUNK);
+               o->data = xrealloc(o->data, 1 + o->maxlen);
        }
-       return echo_main(argc, argv - argc);
 }
 
-/* built-in 'eval' handler */
-static int builtin_eval(char **argv)
+static void o_addchr(o_string *o, int ch)
 {
-       int rcode = EXIT_SUCCESS;
-
-       if (argv[1]) {
-               char *str = expand_strvec_to_string(argv + 1);
-               parse_and_run_string(str, PARSEFLAG_EXIT_FROM_LOOP);
-               free(str);
-               rcode = last_return_code;
-       }
-       return rcode;
+       debug_printf("o_addchr: '%c' o->length=%d o=%p\n", ch, o->length, o);
+       o_grow_by(o, 1);
+       o->data[o->length] = ch;
+       o->length++;
+       o->data[o->length] = '\0';
 }
 
-/* built-in 'cd <path>' handler */
-static int builtin_cd(char **argv)
+static void o_addstr(o_string *o, const char *str, int len)
 {
-       const char *newdir;
-       if (argv[1] == NULL) {
-               // bash does nothing (exitcode 0) if HOME is ""; if it's unset,
-               // bash says "bash: cd: HOME not set" and does nothing (exitcode 1)
-               newdir = getenv("HOME") ? : "/";
-       } else
-               newdir = argv[1];
-       if (chdir(newdir)) {
-               printf("cd: %s: %s\n", newdir, strerror(errno));
-               return EXIT_FAILURE;
-       }
-       set_cwd();
-       return EXIT_SUCCESS;
+       o_grow_by(o, len);
+       memcpy(&o->data[o->length], str, len);
+       o->length += len;
+       o->data[o->length] = '\0';
 }
 
-/* built-in 'exec' handler */
-static int builtin_exec(char **argv)
+/* My analysis of quoting semantics tells me that state information
+ * is associated with a destination, not a source.
+ */
+static void o_addqchr(o_string *o, int ch)
 {
-       if (argv[1] == NULL)
-               return EXIT_SUCCESS; /* bash does this */
-       {
-#if !BB_MMU
-               char **ptrs2free = alloc_ptrs(argv);
-#endif
-// FIXME: if exec fails, bash does NOT exit! We do...
-               pseudo_exec_argv(ptrs2free, argv + 1);
-               /* never returns */
+       int sz = 1;
+       if (strchr("*?[\\", ch)) {
+               sz++;
+               o->data[o->length] = '\\';
+               o->length++;
        }
+       o_grow_by(o, sz);
+       o->data[o->length] = ch;
+       o->length++;
+       o->data[o->length] = '\0';
 }
 
-/* built-in 'exit' handler */
-static int builtin_exit(char **argv)
+static void o_addQchr(o_string *o, int ch)
 {
-// TODO: bash does it ONLY on top-level sh exit (+interacive only?)
-       //puts("exit"); /* bash does it */
-// TODO: warn if we have background jobs: "There are stopped jobs"
-// On second consecutive 'exit', exit anyway.
-       if (argv[1] == NULL)
-               hush_exit(last_return_code);
-       /* mimic bash: exit 123abc == exit 255 + error msg */
-       xfunc_error_retval = 255;
-       /* bash: exit -2 == exit 254, no error msg */
-       hush_exit(xatoi(argv[1]) & 0xff);
+       int sz = 1;
+       if (o->o_quote && strchr("*?[\\", ch)) {
+               sz++;
+               o->data[o->length] = '\\';
+               o->length++;
+       }
+       o_grow_by(o, sz);
+       o->data[o->length] = ch;
+       o->length++;
+       o->data[o->length] = '\0';
 }
 
-/* built-in 'export VAR=value' handler */
-static int builtin_export(char **argv)
+static void o_addQstr(o_string *o, const char *str, int len)
 {
-       const char *value;
-       char *name = argv[1];
-
-       if (name == NULL) {
-               // TODO:
-               // ash emits: export VAR='VAL'
-               // bash: declare -x VAR="VAL"
-               // (both also escape as needed (quotes, $, etc))
-               char **e = environ;
-               if (e)
-                       while (*e)
-                               puts(*e++);
-               return EXIT_SUCCESS;
+       if (!o->o_quote) {
+               o_addstr(o, str, len);
+               return;
        }
+       while (len) {
+               char ch;
+               int sz;
+               int ordinary_cnt = strcspn(str, "*?[\\");
+               if (ordinary_cnt > len) /* paranoia */
+                       ordinary_cnt = len;
+               o_addstr(o, str, ordinary_cnt);
+               if (ordinary_cnt == len)
+                       return;
+               str += ordinary_cnt;
+               len -= ordinary_cnt - 1; /* we are processing + 1 char below */
 
-       value = strchr(name, '=');
-       if (!value) {
-               /* They are exporting something without a =VALUE */
-               struct variable *var;
-
-               var = get_local_var(name);
-               if (var) {
-                       var->flg_export = 1;
-                       putenv(var->varstr);
+               ch = *str++;
+               sz = 1;
+               if (ch) { /* it is necessarily one of "*?[\\" */
+                       sz++;
+                       o->data[o->length] = '\\';
+                       o->length++;
                }
-               /* bash does not return an error when trying to export
-                * an undefined variable.  Do likewise. */
-               return EXIT_SUCCESS;
+               o_grow_by(o, sz);
+               o->data[o->length] = ch;
+               o->length++;
+               o->data[o->length] = '\0';
        }
-
-       set_local_var(xstrdup(name), 1);
-       return EXIT_SUCCESS;
 }
 
-#if ENABLE_HUSH_JOB
-/* built-in 'fg' and 'bg' handler */
-static int builtin_fg_bg(char **argv)
+/* A special kind of o_string for $VAR and `cmd` expansion.
+ * It contains char* list[] at the beginning, which is grown in 16 element
+ * increments. Actual string data starts at the next multiple of 16 * (char*).
+ * list[i] contains an INDEX (int!) into this string data.
+ * It means that if list[] needs to grow, data needs to be moved higher up
+ * but list[i]'s need not be modified.
+ * NB: remembering how many list[i]'s you have there is crucial.
+ * o_finalize_list() operation post-processes this structure - calculates
+ * and stores actual char* ptrs in list[]. Oh, it NULL terminates it as well.
+ */
+#if DEBUG_EXPAND || DEBUG_GLOB
+static void debug_print_list(const char *prefix, o_string *o, int n)
 {
-       int i, jobnum;
-       struct pipe *pi;
-
-       if (!interactive_fd)
-               return EXIT_FAILURE;
-       /* If they gave us no args, assume they want the last backgrounded task */
-       if (!argv[1]) {
-               for (pi = job_list; pi; pi = pi->next) {
-                       if (pi->jobid == last_jobid) {
-                               goto found;
-                       }
-               }
-               bb_error_msg("%s: no current job", argv[0]);
-               return EXIT_FAILURE;
-       }
-       if (sscanf(argv[1], "%%%d", &jobnum) != 1) {
-               bb_error_msg("%s: bad argument '%s'", argv[0], argv[1]);
-               return EXIT_FAILURE;
-       }
-       for (pi = job_list; pi; pi = pi->next) {
-               if (pi->jobid == jobnum) {
-                       goto found;
-               }
-       }
-       bb_error_msg("%s: %d: no such job", argv[0], jobnum);
-       return EXIT_FAILURE;
- found:
-       // TODO: bash prints a string representation
-       // of job being foregrounded (like "sleep 1 | cat")
-       if (*argv[0] == 'f') {
-               /* Put the job into the foreground.  */
-               tcsetpgrp(interactive_fd, pi->pgrp);
-       }
-
-       /* 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;
-       }
-       pi->stopped_progs = 0;
-
-       i = kill(- pi->pgrp, SIGCONT);
-       if (i < 0) {
-               if (errno == ESRCH) {
-                       delete_finished_bg_job(pi);
-                       return EXIT_SUCCESS;
-               } else {
-                       bb_perror_msg("kill (SIGCONT)");
-               }
+       char **list = (char**)o->data;
+       int string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
+       int i = 0;
+       fprintf(stderr, "%s: list:%p n:%d string_start:%d length:%d maxlen:%d\n",
+                       prefix, list, n, string_start, o->length, o->maxlen);
+       while (i < n) {
+               fprintf(stderr, " list[%d]=%d '%s' %p\n", i, (int)list[i],
+                               o->data + (int)list[i] + string_start,
+                               o->data + (int)list[i] + string_start);
+               i++;
        }
-
-       if (*argv[0] == 'f') {
-               remove_bg_job(pi);
-               return checkjobs_and_fg_shell(pi);
+       if (n) {
+               const char *p = o->data + (int)list[n - 1] + string_start;
+               fprintf(stderr, " total_sz:%d\n", (p + strlen(p) + 1) - o->data);
        }
-       return EXIT_SUCCESS;
 }
+#else
+#define debug_print_list(prefix, o, n) ((void)0)
 #endif
 
-/* built-in 'help' handler */
-#if ENABLE_HUSH_HELP
-static int builtin_help(char **argv ATTRIBUTE_UNUSED)
-{
-       const struct built_in_command *x;
-
-       printf("\nBuilt-in commands:\n");
-       printf("-------------------\n");
-       for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
-               printf("%s\t%s\n", x->cmd, x->descr);
-       }
-       printf("\n\n");
-       return EXIT_SUCCESS;
-}
-#endif
-
-#if ENABLE_HUSH_JOB
-/* built-in 'jobs' handler */
-static int builtin_jobs(char **argv ATTRIBUTE_UNUSED)
+/* n = o_save_ptr_helper(str, n) "starts new string" by storing an index value
+ * in list[n] so that it points past last stored byte so far.
+ * It returns n+1. */
+static int o_save_ptr_helper(o_string *o, int n)
 {
-       struct pipe *job;
-       const char *status_string;
-
-       for (job = job_list; job; job = job->next) {
-               if (job->running_progs == job->stopped_progs)
-                       status_string = "Stopped";
-               else
-                       status_string = "Running";
+       char **list = (char**)o->data;
+       int string_start;
+       int string_len;
 
-               printf(JOB_STATUS_FORMAT, job->jobid, status_string, job->cmdtext);
+       if (!o->has_empty_slot) {
+               string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
+               string_len = o->length - string_start;
+               if (!(n & 0xf)) { /* 0, 0x10, 0x20...? */
+                       debug_printf_list("list[%d]=%d string_start=%d (growing)", n, string_len, string_start);
+                       /* list[n] points to string_start, make space for 16 more pointers */
+                       o->maxlen += 0x10 * sizeof(list[0]);
+                       o->data = xrealloc(o->data, o->maxlen + 1);
+                       list = (char**)o->data;
+                       memmove(list + n + 0x10, list + n, string_len);
+                       o->length += 0x10 * sizeof(list[0]);
+               } else
+                       debug_printf_list("list[%d]=%d string_start=%d", n, string_len, string_start);
+       } else {
+               /* We have empty slot at list[n], reuse without growth */
+               string_start = ((n+1 + 0xf) & ~0xf) * sizeof(list[0]); /* NB: n+1! */
+               string_len = o->length - string_start;
+               debug_printf_list("list[%d]=%d string_start=%d (empty slot)", n, string_len, string_start);
+               o->has_empty_slot = 0;
        }
-       return EXIT_SUCCESS;
-}
-#endif
-
-/* built-in 'pwd' handler */
-static int builtin_pwd(char **argv ATTRIBUTE_UNUSED)
-{
-       puts(set_cwd());
-       return EXIT_SUCCESS;
+       list[n] = (char*)string_len;
+       return n + 1;
 }
 
-/* built-in 'read VAR' handler */
-static int builtin_read(char **argv)
+/* "What was our last o_save_ptr'ed position (byte offset relative o->data)?" */
+static int o_get_last_ptr(o_string *o, int n)
 {
-       char *string;
-       const char *name = argv[1] ? argv[1] : "REPLY";
+       char **list = (char**)o->data;
+       int string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
 
-       string = xmalloc_reads(STDIN_FILENO, xasprintf("%s=", name), NULL);
-       return set_local_var(string, 0);
+       return ((int)list[n-1]) + string_start;
 }
 
-/* built-in 'set [VAR=value]' handler */
-static int builtin_set(char **argv)
+/* o_glob performs globbing on last list[], saving each result
+ * as a new list[]. unbackslash() is just a helper */
+static char *unbackslash(char *src)
 {
-       char *temp = argv[1];
-       struct variable *e;
-
-       if (temp == NULL)
-               for (e = top_var; e; e = e->next)
-                       puts(e->varstr);
-       else
-               set_local_var(xstrdup(temp), 0);
-
-       return EXIT_SUCCESS;
+       char *dst = src;
+       while (1) {
+               if (*src == '\\')
+                       src++;
+               if ((*dst++ = *src++) == '\0')
+                       break;
+       }
+       return dst;
 }
+static int o_glob(o_string *o, int n)
+{
+       glob_t globdata;
+       int gr;
+       char *pattern;
 
+       debug_printf_glob("start o_glob: n:%d o->data:%p", n, o->data);
+       if (!o->data)
+               return o_save_ptr_helper(o, n);
+       pattern = o->data + o_get_last_ptr(o, n);
+       debug_printf_glob("glob pattern '%s'", pattern);
+       if (!glob_needed(pattern)) {
+ literal:
+               o->length = unbackslash(pattern) - o->data;
+               debug_printf_glob("glob pattern '%s' is literal", pattern);
+               return o_save_ptr_helper(o, n);
+       }
 
-/* Built-in 'shift' handler */
-static int builtin_shift(char **argv)
-{
-       int n = 1;
-       if (argv[1]) {
-               n = atoi(argv[1]);
+       memset(&globdata, 0, sizeof(globdata));
+       gr = glob(pattern, 0, NULL, &globdata);
+       debug_printf_glob("glob('%s'):%d\n", pattern, gr);
+       if (gr == GLOB_NOSPACE)
+               bb_error_msg_and_die("out of memory during glob");
+       if (gr == GLOB_NOMATCH) {
+               globfree(&globdata);
+               goto literal;
        }
-       if (n >= 0 && n < global_argc) {
-               global_argv[n] = global_argv[0];
-               global_argc -= n;
-               global_argv += n;
-               return EXIT_SUCCESS;
+       if (gr != 0) { /* GLOB_ABORTED ? */
+//TODO: testcase for bad glob pattern behavior
+               bb_error_msg("glob(3) error %d on '%s'", gr, pattern);
        }
-       return EXIT_FAILURE;
+       if (globdata.gl_pathv && globdata.gl_pathv[0]) {
+               char **argv = globdata.gl_pathv;
+               o->length = pattern - o->data; /* "forget" pattern */
+               while (1) {
+                       o_addstr(o, *argv, strlen(*argv) + 1);
+                       n = o_save_ptr_helper(o, n);
+                       argv++;
+                       if (!*argv)
+                               break;
+               }
+       }
+       globfree(&globdata);
+       if (DEBUG_GLOB)
+               debug_print_list("o_glob returning", o, n);
+       return n;
 }
 
-/* Built-in '.' handler (read-in and execute commands from file) */
-static int builtin_source(char **argv)
+/* If o->o_glob == 1, glob the string so far remembered.
+ * Otherwise, just finish current list[] and start new */
+static int o_save_ptr(o_string *o, int n)
 {
-       FILE *input;
-       int status;
-
-       if (argv[1] == NULL)
-               return EXIT_FAILURE;
-
-       /* XXX search through $PATH is missing */
-       input = fopen(argv[1], "r");
-       if (!input) {
-               bb_error_msg("cannot open '%s'", argv[1]);
-               return EXIT_FAILURE;
-       }
-       close_on_exec_on(fileno(input));
-
-       /* Now run the file */
-       /* XXX argv and argc are broken; need to save old global_argv
-        * (pointer only is OK!) on this stack frame,
-        * set global_argv=argv+1, recurse, and restore. */
-       status = parse_and_run_file(input);
-       fclose(input);
-       return status;
+       if (o->o_glob)
+               return o_glob(o, n); /* o_save_ptr_helper is inside */
+       return o_save_ptr_helper(o, n);
 }
 
-static int builtin_umask(char **argv)
+/* "Please convert list[n] to real char* ptrs, and NULL terminate it." */
+static char **o_finalize_list(o_string *o, int n)
 {
-       mode_t new_umask;
-       const char *arg = argv[1];
-       char *end;
-       if (arg) {
-               new_umask = strtoul(arg, &end, 8);
-               if (*end != '\0' || end == arg) {
-                       return EXIT_FAILURE;
-               }
-       } else {
-               new_umask = umask(0);
-               printf("%.3o\n", (unsigned) new_umask);
+       char **list;
+       int string_start;
+
+       n = o_save_ptr(o, n); /* force growth for list[n] if necessary */
+       if (DEBUG_EXPAND)
+               debug_print_list("finalized", o, n);
+       debug_printf_expand("finalized n:%d", n);
+       list = (char**)o->data;
+       string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
+       list[--n] = NULL;
+       while (n) {
+               n--;
+               list[n] = o->data + (int)list[n] + string_start;
        }
-       umask(new_umask);
-       return EXIT_SUCCESS;
+       return list;
 }
 
-/* built-in 'unset VAR' handler */
-static int builtin_unset(char **argv)
-{
-       /* bash always returns true */
-       unset_local_var(argv[1]);
-       return EXIT_SUCCESS;
-}
 
 /*
- * o_string support
+ * in_str support
  */
-#define B_CHUNK  (32 * sizeof(char*))
-
-static void o_reset(o_string *o)
+static int static_get(struct in_str *i)
 {
-       o->length = 0;
-       o->nonnull = 0;
-       if (o->data)
-               o->data[0] = '\0';
+       int ch = *i->p++;
+       if (ch == '\0') return EOF;
+       return ch;
 }
 
-static void o_free(o_string *o)
+static int static_peek(struct in_str *i)
 {
-       free(o->data);
-       memset(o, 0, sizeof(*o));
+       return *i->p;
 }
 
-static void o_grow_by(o_string *o, int len)
-{
-       if (o->length + len > o->maxlen) {
-               o->maxlen += (2*len > B_CHUNK ? 2*len : B_CHUNK);
-               o->data = xrealloc(o->data, 1 + o->maxlen);
-       }
-}
+#if ENABLE_HUSH_INTERACTIVE
 
-static void o_addchr(o_string *o, int ch)
+#if ENABLE_FEATURE_EDITING
+static void cmdedit_set_initial_prompt(void)
 {
-       debug_printf("o_addchr: '%c' o->length=%d o=%p\n", ch, o->length, o);
-       o_grow_by(o, 1);
-       o->data[o->length] = ch;
-       o->length++;
-       o->data[o->length] = '\0';
+#if !ENABLE_FEATURE_EDITING_FANCY_PROMPT
+       PS1 = NULL;
+#else
+       PS1 = getenv("PS1");
+       if (PS1 == NULL)
+               PS1 = "\\w \\$ ";
+#endif
 }
+#endif /* EDITING */
 
-static void o_addstr(o_string *o, const char *str, int len)
-{
-       o_grow_by(o, len);
-       memcpy(&o->data[o->length], str, len);
-       o->length += len;
-       o->data[o->length] = '\0';
-}
-
-/* My analysis of quoting semantics tells me that state information
- * is associated with a destination, not a source.
- */
-static void o_addqchr(o_string *o, int ch)
-{
-       int sz = 1;
-       if (strchr("*?[\\", ch)) {
-               sz++;
-               o->data[o->length] = '\\';
-               o->length++;
-       }
-       o_grow_by(o, sz);
-       o->data[o->length] = ch;
-       o->length++;
-       o->data[o->length] = '\0';
-}
-
-static void o_addQchr(o_string *o, int ch)
-{
-       int sz = 1;
-       if (o->o_quote && strchr("*?[\\", ch)) {
-               sz++;
-               o->data[o->length] = '\\';
-               o->length++;
-       }
-       o_grow_by(o, sz);
-       o->data[o->length] = ch;
-       o->length++;
-       o->data[o->length] = '\0';
-}
-
-static void o_addQstr(o_string *o, const char *str, int len)
-{
-       if (!o->o_quote) {
-               o_addstr(o, str, len);
-               return;
-       }
-       while (len) {
-               char ch;
-               int sz;
-               int ordinary_cnt = strcspn(str, "*?[\\");
-               if (ordinary_cnt > len) /* paranoia */
-                       ordinary_cnt = len;
-               o_addstr(o, str, ordinary_cnt);
-               if (ordinary_cnt == len)
-                       return;
-               str += ordinary_cnt;
-               len -= ordinary_cnt - 1; /* we are processing + 1 char below */
-
-               ch = *str++;
-               sz = 1;
-               if (ch) { /* it is necessarily one of "*?[\\" */
-                       sz++;
-                       o->data[o->length] = '\\';
-                       o->length++;
-               }
-               o_grow_by(o, sz);
-               o->data[o->length] = ch;
-               o->length++;
-               o->data[o->length] = '\0';
-       }
-}
-
-/* A special kind of o_string for $VAR and `cmd` expansion.
- * It contains char* list[] at the beginning, which is grown in 16 element
- * increments. Actual string data starts at the next multiple of 16.
- * list[i] contains an INDEX (int!) into this string data.
- * It means that if list[] needs to grow, data needs to be moved higher up
- * but list[i]'s need not be modified.
- * NB: remembering how many list[i]'s you have there is crucial.
- * o_finalize_list() operation post-processes this structure - calculates
- * and stores actual char* ptrs in list[]. Oh, it NULL terminates it as well.
- */
-#if DEBUG_EXPAND || DEBUG_GLOB
-static void debug_print_list(const char *prefix, o_string *o, int n)
-{
-       char **list = (char**)o->data;
-       int string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
-       int i = 0;
-       fprintf(stderr, "%s: list:%p n:%d string_start:%d length:%d maxlen:%d\n",
-                       prefix, list, n, string_start, o->length, o->maxlen);
-       while (i < n) {
-               fprintf(stderr, " list[%d]=%d '%s' %p\n", i, (int)list[i],
-                               o->data + (int)list[i] + string_start,
-                               o->data + (int)list[i] + string_start);
-               i++;
-       }
-       if (n) {
-               const char *p = o->data + (int)list[n - 1] + string_start;
-               fprintf(stderr, " total_sz:%d\n", (p + strlen(p) + 1) - o->data);
-       }
-}
-#else
-#define debug_print_list(prefix, o, n) ((void)0)
-#endif
-
-/* n = o_save_ptr_helper(str, n) "starts new string" by storing an index value
- * in list[n] so that it points past last stored byte so far.
- * It returns n+1. */
-static int o_save_ptr_helper(o_string *o, int n)
-{
-       char **list = (char**)o->data;
-       int string_start;
-       int string_len;
-
-       if (!o->has_empty_slot) {
-               string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
-               string_len = o->length - string_start;
-               if (!(n & 0xf)) { /* 0, 0x10, 0x20...? */
-                       debug_printf_list("list[%d]=%d string_start=%d (growing)", n, string_len, string_start);
-                       /* list[n] points to string_start, make space for 16 more pointers */
-                       o->maxlen += 0x10 * sizeof(list[0]);
-                       o->data = xrealloc(o->data, o->maxlen + 1);
-                       list = (char**)o->data;
-                       memmove(list + n + 0x10, list + n, string_len);
-                       o->length += 0x10 * sizeof(list[0]);
-               } else
-                       debug_printf_list("list[%d]=%d string_start=%d", n, string_len, string_start);
-       } else {
-               /* We have empty slot at list[n], reuse without growth */
-               string_start = ((n+1 + 0xf) & ~0xf) * sizeof(list[0]); /* NB: n+1! */
-               string_len = o->length - string_start;
-               debug_printf_list("list[%d]=%d string_start=%d (empty slot)", n, string_len, string_start);
-               o->has_empty_slot = 0;
-       }
-       list[n] = (char*)string_len;
-       return n + 1;
-}
-
-/* "What was our last o_save_ptr'ed position (byte offset relative o->data)?" */
-static int o_get_last_ptr(o_string *o, int n)
-{
-       char **list = (char**)o->data;
-       int string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
-
-       return ((int)list[n-1]) + string_start;
-}
-
-/* Convert every \x to x in-place, return ptr past NUL. */
-static char *unbackslash(char *src)
-{
-       char *dst = src;
-       while (1) {
-               if (*src == '\\')
-                       src++;
-               if ((*dst++ = *src++) == '\0')
-                       break;
-       }
-       return dst;
-}
-
-static int o_glob(o_string *o, int n)
-{
-       glob_t globdata;
-       int gr;
-       char *pattern;
-
-       debug_printf_glob("start o_glob: n:%d o->data:%p", n, o->data);
-       if (!o->data)
-               return o_save_ptr_helper(o, n);
-       pattern = o->data + o_get_last_ptr(o, n);
-       debug_printf_glob("glob pattern '%s'", pattern);
-       if (!glob_needed(pattern)) {
- literal:
-               o->length = unbackslash(pattern) - o->data;
-               debug_printf_glob("glob pattern '%s' is literal", pattern);
-               return o_save_ptr_helper(o, n);
-       }
-
-       memset(&globdata, 0, sizeof(globdata));
-       gr = glob(pattern, 0, NULL, &globdata);
-       debug_printf_glob("glob('%s'):%d\n", pattern, gr);
-       if (gr == GLOB_NOSPACE)
-               bb_error_msg_and_die("out of memory during glob");
-       if (gr == GLOB_NOMATCH) {
-               globfree(&globdata);
-               goto literal;
-       }
-       if (gr != 0) { /* GLOB_ABORTED ? */
-//TODO: testcase for bad glob pattern behavior
-               bb_error_msg("glob(3) error %d on '%s'", gr, pattern);
-       }
-       if (globdata.gl_pathv && globdata.gl_pathv[0]) {
-               char **argv = globdata.gl_pathv;
-               o->length = pattern - o->data; /* "forget" pattern */
-               while (1) {
-                       o_addstr(o, *argv, strlen(*argv) + 1);
-                       n = o_save_ptr_helper(o, n);
-                       argv++;
-                       if (!*argv)
-                               break;
-               }
-       }
-       globfree(&globdata);
-       if (DEBUG_GLOB)
-               debug_print_list("o_glob returning", o, n);
-       return n;
-}
-
-/* o_save_ptr_helper + but glob the string so far remembered
- * if o->o_glob == 1 */
-static int o_save_ptr(o_string *o, int n)
-{
-       if (o->o_glob)
-               return o_glob(o, n); /* o_save_ptr_helper is inside */
-       return o_save_ptr_helper(o, n);
-}
-
-/* "Please convert list[n] to real char* ptrs, and NULL terminate it." */
-static char **o_finalize_list(o_string *o, int n)
-{
-       char **list;
-       int string_start;
-
-       n = o_save_ptr(o, n); /* force growth for list[n] if necessary */
-       if (DEBUG_EXPAND)
-               debug_print_list("finalized", o, n);
-       debug_printf_expand("finalized n:%d", n);
-       list = (char**)o->data;
-       string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
-       list[--n] = NULL;
-       while (n) {
-               n--;
-               list[n] = o->data + (int)list[n] + string_start;
-       }
-       return list;
-}
-
-
-/*
- * in_str support
- */
-static int static_get(struct in_str *i)
-{
-       int ch = *i->p++;
-       if (ch == '\0') return EOF;
-       return ch;
-}
-
-static int static_peek(struct in_str *i)
-{
-       return *i->p;
-}
-
-#if ENABLE_HUSH_INTERACTIVE
-#if ENABLE_FEATURE_EDITING
-static void cmdedit_set_initial_prompt(void)
-{
-#if !ENABLE_FEATURE_EDITING_FANCY_PROMPT
-       PS1 = NULL;
-#else
-       PS1 = getenv("PS1");
-       if (PS1 == NULL)
-               PS1 = "\\w \\$ ";
-#endif
-}
-#endif /* EDITING */
-
-static const char* setup_prompt_string(int promptmode)
+static const char* setup_prompt_string(int promptmode)
 {
        const char *prompt_str;
        debug_printf("setup_prompt_string %d ", promptmode);
@@ -1521,6 +1196,7 @@ static void get_user_input(struct in_str *i)
 #endif
        i->p = user_input_buf;
 }
+
 #endif  /* INTERACTIVE */
 
 /* This is the magic location that prints prompts
@@ -1604,6 +1280,7 @@ static void setup_string_in_str(struct in_str *i, const char *s)
        i->eof_flag = 0;
 }
 
+
 /* 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[])
@@ -1659,6 +1336,7 @@ static void restore_redirects(int squirrel[])
        }
 }
 
+
 /* Called after [v]fork() in run_pipe(), or from builtin_exec().
  * Never returns.
  * XXX no exit() here.  If you don't exec, use _exit instead.
@@ -2589,6 +2267,7 @@ static int run_and_free_list(struct pipe *pi)
        return rcode;
 }
 
+
 /* expand_strvec_to_strvec() takes a list of strings, expands
  * all variable references within and returns a pointer to
  * a list of expanded strings, possibly with larger number
@@ -2737,7 +2416,9 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
                        } else
                                val = lookup_param(arg);
                        arg[0] = first_ch;
+#if ENABLE_HUSH_TICK
  store_val:
+#endif
                        *p = SPECIAL_VAR_SYMBOL;
                        if (!(first_ch & 0x80)) { /* unquoted $VAR */
                                if (val) {
@@ -2801,7 +2482,7 @@ static char **expand_strvec_to_strvec(char **argv)
        return expand_variables(argv, 0x100);
 }
 
-/* used for expansion of right hand of assignments */
+/* Used for expansion of right hand of assignments */
 /* NB: should NOT do globbing! "export v=/bin/c*; env | grep ^v=" outputs
  * "v=/bin/c*" */
 static char *expand_string_to_string(const char *str)
@@ -2820,7 +2501,7 @@ static char *expand_string_to_string(const char *str)
        return (char*)list;
 }
 
-/* used for eval */
+/* Used for "eval" builtin */
 static char* expand_strvec_to_string(char **argv)
 {
        char **list;
@@ -2842,7 +2523,8 @@ static char* expand_strvec_to_string(char **argv)
        return (char*)list;
 }
 
-/* This is used to get/check local shell variables */
+
+/* Used to get/check local shell variables */
 static struct variable *get_local_var(const char *name)
 {
        struct variable *cur;
@@ -3099,12 +2781,16 @@ static int reserved_word(const o_string *word, struct p_context *ctx)
                        continue;
                debug_printf("found reserved word %s, res %d\n", r->literal, r->res);
                if (r->flag == 0) { /* '!' */
+#if ENABLE_HUSH_LOOPS
                        if (ctx->res_w == RES_IN) {
                                /* 'for a in ! a b c; ...' - ! isn't a keyword here */
                                break;
                        }
-                       if (ctx->res_w == RES_FOR /* example: 'for ! a' */
-                        || ctx->ctx_inverted /* bash doesn't accept '! ! true' */
+#endif
+                       if (ctx->ctx_inverted /* bash doesn't accept '! ! true' */
+#if ENABLE_HUSH_LOOPS
+                        || ctx->res_w == RES_FOR /* example: 'for ! a' */
+#endif
                        ) {
                                syntax(NULL);
                                ctx->res_w = RES_SNTX;
@@ -3152,8 +2838,7 @@ static int reserved_word(const o_string *word, struct p_context *ctx)
 #endif
 
 /* Word is complete, look at it and update parsing context.
- * Normal return is 0.
- * Syntax or xglob errors return 1. */
+ * Normal return is 0. Syntax errors return 1. */
 static int done_word(o_string *word, struct p_context *ctx)
 {
        struct child_prog *child = ctx->child;
@@ -3184,7 +2869,7 @@ static int done_word(o_string *word, struct p_context *ctx)
        }
 
        if (word->length || word->nonnull) {
-               *glob_target = add_string_to_strings(*glob_target, xstrdup(word->data));
+               *glob_target = add_malloced_string_to_strings(*glob_target, xstrdup(word->data));
                debug_print_strings("glob_target appended", *glob_target);
        }
 
@@ -3282,9 +2967,9 @@ static void done_pipe(struct p_context *ctx, pipe_style type)
        debug_printf_parse("done_pipe return\n");
 }
 
-/* peek ahead in the in_str to find out if we have a "&n" construct,
+/* Peek ahead in the in_str to find out if we have a "&n" construct,
  * as in "2>&1", that represents duplicating a file descriptor.
- * returns either -2 (syntax error), -1 (no &), or the number found.
+ * Return either -2 (syntax error), -1 (no &), or the number found.
  */
 static int redirect_dup_num(struct in_str *input)
 {
@@ -3395,7 +3080,7 @@ static int process_command_subs(o_string *dest,
 
        initialize_context(&inner);
 
-       /* recursion to generate command */
+       /* Recursion to generate command */
        retcode = parse_stream(&result, &inner, input, subst_end);
        if (retcode != 0)
                return retcode;  /* syntax error or EOF */
@@ -3409,7 +3094,7 @@ static int process_command_subs(o_string *dest,
        close_on_exec_on(fileno(p));
        setup_file_in_str(&pipe_str, p);
 
-       /* now send results of command back into original context */
+       /* Now send results of command back into original context */
        eol_cnt = 0;
        while ((ch = i_getch(&pipe_str)) != EOF) {
                if (ch == '\n') {
@@ -3596,7 +3281,7 @@ static void add_till_closing_curly_brace(o_string *dest, struct in_str *input)
 }
 #endif /* ENABLE_HUSH_TICK */
 
-/* return code: 0 for OK, 1 for syntax error */
+/* Return code: 0 for OK, 1 for syntax error */
 static int handle_dollar(o_string *dest, struct in_str *input)
 {
        int ch = i_peek(input);  /* first character after the $ */
@@ -3674,7 +3359,7 @@ static int handle_dollar(o_string *dest, struct in_str *input)
        return 0;
 }
 
-/* return code is 0 for normal exit, 1 for syntax error */
+/* Return code is 0 for normal exit, 1 for syntax error */
 static int parse_stream(o_string *dest, struct p_context *ctx,
        struct in_str *input, const char *end_trigger)
 {
@@ -3937,7 +3622,7 @@ static void update_charmap(void)
        set_in_charmap(ifs, CHAR_IFS);  /* are ordinary if quoted */
 }
 
-/* most recursion does not come through here, the exception is
+/* Most recursion does not come through here, the exception is
  * from builtin_source() and builtin_eval() */
 static int parse_and_run_stream(struct in_str *inp, int parse_flag)
 {
@@ -3975,7 +3660,9 @@ static int parse_and_run_stream(struct in_str *inp, int parse_flag)
                        free_pipe_list(ctx.list_head, /* indent: */ 0);
                        /* Discard all unprocessed line input, force prompt on */
                        inp->p = NULL;
+#if ENABLE_HUSH_INTERACTIVE
                        inp->promptme = 1;
+#endif
                }
                o_free(&temp);
                /* loop on syntax errors, return on EOF: */
@@ -4034,6 +3721,7 @@ static void setup_job_control(void)
 }
 #endif
 
+
 int hush_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int hush_main(int argc, char **argv)
 {
@@ -4230,3 +3918,318 @@ int lash_main(int argc, char **argv)
        return hush_main(argc, argv);
 }
 #endif
+
+
+/*
+ * Built-ins
+ */
+static int builtin_true(char **argv ATTRIBUTE_UNUSED)
+{
+       return 0;
+}
+
+static int builtin_test(char **argv)
+{
+       int argc = 0;
+       while (*argv) {
+               argc++;
+               argv++;
+       }
+       return test_main(argc, argv - argc);
+}
+
+static int builtin_echo(char **argv)
+{
+       int argc = 0;
+       while (*argv) {
+               argc++;
+               argv++;
+       }
+       return echo_main(argc, argv - argc);
+}
+
+static int builtin_eval(char **argv)
+{
+       int rcode = EXIT_SUCCESS;
+
+       if (argv[1]) {
+               char *str = expand_strvec_to_string(argv + 1);
+               parse_and_run_string(str, PARSEFLAG_EXIT_FROM_LOOP);
+               free(str);
+               rcode = last_return_code;
+       }
+       return rcode;
+}
+
+static int builtin_cd(char **argv)
+{
+       const char *newdir;
+       if (argv[1] == NULL) {
+               // bash does nothing (exitcode 0) if HOME is ""; if it's unset,
+               // bash says "bash: cd: HOME not set" and does nothing (exitcode 1)
+               newdir = getenv("HOME") ? : "/";
+       } else
+               newdir = argv[1];
+       if (chdir(newdir)) {
+               printf("cd: %s: %s\n", newdir, strerror(errno));
+               return EXIT_FAILURE;
+       }
+       set_cwd();
+       return EXIT_SUCCESS;
+}
+
+static int builtin_exec(char **argv)
+{
+       if (argv[1] == NULL)
+               return EXIT_SUCCESS; /* bash does this */
+       {
+#if !BB_MMU
+               char **ptrs2free = alloc_ptrs(argv);
+#endif
+// FIXME: if exec fails, bash does NOT exit! We do...
+               pseudo_exec_argv(ptrs2free, argv + 1);
+               /* never returns */
+       }
+}
+
+static int builtin_exit(char **argv)
+{
+// TODO: bash does it ONLY on top-level sh exit (+interacive only?)
+       //puts("exit"); /* bash does it */
+// TODO: warn if we have background jobs: "There are stopped jobs"
+// On second consecutive 'exit', exit anyway.
+       if (argv[1] == NULL)
+               hush_exit(last_return_code);
+       /* mimic bash: exit 123abc == exit 255 + error msg */
+       xfunc_error_retval = 255;
+       /* bash: exit -2 == exit 254, no error msg */
+       hush_exit(xatoi(argv[1]) & 0xff);
+}
+
+static int builtin_export(char **argv)
+{
+       const char *value;
+       char *name = argv[1];
+
+       if (name == NULL) {
+               // TODO:
+               // ash emits: export VAR='VAL'
+               // bash: declare -x VAR="VAL"
+               // (both also escape as needed (quotes, $, etc))
+               char **e = environ;
+               if (e)
+                       while (*e)
+                               puts(*e++);
+               return EXIT_SUCCESS;
+       }
+
+       value = strchr(name, '=');
+       if (!value) {
+               /* They are exporting something without a =VALUE */
+               struct variable *var;
+
+               var = get_local_var(name);
+               if (var) {
+                       var->flg_export = 1;
+                       putenv(var->varstr);
+               }
+               /* bash does not return an error when trying to export
+                * an undefined variable.  Do likewise. */
+               return EXIT_SUCCESS;
+       }
+
+       set_local_var(xstrdup(name), 1);
+       return EXIT_SUCCESS;
+}
+
+#if ENABLE_HUSH_JOB
+/* built-in 'fg' and 'bg' handler */
+static int builtin_fg_bg(char **argv)
+{
+       int i, jobnum;
+       struct pipe *pi;
+
+       if (!interactive_fd)
+               return EXIT_FAILURE;
+       /* If they gave us no args, assume they want the last backgrounded task */
+       if (!argv[1]) {
+               for (pi = job_list; pi; pi = pi->next) {
+                       if (pi->jobid == last_jobid) {
+                               goto found;
+                       }
+               }
+               bb_error_msg("%s: no current job", argv[0]);
+               return EXIT_FAILURE;
+       }
+       if (sscanf(argv[1], "%%%d", &jobnum) != 1) {
+               bb_error_msg("%s: bad argument '%s'", argv[0], argv[1]);
+               return EXIT_FAILURE;
+       }
+       for (pi = job_list; pi; pi = pi->next) {
+               if (pi->jobid == jobnum) {
+                       goto found;
+               }
+       }
+       bb_error_msg("%s: %d: no such job", argv[0], jobnum);
+       return EXIT_FAILURE;
+ found:
+       // TODO: bash prints a string representation
+       // of job being foregrounded (like "sleep 1 | cat")
+       if (*argv[0] == 'f') {
+               /* Put the job into the foreground.  */
+               tcsetpgrp(interactive_fd, pi->pgrp);
+       }
+
+       /* 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;
+       }
+       pi->stopped_progs = 0;
+
+       i = kill(- pi->pgrp, SIGCONT);
+       if (i < 0) {
+               if (errno == ESRCH) {
+                       delete_finished_bg_job(pi);
+                       return EXIT_SUCCESS;
+               } else {
+                       bb_perror_msg("kill (SIGCONT)");
+               }
+       }
+
+       if (*argv[0] == 'f') {
+               remove_bg_job(pi);
+               return checkjobs_and_fg_shell(pi);
+       }
+       return EXIT_SUCCESS;
+}
+#endif
+
+#if ENABLE_HUSH_HELP
+static int builtin_help(char **argv ATTRIBUTE_UNUSED)
+{
+       const struct built_in_command *x;
+
+       printf("\nBuilt-in commands:\n");
+       printf("-------------------\n");
+       for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
+               printf("%s\t%s\n", x->cmd, x->descr);
+       }
+       printf("\n\n");
+       return EXIT_SUCCESS;
+}
+#endif
+
+#if ENABLE_HUSH_JOB
+static int builtin_jobs(char **argv ATTRIBUTE_UNUSED)
+{
+       struct pipe *job;
+       const char *status_string;
+
+       for (job = job_list; job; job = job->next) {
+               if (job->running_progs == job->stopped_progs)
+                       status_string = "Stopped";
+               else
+                       status_string = "Running";
+
+               printf(JOB_STATUS_FORMAT, job->jobid, status_string, job->cmdtext);
+       }
+       return EXIT_SUCCESS;
+}
+#endif
+
+static int builtin_pwd(char **argv ATTRIBUTE_UNUSED)
+{
+       puts(set_cwd());
+       return EXIT_SUCCESS;
+}
+
+static int builtin_read(char **argv)
+{
+       char *string;
+       const char *name = argv[1] ? argv[1] : "REPLY";
+
+       string = xmalloc_reads(STDIN_FILENO, xasprintf("%s=", name), NULL);
+       return set_local_var(string, 0);
+}
+
+/* built-in 'set [VAR=value]' handler */
+static int builtin_set(char **argv)
+{
+       char *temp = argv[1];
+       struct variable *e;
+
+       if (temp == NULL)
+               for (e = top_var; e; e = e->next)
+                       puts(e->varstr);
+       else
+               set_local_var(xstrdup(temp), 0);
+
+       return EXIT_SUCCESS;
+}
+
+static int builtin_shift(char **argv)
+{
+       int n = 1;
+       if (argv[1]) {
+               n = atoi(argv[1]);
+       }
+       if (n >= 0 && n < global_argc) {
+               global_argv[n] = global_argv[0];
+               global_argc -= n;
+               global_argv += n;
+               return EXIT_SUCCESS;
+       }
+       return EXIT_FAILURE;
+}
+
+static int builtin_source(char **argv)
+{
+       FILE *input;
+       int status;
+
+       if (argv[1] == NULL)
+               return EXIT_FAILURE;
+
+       /* XXX search through $PATH is missing */
+       input = fopen(argv[1], "r");
+       if (!input) {
+               bb_error_msg("cannot open '%s'", argv[1]);
+               return EXIT_FAILURE;
+       }
+       close_on_exec_on(fileno(input));
+
+       /* Now run the file */
+       /* XXX argv and argc are broken; need to save old global_argv
+        * (pointer only is OK!) on this stack frame,
+        * set global_argv=argv+1, recurse, and restore. */
+       status = parse_and_run_file(input);
+       fclose(input);
+       return status;
+}
+
+static int builtin_umask(char **argv)
+{
+       mode_t new_umask;
+       const char *arg = argv[1];
+       char *end;
+       if (arg) {
+               new_umask = strtoul(arg, &end, 8);
+               if (*end != '\0' || end == arg) {
+                       return EXIT_FAILURE;
+               }
+       } else {
+               new_umask = umask(0);
+               printf("%.3o\n", (unsigned) new_umask);
+       }
+       umask(new_umask);
+       return EXIT_SUCCESS;
+}
+
+static int builtin_unset(char **argv)
+{
+       /* bash always returns true */
+       unset_local_var(argv[1]);
+       return EXIT_SUCCESS;
+}
index 54a19aa6fcdc9242ddf3956aaed77591ff6a1c8f..54161b3e98aea1064f2cc7614aa3439ad621d9d1 100644 (file)
@@ -8,6 +8,6 @@ freelist=`grep 'free 0x' "$output" | cut -d' ' -f2 | sort | uniq | xargs`
 grep -v free "$output" >temp1
 for freed in $freelist; do
     echo Dropping $freed
-    cat temp1 | grep -v $freed >temp2
+    grep -v $freed <temp1 >temp2
     mv temp2 temp1
 done