* fancy forms of Parameter Expansion
* Arithmetic Expansion
* <(list) and >(list) Process Substitution
- * reserved words: if, then, elif, else, fi, while, until, for,
- * do, done, case
+ * reserved words: case, esac, function
* Here Documents ( << word )
* Functions
* Major bugs:
* job handling woefully incomplete and buggy
* reserved word execution woefully incomplete and buggy
- * incomplete reserved word sequence doesn't request more lines of input
* to-do:
* port selected bugfixes from post-0.49 busybox lash
* finish implementing reserved words
* more testing, especially quoting rules and redirection
* maybe change map[] to use 2-bit entries
* (eventually) remove all the printf's
- * more integration with BusyBox: prompts, cmdedit, applets
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
int job_context; /* bitmask defining current context */
pipe_style followup; /* PIPE_BG, PIPE_SEQ, PIPE_OR, PIPE_AND */
reserved_style r_mode; /* supports if, for, while, until */
- struct jobset *job_list;
};
struct jobset {
static int interactive=0;
static struct close_me *close_me_head = NULL;
static char *cwd;
-/* static struct jobset job_list = { NULL, NULL }; */
+static struct jobset *job_list;
static unsigned int last_bg_pid=0;
static char *PS1;
static char *PS2 = "> ";
static int parse_stream_outer(struct in_str *inp);
static int parse_string_outer(const char *s);
static int parse_file_outer(FILE *f);
+/* job management: */
+static void checkjobs();
+static void insert_bg_job(struct pipe *pi);
+static void remove_bg_job(struct pipe *pi);
+static void free_pipe(struct pipe *pi);
/* Table of built-in functions. They can be forked or not, depending on
* context: within pipes, they fork. As simple commands, they do not.
static int builtin_exit(struct child_prog *child)
{
if (child->argv[1] == NULL)
- exit(EXIT_SUCCESS);
+ exit(last_return_code);
exit (atoi(child->argv[1]));
}
/* built-in 'fg' and 'bg' handler */
static int builtin_fg_bg(struct child_prog *child)
{
- int i, jobNum;
- struct pipe *job=NULL;
-
- if (!child->argv[1] || child->argv[2]) {
- error_msg("%s: exactly one argument is expected\n",
- child->argv[0]);
- return EXIT_FAILURE;
- }
-
- if (sscanf(child->argv[1], "%%%d", &jobNum) != 1) {
- error_msg("%s: bad argument '%s'\n",
- child->argv[0], child->argv[1]);
- return EXIT_FAILURE;
- }
+ int i, jobnum;
+ struct pipe *pi=NULL;
- for (job = child->family->job_list->head; job; job = job->next) {
- if (job->jobid == jobNum) {
- break;
+ /* If they gave us no args, assume they want the last backgrounded task */
+ if (!child->argv[1]) {
+ for (pi = job_list->head; pi; pi = pi->next) {
+ if (pi->progs && pi->progs->pid == last_bg_pid) {
+ break;
+ }
+ }
+ if (!pi) {
+ error_msg("%s: no current job", child->argv[0]);
+ return EXIT_FAILURE;
+ }
+ } else {
+ if (sscanf(child->argv[1], "%%%d", &jobnum) != 1) {
+ error_msg("%s: bad argument '%s'", child->argv[0], child->argv[1]);
+ return EXIT_FAILURE;
}
- }
- if (!job) {
- error_msg("%s: unknown job %d\n",
- child->argv[0], jobNum);
- return EXIT_FAILURE;
+ for (pi = job_list->head; pi; pi = pi->next) {
+ if (pi->jobid == jobnum) {
+ break;
+ }
+ }
+ if (!pi) {
+ error_msg("%s: %d: no such job", child->argv[0], jobnum);
+ return EXIT_FAILURE;
+ }
}
-
if (*child->argv[0] == 'f') {
/* Make this job the foreground job */
+ signal(SIGTTOU, SIG_IGN);
/* suppress messages when run from /linuxrc mag@sysgo.de */
- if (tcsetpgrp(0, job->pgrp) && errno != ENOTTY)
+ if (tcsetpgrp(0, pi->pgrp) && errno != ENOTTY)
perror_msg("tcsetpgrp");
- child->family->job_list->fg = job;
+ signal(SIGTTOU, SIG_DFL);
+ job_list->fg = pi;
}
/* Restart the processes in the job */
- for (i = 0; i < job->num_progs; i++)
- job->progs[i].is_stopped = 0;
+ for (i = 0; i < pi->num_progs; i++)
+ pi->progs[i].is_stopped = 0;
- kill(-job->pgrp, SIGCONT);
+ kill(-pi->pgrp, SIGCONT);
- job->stopped_progs = 0;
+ pi->stopped_progs = 0;
return EXIT_SUCCESS;
}
struct pipe *job;
char *status_string;
- for (job = child->family->job_list->head; job; job = job->next) {
+ for (job = job_list->head; job; job = job->next) {
if (job->running_progs == job->stopped_progs)
status_string = "Stopped";
else
static void get_user_input(struct in_str *i)
{
char *prompt_str;
- static char the_command[MAX_LINE];
+ static char the_command[BUFSIZ];
setup_prompt_string(i->promptmode, &prompt_str);
#ifdef BB_FEATURE_COMMAND_EDITING
if (i->__promptme && interactive && i->file == stdin) {
get_user_input(i);
i->promptmode=2;
+ i->__promptme = 0;
+ if (i->p && *i->p) {
+ ch=*i->p++;
+ }
+ } else {
+ ch = fgetc(i->file);
}
- i->__promptme = 0;
- if (i->p && *i->p) {
- ch=*i->p++;
- }
debug_printf("b_getch: got a %d\n", ch);
}
if (ch == '\n') i->__promptme=1;
if (i->p && *i->p) {
return *i->p;
} else {
- static char buffer;
- buffer = fgetc(i->file);
- i->p = &buffer;
+ static char buffer[2];
+ buffer[0] = fgetc(i->file);
+ buffer[1] = '\0';
+ i->p = buffer;
debug_printf("b_peek: got a %d\n", *i->p);
return *i->p;
}
exit(x->function(child));
}
}
+
+ /* Check if the command matches any busybox internal commands
+ * ("applets") here.
+ * FIXME: This feature is not 100% safe, since
+ * BusyBox is not fully reentrant, so we have no guarantee the things
+ * from the .bss are still zeroed, or that things from .data are still
+ * at their defaults. We could exec ourself from /proc/self/exe, but I
+ * really dislike relying on /proc for things. We could exec ourself
+ * from global_argv[0], but if we are in a chroot, we may not be able
+ * to find ourself... */
+#ifdef BB_FEATURE_SH_STANDALONE_SHELL
+ {
+ int argc_l;
+ char** argv_l=child->argv;
+ char *name = child->argv[0];
+
+#ifdef BB_FEATURE_SH_APPLETS_ALWAYS_WIN
+ /* Following discussions from November 2000 on the busybox mailing
+ * list, the default configuration, (without
+ * get_last_path_component()) lets the user force use of an
+ * external command by specifying the full (with slashes) filename.
+ * If you enable BB_FEATURE_SH_APPLETS_ALWAYS_WIN, then applets
+ * _aways_ override external commands, so if you want to run
+ * /bin/cat, it will use BusyBox cat even if /bin/cat exists on the
+ * filesystem and is _not_ busybox. Some systems may want this,
+ * most do not. */
+ name = get_last_path_component(name);
+#endif
+ /* Count argc for use in a second... */
+ for(argc_l=0;*argv_l!=NULL; argv_l++, argc_l++);
+ optind = 1;
+ debug_printf("running applet %s\n", name);
+ run_applet_by_name(name, argc_l, child->argv);
+ }
+#endif
debug_printf("exec of %s\n",child->argv[0]);
execvp(child->argv[0],child->argv);
perror("execvp");
}
}
+static void insert_bg_job(struct pipe *pi)
+{
+ struct pipe *thejob;
+
+ /* Linear search for the ID of the job to use */
+ pi->jobid = 1;
+ for (thejob = job_list->head; thejob; thejob = thejob->next)
+ if (thejob->jobid >= pi->jobid)
+ pi->jobid = thejob->jobid + 1;
+
+ /* add thejob to the list of running jobs */
+ if (!job_list->head) {
+ thejob = job_list->head = xmalloc(sizeof(*thejob));
+ } else {
+ for (thejob = job_list->head; thejob->next; thejob = thejob->next) /* nothing */;
+ thejob->next = xmalloc(sizeof(*thejob));
+ thejob = thejob->next;
+ }
+
+ /* physically copy the struct job */
+ memcpy(thejob, pi, sizeof(struct pipe));
+ thejob->next = NULL;
+ thejob->running_progs = thejob->num_progs;
+ thejob->stopped_progs = 0;
+
+ /* we don't wait for background thejobs to return -- append it
+ to the list of backgrounded thejobs and leave it alone */
+ printf("[%d] %d\n", pi->jobid, pi->pgrp);
+ last_bg_pid = pi->pgrp;
+}
+
+/* remove a backgrounded job from a jobset */
+static void remove_bg_job(struct pipe *pi)
+{
+ struct pipe *prev_pipe;
+
+ free_pipe(pi);
+ if (pi == job_list->head) {
+ job_list->head = pi->next;
+ } else {
+ prev_pipe = job_list->head;
+ while (prev_pipe->next != pi)
+ prev_pipe = prev_pipe->next;
+ prev_pipe->next = pi->next;
+ }
+
+ free(pi);
+}
+
+/* free up all memory from a pipe */
+static void free_pipe(struct pipe *pi)
+{
+ int i;
+
+ for (i = 0; i < pi->num_progs; i++) {
+ free(pi->progs[i].argv);
+ if (pi->progs[i].redirects)
+ free(pi->progs[i].redirects);
+ }
+ if (pi->progs)
+ free(pi->progs);
+ if (pi->text)
+ free(pi->text);
+ if (pi->cmdbuf)
+ free(pi->cmdbuf);
+ memset(pi, 0, sizeof(struct pipe));
+}
+
+
+/* Checks to see if any background processes have exited -- if they
+ have, figure out why and see if a job has completed */
+static void checkjobs()
+{
+ int status;
+ int prognum = 0;
+ struct pipe *pi;
+ pid_t childpid;
+
+ while ((childpid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
+ for (pi = job_list->head; pi; pi = pi->next) {
+ prognum = 0;
+ while (prognum < pi->num_progs &&
+ pi->progs[prognum].pid != childpid) prognum++;
+ if (prognum < pi->num_progs)
+ break;
+ }
+
+ if (WIFEXITED(status) || WIFSIGNALED(status)) {
+ /* child exited */
+ pi->running_progs--;
+ pi->progs[prognum].pid = 0;
+
+ if (!pi->running_progs) {
+ printf(JOB_STATUS_FORMAT, pi->jobid, "Done", pi->text);
+ remove_bg_job(pi);
+ }
+ } else {
+ /* child stopped */
+ pi->stopped_progs++;
+ pi->progs[prognum].is_stopped = 1;
+
+ if (pi->stopped_progs == pi->num_progs) {
+ printf(JOB_STATUS_FORMAT, pi->jobid, "Stopped",
+ pi->text);
+ }
+ }
+ }
+
+ if (childpid == -1 && errno != ECHILD)
+ perror_msg("waitpid");
+
+ /* move the shell to the foreground */
+ if (tcsetpgrp(0, getpgrp()) && errno != ENOTTY)
+ perror_msg("tcsetpgrp");
+}
+
/* run_pipe_real() starts all the jobs, but doesn't wait for anything
* to finish. See pipe_wait().
*
static int run_pipe_real(struct pipe *pi)
{
int i;
+ int ctty;
int nextin, nextout;
int pipefds[2]; /* pipefds[0] is for reading */
struct child_prog *child;
struct built_in_command *x;
+ ctty = -1;
nextin = 0;
pi->pgrp = 0;
+ /* Check if we are supposed to run in the foreground */
+ if (interactive && pi->followup!=PIPE_BG) {
+ if ((pi->pgrp = tcgetpgrp(ctty = 2)) < 0
+ && (pi->pgrp = tcgetpgrp(ctty = 0)) < 0
+ && (pi->pgrp = tcgetpgrp(ctty = 1)) < 0)
+ return errno = ENOTTY, -1;
+
+ if (pi->pgrp < 0 && pi->pgrp != getpgrp())
+ return errno = EPERM, -1;
+ }
+
/* Check if this is a simple builtin (not part of a pipe).
* Builtins within pipes have to fork anyway, and are handled in
* pseudo_exec. "echo foo | read bar" doesn't work on bash, either.
/* XXX test for failed fork()? */
if (!(child->pid = fork())) {
+
+ signal(SIGTTOU, SIG_DFL);
+
close_all();
if (nextin != 0) {
/* Like bash, explicit redirects override pipes,
* and the pipe fd is available for dup'ing. */
setup_redirects(child,NULL);
+
+ if (pi->followup!=PIPE_BG) {
+ /* Put our child in the process group whose leader is the
+ * first process in this pipe. */
+ if (pi->pgrp < 0) {
+ pi->pgrp = child->pid;
+ }
+ /* Don't check for errors. The child may be dead already,
+ * in which case setpgid returns error code EACCES. */
+ if (setpgid(0, pi->pgrp) == 0) {
+ signal(SIGTTOU, SIG_IGN);
+ tcsetpgrp(ctty, pi->pgrp);
+ signal(SIGTTOU, SIG_DFL);
+ }
+ }
pseudo_exec(child);
}
- if (interactive) {
- /* Put our child in the process group whose leader is the
- * first process in this pipe. */
- if (pi->pgrp==0) {
- pi->pgrp = child->pid;
- }
- /* Don't check for errors. The child may be dead already,
- * in which case setpgid returns error code EACCES. */
- setpgid(child->pid, pi->pgrp);
+ /* Put our child in the process group whose leader is the
+ * first process in this pipe. */
+ if (pi->pgrp < 0) {
+ pi->pgrp = child->pid;
}
- /* In the non-interactive case, do nothing. Leave the children
- * with the process group that they inherited from us. */
-
+ /* Don't check for errors. The child may be dead already,
+ * in which case setpgid returns error code EACCES. */
+ setpgid(child->pid, pi->pgrp);
+
if (nextin != 0)
close(nextin);
if (nextout != 1)
{
int rcode=0;
int if_code=0, next_if_code=0; /* need double-buffer to handle elif */
- reserved_style rmode=RES_NONE;
+ reserved_style rmode, skip_more_in_this_rmode=RES_XXXX;
for (;pi;pi=pi->next) {
rmode = pi->r_mode;
- debug_printf("rmode=%d if_code=%d next_if_code=%d\n", rmode, if_code, next_if_code);
+ debug_printf("rmode=%d if_code=%d next_if_code=%d skip_more=%d\n", rmode, if_code, next_if_code, skip_more_in_this_rmode);
+ if (rmode == skip_more_in_this_rmode) continue;
+ skip_more_in_this_rmode = RES_XXXX;
if (rmode == RES_THEN || rmode == RES_ELSE) if_code = next_if_code;
if (rmode == RES_THEN && if_code) continue;
if (rmode == RES_ELSE && !if_code) continue;
if (rmode == RES_ELIF && !if_code) continue;
- if (pi->num_progs == 0) break;
+ if (pi->num_progs == 0) continue;
rcode = run_pipe_real(pi);
if (rcode!=-1) {
/* We only ran a builtin: rcode was set by the return value
/* XXX check bash's behavior with nontrivial pipes */
/* XXX compute jobid */
/* XXX what does bash do with attempts to background builtins? */
- printf("[%d] %d\n", pi->jobid, pi->pgrp);
- last_bg_pid = pi->pgrp;
+ insert_bg_job(pi);
rcode = EXIT_SUCCESS;
} else {
+
if (interactive) {
/* move the new process group into the foreground */
/* suppress messages when run from /linuxrc mag@sysgo.de */
- signal(SIGTTIN, SIG_IGN);
- signal(SIGTTOU, SIG_IGN);
+ //signal(SIGTTIN, SIG_IGN);
+ //signal(SIGTTOU, SIG_IGN);
if (tcsetpgrp(0, pi->pgrp) && errno != ENOTTY)
perror_msg("tcsetpgrp");
rcode = pipe_wait(pi);
- if (tcsetpgrp(0, getpid()) && errno != ENOTTY)
+ if (tcsetpgrp(0, getpgrp()) && errno != ENOTTY)
perror_msg("tcsetpgrp");
- signal(SIGTTIN, SIG_DFL);
- signal(SIGTTOU, SIG_DFL);
+ //signal(SIGTTIN, SIG_DFL);
+ //signal(SIGTTOU, SIG_DFL);
} else {
rcode = pipe_wait(pi);
}
next_if_code=rcode; /* can be overwritten a number of times */
if ( (rcode==EXIT_SUCCESS && pi->followup==PIPE_OR) ||
(rcode!=EXIT_SUCCESS && pi->followup==PIPE_AND) )
- return rcode; /* XXX broken if list is part of if/then/else */
+ skip_more_in_this_rmode=rmode;
+ /* return rcode; */ /* XXX broken if list is part of if/then/else */
}
+ checkjobs();
return rcode;
}
old->child->group = ctx->list_head;
*ctx = *old; /* physical copy */
free(old);
- ctx->w=RES_NONE;
}
b_reset (dest);
return 1;
lookup_param(dest, ctx, &alt);
break;
case '(':
+ b_getch(input);
process_command_subs(dest, ctx, input, ')');
break;
case '*':
ch,ch,m,dest->quote);
if (m==0 || ((m==1 || m==2) && dest->quote)) {
b_addqchr(dest, ch, dest->quote);
- } else if (ch == end_trigger && !dest->quote && ctx->w==RES_NONE) {
- debug_printf("leaving parse_stream\n");
- return 0;
- } else if (m==2 && !dest->quote) { /* IFS */
- done_word(dest, ctx);
- if (ch=='\n') done_pipe(ctx,PIPE_SEQ);
+ } else {
+ if (m==2) { /* unquoted IFS */
+ done_word(dest, ctx);
+ /* If we aren't performing a substitution, treat a newline as a
+ * command separator. */
+ if (end_trigger != '\0' && ch=='\n')
+ done_pipe(ctx,PIPE_SEQ);
+ }
if (ch == end_trigger && !dest->quote && ctx->w==RES_NONE) {
- debug_printf("leaving parse_stream (stupid duplication)\n");
+ debug_printf("leaving parse_stream\n");
return 0;
}
#if 0
initialize_context(ctx);
}
#endif
- } else switch (ch) {
+ if (m!=2) switch (ch) {
case '#':
if (dest->length == 0 && !dest->quote) {
while(ch=b_peek(input),ch!=EOF && ch!='\n') { b_getch(input); }
default:
syntax(); /* this is really an internal logic error */
return 1;
+ }
}
}
/* complain if quote? No, maybe we just finished a command substitution
{
int opt;
FILE *input;
+ struct jobset joblist_end = { NULL, NULL };
+ job_list = &joblist_end;
+
+ last_return_code=EXIT_SUCCESS;
/* XXX what should these be while sourcing /etc/profile? */
global_argc = argc;
global_argv = argv;
+ /* don't pay any attention to this signal; it just confuses
+ things and isn't really meant for shells anyway */
+ signal(SIGTTOU, SIG_IGN);
+
if (argv[0] && argv[0][0] == '-') {
debug_printf("\nsourcing /etc/profile\n");
input = xfopen("/etc/profile", "r");
global_argv = argv+optind;
global_argc = argc-optind;
opt = parse_string_outer(optarg);
- exit(opt);
+ goto final_return;
}
break;
case 'i':
isatty(fileno(stdin)) && isatty(fileno(stdout))) {
interactive++;
}
-
+
+ debug_printf("\ninteractive=%d\n", interactive);
if (interactive) {
/* Looks like they want an interactive shell */
fprintf(stdout, "\nhush -- the humble shell v0.01 (testing)\n\n");
- exit(parse_file_outer(stdin));
+ opt=parse_file_outer(stdin);
+ goto final_return;
}
- debug_printf("\ninteractive=%d\n", interactive);
debug_printf("\nrunning script '%s'\n", argv[optind]);
global_argv = argv+optind;
fclose(input.file);
#endif
- return(opt);
+final_return:
+ return(opt?opt:last_return_code);
}