PIPE_BG = 4,
} pipe_style;
-/* might eventually control execution */
typedef enum {
RES_NONE = 0,
#if ENABLE_HUSH_IF
RES_XXXX = 12,
RES_SNTX = 13
} reserved_style;
+
/* This holds pointers to the various results of parsing */
struct p_context {
struct child_prog *child;
* 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 {
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? */
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;
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)
#endif
};
+
/* Signals are grouped, we handle them in batches */
static void set_misc_sighandler(void (*handler)(int))
{
}
-/* 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);
#endif
i->p = user_input_buf;
}
+
#endif /* INTERACTIVE */
/* This is the magic location that prints prompts
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[])
}
}
+
/* 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.
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
} 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) {
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)
return (char*)list;
}
-/* used for eval */
+/* Used for "eval" builtin */
static char* expand_strvec_to_string(char **argv)
{
char **list;
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;
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;
#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;
}
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);
}
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)
{
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 */
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') {
}
#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 $ */
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)
{
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)
{
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: */
}
#endif
+
int hush_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int hush_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;
+}