+ child->redirects = NULL;
+ }
+ free(pi->progs); /* children are an array, they get freed all at once */
+ pi->progs = NULL;
+#if ENABLE_HUSH_JOB
+ free(pi->cmdtext);
+ pi->cmdtext = NULL;
+#endif
+ return ret_code;
+}
+
+static int free_pipe_list(struct pipe *head, int indent)
+{
+ int rcode = 0; /* if list has no members */
+ struct pipe *pi, *next;
+
+ for (pi = head; pi; pi = next) {
+ debug_printf_clean("%s pipe reserved mode %d\n", indenter(indent), pi->res_word);
+ rcode = free_pipe(pi, indent);
+ debug_printf_clean("%s pipe followup code %d\n", indenter(indent), pi->followup);
+ next = pi->next;
+ /*pi->next = NULL;*/
+ free(pi);
+ }
+ return rcode;
+}
+
+/* Select which version we will use */
+static int run_and_free_list(struct pipe *pi)
+{
+ int rcode = 0;
+ debug_printf_exec("run_and_free_list entered\n");
+ if (!fake_mode) {
+ debug_printf_exec(": run_list with %d members\n", pi->num_progs);
+ rcode = run_list(pi);
+ }
+ /* free_pipe_list has the side effect of clearing memory.
+ * In the long run that function can be merged with run_list,
+ * but doing that now would hobble the debugging effort. */
+ free_pipe_list(pi, /* indent: */ 0);
+ debug_printf_exec("run_nad_free_list return %d\n", rcode);
+ return rcode;
+}
+
+/* Whoever decided to muck with glob internal data is AN IDIOT! */
+/* uclibc happily changed the way it works (and it has rights to do so!),
+ all hell broke loose (SEGVs) */
+
+/* The API for glob is arguably broken. This routine pushes a non-matching
+ * string into the output structure, removing non-backslashed backslashes.
+ * If someone can prove me wrong, by performing this function within the
+ * original glob(3) api, feel free to rewrite this routine into oblivion.
+ * XXX broken if the last character is '\\', check that before calling.
+ */
+static char **globhack(const char *src, char **strings)
+{
+ int cnt;
+ const char *s;
+ char *v, *dest;
+
+ for (cnt = 1, s = src; s && *s; s++) {
+ if (*s == '\\') s++;
+ cnt++;
+ }
+ v = dest = xmalloc(cnt);
+ for (s = src; s && *s; s++, dest++) {
+ if (*s == '\\') s++;
+ *dest = *s;
+ }
+ *dest = '\0';
+
+ return add_string_to_strings(strings, v);
+}
+
+/* XXX broken if the last character is '\\', check that before calling */
+static int glob_needed(const char *s)
+{
+ for (; *s; s++) {
+ if (*s == '\\')
+ s++;
+ if (strchr("*[?", *s))
+ return 1;
+ }
+ return 0;
+}
+
+static int xglob(o_string *dest, char ***pglob)
+{
+ /* short-circuit for null word */
+ /* we can code this better when the debug_printf's are gone */
+ if (dest->length == 0) {
+ if (dest->nonnull) {
+ /* bash man page calls this an "explicit" null */
+ *pglob = globhack(dest->data, *pglob);
+ }
+ return 0;
+ }
+
+ if (glob_needed(dest->data)) {
+ glob_t globdata;
+ int gr;
+
+ memset(&globdata, 0, sizeof(globdata));
+ gr = glob(dest->data, 0, NULL, &globdata);
+ debug_printf("glob returned %d\n", gr);
+ if (gr == GLOB_NOSPACE)
+ bb_error_msg_and_die("out of memory during glob");
+ if (gr == GLOB_NOMATCH) {
+ debug_printf("globhack returned %d\n", gr);
+ /* quote removal, or more accurately, backslash removal */
+ *pglob = globhack(dest->data, *pglob);
+ globfree(&globdata);
+ return 0;
+ }
+ if (gr != 0) { /* GLOB_ABORTED ? */
+ bb_error_msg("glob(3) error %d", gr);
+ }
+ if (globdata.gl_pathv && globdata.gl_pathv[0])
+ *pglob = add_strings_to_strings(1, *pglob, globdata.gl_pathv);
+ globfree(&globdata);
+ return gr;
+ }
+
+ *pglob = globhack(dest->data, *pglob);
+ return 0;
+}
+
+/* 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
+ * of strings. (Think VAR="a b"; echo $VAR).
+ * This new list is allocated as a single malloc block.
+ * NULL-terminated list of char* pointers is at the beginning of it,
+ * followed by strings themself.
+ * Caller can deallocate entire list by single free(list). */
+
+/* Helpers first:
+ * count_XXX estimates size of the block we need. It's okay
+ * to over-estimate sizes a bit, if it makes code simpler */
+static int count_ifs(const char *str)
+{
+ int cnt = 0;
+ debug_printf_expand("count_ifs('%s') ifs='%s'", str, ifs);
+ while (1) {
+ str += strcspn(str, ifs);
+ if (!*str) break;
+ str++; /* str += strspn(str, ifs); */
+ cnt++; /* cnt += strspn(str, ifs); - but this code is larger */
+ }
+ debug_printf_expand(" return %d\n", cnt);
+ return cnt;
+}
+
+static void count_var_expansion_space(int *countp, int *lenp, char *arg)
+{
+ char first_ch;
+ int i;
+ int len = *lenp;
+ int count = *countp;
+ const char *val;
+ char *p;
+
+ while ((p = strchr(arg, SPECIAL_VAR_SYMBOL))) {
+ len += p - arg;
+ arg = ++p;
+ p = strchr(p, SPECIAL_VAR_SYMBOL);
+ first_ch = arg[0];
+
+ switch (first_ch & 0x7f) {
+ /* high bit in 1st_ch indicates that var is double-quoted */
+ case '$': /* pid */
+ case '!': /* bg pid */
+ case '?': /* exitcode */
+ case '#': /* argc */
+ len += sizeof(int)*3 + 1; /* enough for int */
+ break;
+ case '*':
+ case '@':
+ for (i = 1; global_argv[i]; i++) {
+ len += strlen(global_argv[i]) + 1;
+ count++;
+ if (!(first_ch & 0x80))
+ count += count_ifs(global_argv[i]);
+ }
+ break;
+ default:
+ *p = '\0';
+ arg[0] = first_ch & 0x7f;
+ if (isdigit(arg[0])) {
+ i = xatoi_u(arg);
+ val = NULL;
+ if (i < global_argc)
+ val = global_argv[i];
+ } else
+ val = lookup_param(arg);
+ arg[0] = first_ch;
+ *p = SPECIAL_VAR_SYMBOL;
+
+ if (val) {
+ len += strlen(val) + 1;
+ if (!(first_ch & 0x80))
+ count += count_ifs(val);
+ }
+ }
+ arg = ++p;
+ }
+
+ len += strlen(arg) + 1;
+ count++;
+ *lenp = len;
+ *countp = count;
+}
+
+/* Store given string, finalizing the word and starting new one whenever
+ * we encounter ifs char(s). This is used for expanding variable values.
+ * End-of-string does NOT finalize word: think about 'echo -$VAR-' */
+static int expand_on_ifs(char **list, int n, char **posp, const char *str)
+{
+ char *pos = *posp;
+ while (1) {
+ int word_len = strcspn(str, ifs);
+ if (word_len) {
+ memcpy(pos, str, word_len); /* store non-ifs chars */
+ pos += word_len;
+ str += word_len;
+ }
+ if (!*str) /* EOL - do not finalize word */
+ break;
+ *pos++ = '\0';
+ if (n) debug_printf_expand("expand_on_ifs finalized list[%d]=%p '%s' "
+ "strlen=%d next=%p pos=%p\n", n-1, list[n-1], list[n-1],
+ strlen(list[n-1]), list[n-1] + strlen(list[n-1]) + 1, pos);
+ list[n++] = pos;
+ str += strspn(str, ifs); /* skip ifs chars */
+ }
+ *posp = pos;
+ return n;
+}
+
+/* Expand all variable references in given string, adding words to list[]
+ * at n, n+1,... positions. Return updated n (so that list[n] is next one
+ * to be filled). This routine is extremely tricky: has to deal with
+ * variables/parameters with whitespace, $* and $@, and constructs like
+ * 'echo -$*-'. If you play here, you must run testsuite afterwards! */
+/* NB: another bug is that we cannot detect empty strings yet:
+ * "" or $empty"" expands to zero words, has to expand to empty word */
+static int expand_vars_to_list(char **list, int n, char **posp, char *arg, char or_mask)
+{
+ /* or_mask is either 0 (normal case) or 0x80
+ * (expansion of right-hand side of assignment == 1-element expand) */
+
+ char first_ch, ored_ch;
+ int i;
+ const char *val;
+ char *p;
+ char *pos = *posp;
+
+ ored_ch = 0;
+
+ if (n) debug_printf_expand("expand_vars_to_list finalized list[%d]=%p '%s' "
+ "strlen=%d next=%p pos=%p\n", n-1, list[n-1], list[n-1],
+ strlen(list[n-1]), list[n-1] + strlen(list[n-1]) + 1, pos);
+ list[n++] = pos;
+
+ while ((p = strchr(arg, SPECIAL_VAR_SYMBOL))) {
+ memcpy(pos, arg, p - arg);
+ pos += (p - arg);
+ arg = ++p;
+ p = strchr(p, SPECIAL_VAR_SYMBOL);
+
+ first_ch = arg[0] | or_mask; /* forced to "quoted" if or_mask = 0x80 */
+ ored_ch |= first_ch;
+ val = NULL;
+ switch (first_ch & 0x7f) {
+ /* Highest bit in first_ch indicates that var is double-quoted */
+ case '$': /* pid */
+ /* FIXME: (echo $$) should still print pid of main shell */
+ val = utoa(getpid()); /* rootpid? */
+ break;
+ case '!': /* bg pid */
+ val = last_bg_pid ? utoa(last_bg_pid) : (char*)"";
+ break;
+ case '?': /* exitcode */
+ val = utoa(last_return_code);
+ break;
+ case '#': /* argc */
+ val = utoa(global_argc ? global_argc-1 : 0);
+ break;
+ case '*':
+ case '@':
+ i = 1;
+ if (!global_argv[i])
+ break;
+ if (!(first_ch & 0x80)) { /* unquoted $* or $@ */
+ while (global_argv[i]) {
+ n = expand_on_ifs(list, n, &pos, global_argv[i]);
+ debug_printf_expand("expand_vars_to_list: argv %d (last %d)\n", i, global_argc-1);
+ if (global_argv[i++][0] && global_argv[i]) {
+ /* this argv[] is not empty and not last:
+ * put terminating NUL, start new word */
+ *pos++ = '\0';
+ if (n) debug_printf_expand("expand_vars_to_list 2 finalized list[%d]=%p '%s' "
+ "strlen=%d next=%p pos=%p\n", n-1, list[n-1], list[n-1],
+ strlen(list[n-1]), list[n-1] + strlen(list[n-1]) + 1, pos);
+ list[n++] = pos;
+ }
+ }
+ } else
+ /* If or_mask is nonzero, we handle assignment 'a=....$@.....'
+ * and in this case should treat it like '$*' - see 'else...' below */
+ if (first_ch == ('@'|0x80) && !or_mask) { /* quoted $@ */
+ while (1) {
+ strcpy(pos, global_argv[i]);
+ pos += strlen(global_argv[i]);
+ if (++i >= global_argc)
+ break;
+ *pos++ = '\0';
+ if (n) debug_printf_expand("expand_vars_to_list 3 finalized list[%d]=%p '%s' "
+ "strlen=%d next=%p pos=%p\n", n-1, list[n-1], list[n-1],
+ strlen(list[n-1]), list[n-1] + strlen(list[n-1]) + 1, pos);
+ list[n++] = pos;
+ }
+ } else { /* quoted $*: add as one word */
+ while (1) {
+ strcpy(pos, global_argv[i]);
+ pos += strlen(global_argv[i]);
+ if (!global_argv[++i])
+ break;
+ if (ifs[0])
+ *pos++ = ifs[0];
+ }
+ }
+ break;
+ default:
+ *p = '\0';
+ arg[0] = first_ch & 0x7f;
+ if (isdigit(arg[0])) {
+ i = xatoi_u(arg);
+ val = NULL;
+ if (i < global_argc)
+ val = global_argv[i];
+ } else
+ val = lookup_param(arg);
+ arg[0] = first_ch;
+ *p = SPECIAL_VAR_SYMBOL;
+ if (!(first_ch & 0x80)) { /* unquoted $VAR */
+ if (val) {
+ n = expand_on_ifs(list, n, &pos, val);
+ val = NULL;
+ }
+ } /* else: quoted $VAR, val will be appended at pos */
+ }
+ if (val) {
+ strcpy(pos, val);
+ pos += strlen(val);
+ }
+ arg = ++p;