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.
No binary (build failed)
—
(log yok)
| @@ -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; |
| + | } |
| @@ -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 | |