hush: make Ctrl-Z work (at least sometimes)

This commit is contained in:
Denis Vlasenko 2007-04-21 23:27:30 +00:00
parent 762d35c75f
commit 1359da6ac7
2 changed files with 227 additions and 174 deletions

View File

@ -1,5 +1,10 @@
Various bits of what is known about busybox shells, in no particular order. Various bits of what is known about busybox shells, in no particular order.
2007-04-21
hush: "sleep 5 | sleep 6" + Ctrl-Z + fg seems to work.
"rm -i" + Ctrl-C, "sleep 5" + Ctrl-Z still doesn't work
for SH_STANDALONE case :(
2007-04-21 2007-04-21
hush: fixed non-backgrounding of "sleep 1 &" and totally broken hush: fixed non-backgrounding of "sleep 1 &" and totally broken
"sleep 1 | sleep 2 &". Noticed a bug where successive jobs "sleep 1 | sleep 2 &". Noticed a bug where successive jobs

View File

@ -82,7 +82,9 @@
#include <getopt.h> /* should be pretty obvious */ #include <getopt.h> /* should be pretty obvious */
/* #include <dmalloc.h> */ /* #include <dmalloc.h> */
/* #define DEBUG_SHELL */ //#define DEBUG_SHELL
/* Finer-grained debug switch */
//#define DEBUG_SHELL_JOBS
#define SPECIAL_VAR_SYMBOL 03 #define SPECIAL_VAR_SYMBOL 03
@ -276,7 +278,7 @@ struct in_str {
struct built_in_command { struct built_in_command {
const char *cmd; /* name */ const char *cmd; /* name */
const char *descr; /* description */ const char *descr; /* description */
int (*function) (struct child_prog *); /* function ptr */ int (*function) (char **argv); /* function ptr */
}; };
/* belongs in busybox.h */ /* belongs in busybox.h */
@ -285,24 +287,24 @@ static int max(int a, int b)
return (a > b) ? a : b; return (a > b) ? a : b;
} }
/* This should be in utility.c */
#ifdef DEBUG_SHELL #ifdef DEBUG_SHELL
static void debug_printf(const char *format, ...) #define debug_printf(...) fprintf(stderr, __VA_ARGS__)
{
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
}
/* broken, of course, but OK for testing */ /* broken, of course, but OK for testing */
static char *indenter(int i) static char *indenter(int i)
{ {
static char blanks[] = " "; static char blanks[] = " ";
return &blanks[sizeof(blanks)-i-1]; return &blanks[sizeof(blanks) - i - 1];
} }
#else #else
#define debug_printf(...) do {} while (0) #define debug_printf(...) do {} while (0)
#endif #endif
#ifdef DEBUG_SHELL_JOBS
#define debug_jobs_printf(...) fprintf(stderr, __VA_ARGS__)
#else
#define debug_jobs_printf(...) do {} while (0)
#endif
#define final_printf debug_printf #define final_printf debug_printf
static void __syntax(const char *file, int line) static void __syntax(const char *file, int line)
@ -314,23 +316,23 @@ static void __syntax(const char *file, int line)
/* Index of subroutines: */ /* Index of subroutines: */
/* function prototypes for builtins */ /* function prototypes for builtins */
static int builtin_cd(struct child_prog *child); static int builtin_cd(char **argv);
static int builtin_env(struct child_prog *child); static int builtin_env(char **argv);
static int builtin_eval(struct child_prog *child); static int builtin_eval(char **argv);
static int builtin_exec(struct child_prog *child); static int builtin_exec(char **argv);
static int builtin_exit(struct child_prog *child); static int builtin_exit(char **argv);
static int builtin_export(struct child_prog *child); static int builtin_export(char **argv);
static int builtin_fg_bg(struct child_prog *child); static int builtin_fg_bg(char **argv);
static int builtin_help(struct child_prog *child); static int builtin_help(char **argv);
static int builtin_jobs(struct child_prog *child); static int builtin_jobs(char **argv);
static int builtin_pwd(struct child_prog *child); static int builtin_pwd(char **argv);
static int builtin_read(struct child_prog *child); static int builtin_read(char **argv);
static int builtin_set(struct child_prog *child); static int builtin_set(char **argv);
static int builtin_shift(struct child_prog *child); static int builtin_shift(char **argv);
static int builtin_source(struct child_prog *child); static int builtin_source(char **argv);
static int builtin_umask(struct child_prog *child); static int builtin_umask(char **argv);
static int builtin_unset(struct child_prog *child); static int builtin_unset(char **argv);
static int builtin_not_written(struct child_prog *child); static int builtin_not_written(char **argv);
/* o_string manipulation: */ /* o_string manipulation: */
static int b_check_space(o_string *o, int len); static int b_check_space(o_string *o, int len);
static int b_addchr(o_string *o, int ch); static int b_addchr(o_string *o, int ch);
@ -354,6 +356,7 @@ static int free_pipe(struct pipe *pi, int indent);
/* really run the final data structures: */ /* really run the final data structures: */
static int setup_redirects(struct child_prog *prog, int squirrel[]); static int setup_redirects(struct child_prog *prog, int squirrel[]);
static int run_list_real(struct pipe *pi); static int run_list_real(struct pipe *pi);
static void pseudo_exec_argv(char **argv) ATTRIBUTE_NORETURN;
static void pseudo_exec(struct child_prog *child) ATTRIBUTE_NORETURN; static void pseudo_exec(struct child_prog *child) ATTRIBUTE_NORETURN;
static int run_pipe_real(struct pipe *pi); static int run_pipe_real(struct pipe *pi);
/* extended glob support: */ /* extended glob support: */
@ -387,6 +390,7 @@ static int checkjobs(struct pipe* fg_pipe);
static int checkjobs_and_fg_shell(struct pipe* fg_pipe); static int checkjobs_and_fg_shell(struct pipe* fg_pipe);
static void insert_bg_job(struct pipe *pi); static void insert_bg_job(struct pipe *pi);
static void remove_bg_job(struct pipe *pi); static void remove_bg_job(struct pipe *pi);
static void delete_finished_bg_job(struct pipe *pi);
/* local variable support */ /* local variable support */
static char **make_list_in(char **inp, char *name); static char **make_list_in(char **inp, char *name);
static char *insert_var_value(char *inp); static char *insert_var_value(char *inp);
@ -503,18 +507,14 @@ static const char *set_cwd(void)
return cwd; return cwd;
} }
// It seems ALL built-ins ever use *only* child->argv in child param.
// Passing argv directly may make 'child->argv += n' modifications
// unneeded on vfork codepaths.
/* built-in 'eval' handler */ /* built-in 'eval' handler */
static int builtin_eval(struct child_prog *child) static int builtin_eval(char **argv)
{ {
char *str = NULL; char *str = NULL;
int rcode = EXIT_SUCCESS; int rcode = EXIT_SUCCESS;
if (child->argv[1]) { if (argv[1]) {
str = make_string(child->argv + 1); str = make_string(argv + 1);
parse_string_outer(str, FLAG_EXIT_FROM_LOOP | parse_string_outer(str, FLAG_EXIT_FROM_LOOP |
FLAG_PARSE_SEMICOLON); FLAG_PARSE_SEMICOLON);
free(str); free(str);
@ -524,13 +524,13 @@ static int builtin_eval(struct child_prog *child)
} }
/* built-in 'cd <path>' handler */ /* built-in 'cd <path>' handler */
static int builtin_cd(struct child_prog *child) static int builtin_cd(char **argv)
{ {
char *newdir; char *newdir;
if (child->argv[1] == NULL) if (argv[1] == NULL)
newdir = getenv("HOME"); newdir = getenv("HOME");
else else
newdir = child->argv[1]; newdir = argv[1];
if (chdir(newdir)) { if (chdir(newdir)) {
printf("cd: %s: %s\n", newdir, strerror(errno)); printf("cd: %s: %s\n", newdir, strerror(errno));
return EXIT_FAILURE; return EXIT_FAILURE;
@ -540,7 +540,7 @@ static int builtin_cd(struct child_prog *child)
} }
/* built-in 'env' handler */ /* built-in 'env' handler */
static int builtin_env(struct child_prog *dummy ATTRIBUTE_UNUSED) static int builtin_env(char **argv ATTRIBUTE_UNUSED)
{ {
/* TODO: call env applet's code instead */ /* TODO: call env applet's code instead */
char **e = environ; char **e = environ;
@ -553,37 +553,36 @@ static int builtin_env(struct child_prog *dummy ATTRIBUTE_UNUSED)
} }
/* built-in 'exec' handler */ /* built-in 'exec' handler */
static int builtin_exec(struct child_prog *child) static int builtin_exec(char **argv)
{ {
if (child->argv[1] == NULL) if (argv[1] == NULL)
return EXIT_SUCCESS; /* Really? */ return EXIT_SUCCESS; /* Really? */
child->argv++; pseudo_exec_argv(argv + 1);
pseudo_exec(child);
/* never returns */ /* never returns */
} }
/* built-in 'exit' handler */ /* built-in 'exit' handler */
static int builtin_exit(struct child_prog *child) static int builtin_exit(char **argv)
{ {
// TODO: bash does it ONLY on top-level sh exit (+interacive only?) // TODO: bash does it ONLY on top-level sh exit (+interacive only?)
//puts("exit"); /* bash does it */ //puts("exit"); /* bash does it */
if (child->argv[1] == NULL) if (argv[1] == NULL)
hush_exit(last_return_code); hush_exit(last_return_code);
/* mimic bash: exit 123abc == exit 255 + error msg */ /* mimic bash: exit 123abc == exit 255 + error msg */
xfunc_error_retval = 255; xfunc_error_retval = 255;
/* bash: exit -2 == exit 254, no error msg */ /* bash: exit -2 == exit 254, no error msg */
hush_exit(xatoi(child->argv[1])); hush_exit(xatoi(argv[1]));
} }
/* built-in 'export VAR=value' handler */ /* built-in 'export VAR=value' handler */
static int builtin_export(struct child_prog *child) static int builtin_export(char **argv)
{ {
int res = 0; int res = 0;
char *name = child->argv[1]; char *name = argv[1];
if (name == NULL) { if (name == NULL) {
return builtin_env(child); return builtin_env(argv);
} }
name = strdup(name); name = strdup(name);
@ -624,7 +623,7 @@ static int builtin_export(struct child_prog *child)
} }
/* built-in 'fg' and 'bg' handler */ /* built-in 'fg' and 'bg' handler */
static int builtin_fg_bg(struct child_prog *child) static int builtin_fg_bg(char **argv)
{ {
int i, jobnum; int i, jobnum;
struct pipe *pi; struct pipe *pi;
@ -632,17 +631,17 @@ static int builtin_fg_bg(struct child_prog *child)
if (!interactive_fd) if (!interactive_fd)
return EXIT_FAILURE; return EXIT_FAILURE;
/* If they gave us no args, assume they want the last backgrounded task */ /* If they gave us no args, assume they want the last backgrounded task */
if (!child->argv[1]) { if (!argv[1]) {
for (pi = job_list; pi; pi = pi->next) { for (pi = job_list; pi; pi = pi->next) {
if (pi->jobid == last_jobid) { if (pi->jobid == last_jobid) {
goto found; goto found;
} }
} }
bb_error_msg("%s: no current job", child->argv[0]); bb_error_msg("%s: no current job", argv[0]);
return EXIT_FAILURE; return EXIT_FAILURE;
} }
if (sscanf(child->argv[1], "%%%d", &jobnum) != 1) { if (sscanf(argv[1], "%%%d", &jobnum) != 1) {
bb_error_msg("%s: bad argument '%s'", child->argv[0], child->argv[1]); bb_error_msg("%s: bad argument '%s'", argv[0], argv[1]);
return EXIT_FAILURE; return EXIT_FAILURE;
} }
for (pi = job_list; pi; pi = pi->next) { for (pi = job_list; pi; pi = pi->next) {
@ -650,37 +649,42 @@ static int builtin_fg_bg(struct child_prog *child)
goto found; goto found;
} }
} }
bb_error_msg("%s: %d: no such job", child->argv[0], jobnum); bb_error_msg("%s: %d: no such job", argv[0], jobnum);
return EXIT_FAILURE; return EXIT_FAILURE;
found: found:
// TODO: bash prints a string representation // TODO: bash prints a string representation
// of job being foregrounded (like "sleep 1 | cat") // of job being foregrounded (like "sleep 1 | cat")
if (*child->argv[0] == 'f') { if (*argv[0] == 'f') {
/* Put the job into the foreground. */ /* Put the job into the foreground. */
tcsetpgrp(interactive_fd, pi->pgrp); tcsetpgrp(interactive_fd, pi->pgrp);
} }
/* Restart the processes in the job */ /* Restart the processes in the job */
for (i = 0; i < pi->num_progs; i++) debug_jobs_printf("reviving %d procs, pgrp %d\n", pi->num_progs, pi->pgrp);
for (i = 0; i < pi->num_progs; i++) {
debug_jobs_printf("reviving pid %d\n", pi->progs[i].pid);
pi->progs[i].is_stopped = 0; pi->progs[i].is_stopped = 0;
}
pi->stopped_progs = 0;
i = kill(- pi->pgrp, SIGCONT); i = kill(- pi->pgrp, SIGCONT);
pi->stopped_progs = 0;
if (i < 0) { if (i < 0) {
if (errno == ESRCH) { if (errno == ESRCH) {
remove_bg_job(pi); delete_finished_bg_job(pi);
} else { } else {
bb_perror_msg("kill (SIGCONT)"); bb_perror_msg("kill (SIGCONT)");
} }
} }
if (*child->argv[0] == 'f') if (*argv[0] == 'f') {
remove_bg_job(pi);
return checkjobs_and_fg_shell(pi); return checkjobs_and_fg_shell(pi);
}
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
/* built-in 'help' handler */ /* built-in 'help' handler */
static int builtin_help(struct child_prog *dummy ATTRIBUTE_UNUSED) static int builtin_help(char **argv ATTRIBUTE_UNUSED)
{ {
const struct built_in_command *x; const struct built_in_command *x;
@ -696,7 +700,7 @@ static int builtin_help(struct child_prog *dummy ATTRIBUTE_UNUSED)
} }
/* built-in 'jobs' handler */ /* built-in 'jobs' handler */
static int builtin_jobs(struct child_prog *child ATTRIBUTE_UNUSED) static int builtin_jobs(char **argv ATTRIBUTE_UNUSED)
{ {
struct pipe *job; struct pipe *job;
const char *status_string; const char *status_string;
@ -713,18 +717,18 @@ static int builtin_jobs(struct child_prog *child ATTRIBUTE_UNUSED)
} }
/* built-in 'pwd' handler */ /* built-in 'pwd' handler */
static int builtin_pwd(struct child_prog *dummy ATTRIBUTE_UNUSED) static int builtin_pwd(char **argv ATTRIBUTE_UNUSED)
{ {
puts(set_cwd()); puts(set_cwd());
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
/* built-in 'read VAR' handler */ /* built-in 'read VAR' handler */
static int builtin_read(struct child_prog *child) static int builtin_read(char **argv)
{ {
int res; int res;
if (child->argv[1]) { if (argv[1]) {
char string[BUFSIZ]; char string[BUFSIZ];
char *var = NULL; char *var = NULL;
@ -732,9 +736,9 @@ static int builtin_read(struct child_prog *child)
/* read string */ /* read string */
fgets(string, sizeof(string), stdin); fgets(string, sizeof(string), stdin);
chomp(string); chomp(string);
var = malloc(strlen(child->argv[1]) + strlen(string) + 2); var = malloc(strlen(argv[1]) + strlen(string) + 2);
if (var) { if (var) {
sprintf(var, "%s=%s", child->argv[1], string); sprintf(var, "%s=%s", argv[1], string);
res = set_local_var(var, 0); res = set_local_var(var, 0);
} else } else
res = -1; res = -1;
@ -748,9 +752,9 @@ static int builtin_read(struct child_prog *child)
} }
/* built-in 'set VAR=value' handler */ /* built-in 'set VAR=value' handler */
static int builtin_set(struct child_prog *child) static int builtin_set(char **argv)
{ {
char *temp = child->argv[1]; char *temp = argv[1];
struct variables *e; struct variables *e;
if (temp == NULL) if (temp == NULL)
@ -764,11 +768,11 @@ static int builtin_set(struct child_prog *child)
/* Built-in 'shift' handler */ /* Built-in 'shift' handler */
static int builtin_shift(struct child_prog *child) static int builtin_shift(char **argv)
{ {
int n = 1; int n = 1;
if (child->argv[1]) { if (argv[1]) {
n = atoi(child->argv[1]); n = atoi(argv[1]);
} }
if (n >= 0 && n < global_argc) { if (n >= 0 && n < global_argc) {
/* XXX This probably breaks $0 */ /* XXX This probably breaks $0 */
@ -780,25 +784,25 @@ static int builtin_shift(struct child_prog *child)
} }
/* Built-in '.' handler (read-in and execute commands from file) */ /* Built-in '.' handler (read-in and execute commands from file) */
static int builtin_source(struct child_prog *child) static int builtin_source(char **argv)
{ {
FILE *input; FILE *input;
int status; int status;
if (child->argv[1] == NULL) if (argv[1] == NULL)
return EXIT_FAILURE; return EXIT_FAILURE;
/* XXX search through $PATH is missing */ /* XXX search through $PATH is missing */
input = fopen(child->argv[1], "r"); input = fopen(argv[1], "r");
if (!input) { if (!input) {
bb_error_msg("cannot open '%s'", child->argv[1]); bb_error_msg("cannot open '%s'", argv[1]);
return EXIT_FAILURE; return EXIT_FAILURE;
} }
/* Now run the file */ /* Now run the file */
/* XXX argv and argc are broken; need to save old global_argv /* XXX argv and argc are broken; need to save old global_argv
* (pointer only is OK!) on this stack frame, * (pointer only is OK!) on this stack frame,
* set global_argv=child->argv+1, recurse, and restore. */ * set global_argv=argv+1, recurse, and restore. */
mark_open(fileno(input)); mark_open(fileno(input));
status = parse_file_outer(input); status = parse_file_outer(input);
mark_closed(fileno(input)); mark_closed(fileno(input));
@ -806,10 +810,10 @@ static int builtin_source(struct child_prog *child)
return status; return status;
} }
static int builtin_umask(struct child_prog *child) static int builtin_umask(char **argv)
{ {
mode_t new_umask; mode_t new_umask;
const char *arg = child->argv[1]; const char *arg = argv[1];
char *end; char *end;
if (arg) { if (arg) {
new_umask = strtoul(arg, &end, 8); new_umask = strtoul(arg, &end, 8);
@ -825,16 +829,16 @@ static int builtin_umask(struct child_prog *child)
} }
/* built-in 'unset VAR' handler */ /* built-in 'unset VAR' handler */
static int builtin_unset(struct child_prog *child) static int builtin_unset(char **argv)
{ {
/* bash returned already true */ /* bash returned already true */
unset_local_var(child->argv[1]); unset_local_var(argv[1]);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
static int builtin_not_written(struct child_prog *child) static int builtin_not_written(char **argv)
{ {
printf("builtin_%s not written\n", child->argv[0]); printf("builtin_%s not written\n", argv[0]);
return EXIT_FAILURE; return EXIT_FAILURE;
} }
@ -1132,31 +1136,26 @@ static void restore_redirects(int squirrel[])
/* XXX no exit() here. If you don't exec, use _exit instead. /* XXX no exit() here. If you don't exec, use _exit instead.
* The at_exit handlers apparently confuse the calling process, * The at_exit handlers apparently confuse the calling process,
* in particular stdin handling. Not sure why? -- because of vfork! (vda) */ * in particular stdin handling. Not sure why? -- because of vfork! (vda) */
static void pseudo_exec(struct child_prog *child) static void pseudo_exec_argv(char **argv)
{ {
int i, rcode; int i, rcode;
char *p; char *p;
const struct built_in_command *x; const struct built_in_command *x;
if (child->argv) { for (i = 0; is_assignment(argv[i]); i++) {
for (i = 0; is_assignment(child->argv[i]); i++) {
debug_printf("pid %d environment modification: %s\n", debug_printf("pid %d environment modification: %s\n",
getpid(), child->argv[i]); getpid(), argv[i]);
// FIXME: vfork case?? // FIXME: vfork case??
p = insert_var_value(child->argv[i]); p = insert_var_value(argv[i]);
putenv(strdup(p)); putenv(strdup(p));
if (p != child->argv[i]) if (p != argv[i])
free(p); free(p);
} }
child->argv += i; /* XXX this hack isn't so horrible, since we are about argv += i;
to exit, and therefore don't need to keep data
structures consistent for free() use. */
// FIXME: ...unless we have _vforked_! Think NOMMU!
/* If a variable is assigned in a forest, and nobody listens, /* If a variable is assigned in a forest, and nobody listens,
* was it ever really set? * was it ever really set?
*/ */
if (child->argv[0] == NULL) { if (argv[0] == NULL) {
_exit(EXIT_SUCCESS); _exit(EXIT_SUCCESS);
} }
@ -1167,9 +1166,9 @@ static void pseudo_exec(struct child_prog *child)
* if this is one of those cases. * if this is one of those cases.
*/ */
for (x = bltins; x->cmd; x++) { for (x = bltins; x->cmd; x++) {
if (strcmp(child->argv[0], x->cmd) == 0) { if (strcmp(argv[0], x->cmd) == 0) {
debug_printf("builtin exec %s\n", child->argv[0]); debug_printf("builtin exec %s\n", argv[0]);
rcode = x->function(child); rcode = x->function(argv);
fflush(stdout); fflush(stdout);
_exit(rcode); _exit(rcode);
} }
@ -1185,15 +1184,23 @@ static void pseudo_exec(struct child_prog *child)
* from global_argv[0], but if we are in a chroot, we may not be able * from global_argv[0], but if we are in a chroot, we may not be able
* to find ourself... */ * to find ourself... */
#if ENABLE_FEATURE_SH_STANDALONE #if ENABLE_FEATURE_SH_STANDALONE
debug_printf("running applet %s\n", child->argv[0]); debug_printf("running applet %s\n", argv[0]);
run_applet_and_exit(child->argv[0], child->argv); run_applet_and_exit(argv[0], argv);
// is it ok that run_applet_and_exit() does exit(), not _exit()? // is it ok that run_applet_and_exit() does exit(), not _exit()?
// NB: IIRC on NOMMU we are after _vfork_, not fork! // NB: IIRC on NOMMU we are after _vfork_, not fork!
#endif #endif
debug_printf("exec of %s\n", child->argv[0]); debug_printf("exec of %s\n", argv[0]);
execvp(child->argv[0], child->argv); execvp(argv[0], argv);
bb_perror_msg("cannot exec '%s'", child->argv[0]); bb_perror_msg("cannot exec '%s'", argv[0]);
_exit(1); _exit(1);
}
static void pseudo_exec(struct child_prog *child)
{
int rcode;
if (child->argv) {
pseudo_exec_argv(child->argv);
} }
if (child->group) { if (child->group) {
@ -1233,13 +1240,17 @@ static void insert_bg_job(struct pipe *pi)
/* physically copy the struct job */ /* physically copy the struct job */
memcpy(thejob, pi, sizeof(struct pipe)); memcpy(thejob, pi, sizeof(struct pipe));
// (pi->num_progs+1) is one-too-many I think?
thejob->progs = xmalloc(sizeof(pi->progs[0]) * (pi->num_progs+1));
memcpy(thejob->progs, pi->progs, sizeof(pi->progs[0]) * (pi->num_progs+1));
thejob->next = NULL; thejob->next = NULL;
thejob->running_progs = thejob->num_progs; /*seems to be wrong:*/
thejob->stopped_progs = 0; /*thejob->running_progs = thejob->num_progs;*/
/*thejob->stopped_progs = 0;*/
thejob->text = xmalloc(BUFSIZ); /* cmdedit buffer size */ thejob->text = xmalloc(BUFSIZ); /* cmdedit buffer size */
//if (pi->progs[0] && pi->progs[0].argv && pi->progs[0].argv[0]) //if (pi->progs[0] && pi->progs[0].argv && pi->progs[0].argv[0])
{ {
// FIXME: overflow check? and also trim the size, BUFSIZ can be 4K!
char *bar = thejob->text; char *bar = thejob->text;
char **foo = pi->progs[0].argv; char **foo = pi->progs[0].argv;
if (foo) if (foo)
@ -1254,7 +1265,6 @@ static void insert_bg_job(struct pipe *pi)
last_jobid = thejob->jobid; last_jobid = thejob->jobid;
} }
/* remove a backgrounded job */
static void remove_bg_job(struct pipe *pi) static void remove_bg_job(struct pipe *pi)
{ {
struct pipe *prev_pipe; struct pipe *prev_pipe;
@ -1271,7 +1281,12 @@ static void remove_bg_job(struct pipe *pi)
last_jobid = job_list->jobid; last_jobid = job_list->jobid;
else else
last_jobid = 0; last_jobid = 0;
}
/* remove a backgrounded job */
static void delete_finished_bg_job(struct pipe *pi)
{
remove_bg_job(pi);
pi->stopped_progs = 0; pi->stopped_progs = 0;
free_pipe(pi, 0); free_pipe(pi, 0);
free(pi); free(pi);
@ -1289,36 +1304,67 @@ static int checkjobs(struct pipe* fg_pipe)
int rcode = 0; int rcode = 0;
attributes = WUNTRACED; attributes = WUNTRACED;
//WUNTRACED?? huh, what will happed on Ctrl-Z? fg waiting code
//doesn't seem to be ready for stopped children! (only exiting ones)...
if (fg_pipe == NULL) { if (fg_pipe == NULL) {
attributes |= WNOHANG; attributes |= WNOHANG;
} }
/* Do we do this right?
* bash-3.00# sleep 20 | false
* <Ctrl-Z pressed>
* [3]+ Stopped sleep 20 | false
* bash-3.00# echo $?
* 1 <========== bg pipe is not fully done, but exitcode is already known!
*/
wait_more: wait_more:
while ((childpid = waitpid(-1, &status, attributes)) > 0) { while ((childpid = waitpid(-1, &status, attributes)) > 0) {
const int dead = WIFEXITED(status) || WIFSIGNALED(status);
#ifdef DEBUG_SHELL_JOBS
if (WIFSTOPPED(status))
debug_jobs_printf("pid %d stopped by sig %d (exitcode %d)\n",
childpid, WSTOPSIG(status), WEXITSTATUS(status));
if (WIFSIGNALED(status))
debug_jobs_printf("pid %d killed by sig %d (exitcode %d)\n",
childpid, WTERMSIG(status), WEXITSTATUS(status));
if (WIFEXITED(status))
debug_jobs_printf("pid %d exited, exitcode %d\n",
childpid, WEXITSTATUS(status));
#endif
/* Were we asked to wait for fg pipe? */ /* Were we asked to wait for fg pipe? */
if (fg_pipe) { if (fg_pipe) {
int i; int i;
for (i = 0; i < fg_pipe->num_progs; i++) { for (i = 0; i < fg_pipe->num_progs; i++) {
debug_jobs_printf("check pid %d\n", fg_pipe->progs[i].pid);
if (fg_pipe->progs[i].pid == childpid) { if (fg_pipe->progs[i].pid == childpid) {
/* printf("process %d exit %d\n", i, WEXITSTATUS(status)); */ /* printf("process %d exit %d\n", i, WEXITSTATUS(status)); */
if (dead) {
fg_pipe->progs[i].pid = 0; fg_pipe->progs[i].pid = 0;
fg_pipe->running_progs--;
if (i == fg_pipe->num_progs-1) if (i == fg_pipe->num_progs-1)
/* last process gives overall exitstatus */ /* last process gives overall exitstatus */
rcode = WEXITSTATUS(status); rcode = WEXITSTATUS(status);
if (--fg_pipe->running_progs <= 0) } else {
/* All processes in fg pipe have exited */ fg_pipe->progs[i].is_stopped = 1;
fg_pipe->stopped_progs++;
}
debug_jobs_printf("fg_pipe: running_progs %d stopped_progs %d\n",
fg_pipe->running_progs, fg_pipe->stopped_progs);
if (fg_pipe->running_progs - fg_pipe->stopped_progs <= 0) {
/* All processes in fg pipe have exited/stopped */
if (fg_pipe->running_progs)
insert_bg_job(fg_pipe);
return rcode; return rcode;
}
/* There are still running processes in the fg pipe */ /* There are still running processes in the fg pipe */
goto wait_more; goto wait_more;
} }
} }
/* fall through to searching process in bg pipes */
} }
/* We asked to wait for bg or orphaned children */ /* We asked to wait for bg or orphaned children */
/* No need to remember exitcode in this case */ /* No need to remember exitcode in this case */
for (pi = job_list; pi; pi = pi->next) { for (pi = job_list; pi; pi = pi->next) {
prognum = 0; prognum = 0;
while (prognum < pi->num_progs) { while (prognum < pi->num_progs) {
@ -1330,18 +1376,17 @@ static int checkjobs(struct pipe* fg_pipe)
/* Happens when shell is used as init process (init=/bin/sh) */ /* Happens when shell is used as init process (init=/bin/sh) */
debug_printf("checkjobs: pid %d was not in our list!\n", childpid); debug_printf("checkjobs: pid %d was not in our list!\n", childpid);
continue; goto wait_more;
found_pi_and_prognum: found_pi_and_prognum:
if (WIFEXITED(status) || WIFSIGNALED(status)) { if (dead) {
/* child exited */ /* child exited */
pi->running_progs--;
pi->progs[prognum].pid = 0; pi->progs[prognum].pid = 0;
pi->running_progs--;
if (!pi->running_progs) { if (!pi->running_progs) {
printf(JOB_STATUS_FORMAT, pi->jobid, printf(JOB_STATUS_FORMAT, pi->jobid,
"Done", pi->text); "Done", pi->text);
remove_bg_job(pi); delete_finished_bg_job(pi);
} }
} else { } else {
/* child stopped */ /* child stopped */
@ -1350,7 +1395,9 @@ static int checkjobs(struct pipe* fg_pipe)
} }
} }
if (childpid == -1 && errno != ECHILD) /* wait found no children or failed */
if (childpid && errno != ECHILD)
bb_perror_msg("waitpid"); bb_perror_msg("waitpid");
/* move the shell to the foreground */ /* move the shell to the foreground */
@ -1402,6 +1449,8 @@ static int run_pipe_real(struct pipe *pi)
nextin = 0; nextin = 0;
pi->pgrp = -1; pi->pgrp = -1;
pi->running_progs = 0;
pi->stopped_progs = 0;
/* Check if this is a simple builtin (not part of a pipe). /* Check if this is a simple builtin (not part of a pipe).
* Builtins within pipes have to fork anyway, and are handled in * Builtins within pipes have to fork anyway, and are handled in
@ -1418,12 +1467,14 @@ static int run_pipe_real(struct pipe *pi)
return rcode; return rcode;
} }
if (single_fg && pi->progs[0].argv != NULL) { if (single_fg && child->argv != NULL) {
for (i = 0; is_assignment(child->argv[i]); i++) char **argv = child->argv;
for (i = 0; is_assignment(argv[i]); i++)
continue; continue;
if (i != 0 && child->argv[i] == NULL) { if (i != 0 && argv[i] == NULL) {
/* assignments, but no command: set the local environment */ /* assignments, but no command: set the local environment */
for (i = 0; child->argv[i] != NULL; i++) { for (i = 0; argv[i] != NULL; i++) {
/* Ok, this case is tricky. We have to decide if this is a /* Ok, this case is tricky. We have to decide if this is a
* local variable, or an already exported variable. If it is * local variable, or an already exported variable. If it is
* already exported, we have to export the new value. If it is * already exported, we have to export the new value. If it is
@ -1432,7 +1483,7 @@ static int run_pipe_real(struct pipe *pi)
* variable. */ * variable. */
int export_me = 0; int export_me = 0;
char *name, *value; char *name, *value;
name = xstrdup(child->argv[i]); name = xstrdup(argv[i]);
debug_printf("Local environment set: %s\n", name); debug_printf("Local environment set: %s\n", name);
value = strchr(name, '='); value = strchr(name, '=');
if (value) if (value)
@ -1441,17 +1492,17 @@ static int run_pipe_real(struct pipe *pi)
export_me = 1; export_me = 1;
} }
free(name); free(name);
p = insert_var_value(child->argv[i]); p = insert_var_value(argv[i]);
set_local_var(p, export_me); set_local_var(p, export_me);
if (p != child->argv[i]) if (p != argv[i])
free(p); free(p);
} }
return EXIT_SUCCESS; /* don't worry about errors in set_local_var() yet */ return EXIT_SUCCESS; /* don't worry about errors in set_local_var() yet */
} }
for (i = 0; is_assignment(child->argv[i]); i++) { for (i = 0; is_assignment(argv[i]); i++) {
p = insert_var_value(child->argv[i]); p = insert_var_value(argv[i]);
putenv(strdup(p)); putenv(strdup(p));
if (p != child->argv[i]) { if (p != argv[i]) {
child->sp--; child->sp--;
free(p); free(p);
} }
@ -1459,27 +1510,25 @@ static int run_pipe_real(struct pipe *pi)
if (child->sp) { if (child->sp) {
char *str; char *str;
str = make_string(child->argv + i); str = make_string(argv + i);
parse_string_outer(str, FLAG_EXIT_FROM_LOOP | FLAG_REPARSING); parse_string_outer(str, FLAG_EXIT_FROM_LOOP | FLAG_REPARSING);
free(str); free(str);
return last_return_code; return last_return_code;
} }
for (x = bltins; x->cmd; x++) { for (x = bltins; x->cmd; x++) {
if (strcmp(child->argv[i], x->cmd) == 0) { if (strcmp(argv[i], x->cmd) == 0) {
if (x->function == builtin_exec && child->argv[i+1] == NULL) { if (x->function == builtin_exec && argv[i+1] == NULL) {
debug_printf("magic exec\n"); debug_printf("magic exec\n");
setup_redirects(child, NULL); setup_redirects(child, NULL);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
debug_printf("builtin inline %s\n", child->argv[0]); debug_printf("builtin inline %s\n", argv[0]);
/* XXX setup_redirects acts on file descriptors, not FILEs. /* XXX setup_redirects acts on file descriptors, not FILEs.
* This is perfect for work that comes after exec(). * This is perfect for work that comes after exec().
* Is it really safe for inline use? Experimentally, * Is it really safe for inline use? Experimentally,
* things seem to work with glibc. */ * things seem to work with glibc. */
setup_redirects(child, squirrel); setup_redirects(child, squirrel);
child->argv += i; /* XXX horrible hack */ rcode = x->function(argv + i);
rcode = x->function(child);
child->argv -= i; /* XXX restore hack so free() can work right */
restore_redirects(squirrel); restore_redirects(squirrel);
return rcode; return rcode;
} }
@ -1488,10 +1537,10 @@ static int run_pipe_real(struct pipe *pi)
{ {
// FIXME: applet runs like part of shell - for example, it ignores // FIXME: applet runs like part of shell - for example, it ignores
// SIGINT! Try to Ctrl-C out of "rm -i"... doesn't work // SIGINT! Try to Ctrl-C out of "rm -i"... doesn't work
const struct bb_applet *a = find_applet_by_name(child->argv[i]); const struct bb_applet *a = find_applet_by_name(argv[i]);
if (a && a->nofork) { if (a && a->nofork) {
setup_redirects(child, squirrel); setup_redirects(child, squirrel);
rcode = run_nofork_applet(a, child->argv + i); rcode = run_nofork_applet(a, argv + i);
restore_redirects(squirrel); restore_redirects(squirrel);
return rcode; return rcode;
} }
@ -1503,7 +1552,6 @@ static int run_pipe_real(struct pipe *pi)
* for initial child code after fork */ * for initial child code after fork */
set_jobctrl_sighandler(SIG_IGN); set_jobctrl_sighandler(SIG_IGN);
pi->running_progs = 0;
for (i = 0; i < pi->num_progs; i++) { for (i = 0; i < pi->num_progs; i++) {
child = &(pi->progs[i]); child = &(pi->progs[i]);