From c7985b76c5174c4c224b67e29dd554443f80f47e Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Tue, 17 Jun 2008 05:43:38 +0000 Subject: hush: cleanup pass, the biggest is - moved builtins to the end of the file, they really annoy in the middle of parser code. no real code changes. --- shell/hush.c | 1161 ++++++++++++++++++++++++------------------------ shell/hush_leaktool.sh | 2 +- 2 files changed, 583 insertions(+), 580 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 0b92c29..e64cc47 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -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 ' 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; +} diff --git a/shell/hush_leaktool.sh b/shell/hush_leaktool.sh index 54a19aa..54161b3 100644 --- a/shell/hush_leaktool.sh +++ b/shell/hush_leaktool.sh @@ -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 temp2 mv temp2 temp1 done -- cgit v1.1