From b81b3df1fa387fa5c6a3fe577969285858f494ce Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Sat, 28 Apr 2007 16:48:04 +0000 Subject: [PATCH] hush: make job control and interactiveness configurable, part 1 --- shell/Config.in | 9 +++++ shell/hush.c | 116 +++++++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 111 insertions(+), 14 deletions(-) diff --git a/shell/Config.in b/shell/Config.in index 9479e3f..81289e4 100644 --- a/shell/Config.in +++ b/shell/Config.in @@ -176,6 +176,15 @@ config HUSH word ), arithmetic expansion, aliases, brace expansion, tilde expansion, &> and >& redirection of stdout+stderr, etc. +config HUSH_INTERACTIVE + bool "Interactive mode" + default y + depends on HUSH + help + Enable interactive mode (Ctrl-Z, Ctrl-C, command editing) + in the hush shell. Without this, hush reads and executes + stdin just like a shell script from the file. + config LASH bool "lash" diff --git a/shell/hush.c b/shell/hush.c index 25eb78f..57a4ac0 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -51,7 +51,7 @@ * Here Documents ( << word ) * Functions * Major bugs: - * job handling woefully incomplete and buggy + * job handling woefully incomplete and buggy (improved --vda) * reserved word execution woefully incomplete and buggy * to-do: * port selected bugfixes from post-0.49 busybox lash - done? @@ -86,6 +86,16 @@ /* Finer-grained debug switch */ //#define DEBUG_SHELL_JOBS +//TODO: rename HUSH_INTERACTIVE -> HUSH_JOB, +//create HUSH_INTERACTIVE which controls only prompt + line editing, +//make HUSH_JOB dependent on it + +#if !ENABLE_HUSH_INTERACTIVE +#undef ENABLE_FEATURE_EDITING +#define ENABLE_FEATURE_EDITING 0 +#undef ENABLE_FEATURE_EDITING_FANCY_PROMPT +#define ENABLE_FEATURE_EDITING_FANCY_PROMPT 0 +#endif #define SPECIAL_VAR_SYMBOL 03 #define FLAG_EXIT_FROM_LOOP 1 @@ -169,10 +179,10 @@ struct p_context { }; struct redir_struct { + struct redir_struct *next; /* pointer to the next redirect in the list */ redir_type type; /* type of redirection */ int fd; /* file descriptor being redirected */ int dup; /* -1, or file descriptor being duplicated */ - struct redir_struct *next; /* pointer to the next redirect in the list */ glob_t word; /* *word.gl_pathv is the filename */ }; @@ -190,14 +200,16 @@ struct child_prog { }; struct pipe { - int jobid; /* job number */ + struct pipe *next; int num_progs; /* total number of programs in job */ int running_progs; /* number of programs running (not exited) */ - char *cmdtext; /* name of job */ char *cmdbuf; /* buffer various argv's point into */ +#if ENABLE_HUSH_INTERACTIVE + int jobid; /* job number */ + char *cmdtext; /* name of job */ pid_t pgrp; /* process group ID for the job */ +#endif struct child_prog *progs; /* array of commands in pipe */ - struct pipe *next; /* to track background commands */ int stopped_progs; /* number of programs alive, but stopped */ int job_context; /* bitmask defining current context */ pipe_style followup; /* PIPE_BG, PIPE_SEQ, PIPE_OR, PIPE_AND */ @@ -205,16 +217,16 @@ struct pipe { }; struct close_me { - int fd; struct close_me *next; + int fd; }; struct variables { + struct variables *next; const char *name; const char *value; int flg_export; int flg_read_only; - struct variables *next; }; /* globals, connect us to the outside world @@ -230,18 +242,22 @@ static unsigned char map[256]; static int fake_mode; static struct close_me *close_me_head; static const char *cwd; -static struct pipe *job_list; static unsigned last_bg_pid; +#if ENABLE_HUSH_INTERACTIVE static int last_jobid; +static struct pipe *job_list; /* 'interactive_fd' is a fd# open to ctty, if we have one * _AND_ if we decided to mess with job control */ static int interactive_fd; static pid_t saved_task_pgrp; static pid_t saved_tty_pgrp; +#else +enum { interactive_fd = 0 }; +#endif static const char *PS1; static const char *PS2; -static struct variables shell_ver = { "HUSH_VERSION", "0.01", 1, 1, 0 }; +static struct variables shell_ver = { NULL, "HUSH_VERSION", "0.01", 1, 1 }; static struct variables *top_vars = &shell_ver; @@ -322,9 +338,11 @@ static int builtin_eval(char **argv); static int builtin_exec(char **argv); static int builtin_exit(char **argv); static int builtin_export(char **argv); +#if ENABLE_HUSH_INTERACTIVE static int builtin_fg_bg(char **argv); -static int builtin_help(char **argv); static int builtin_jobs(char **argv); +#endif +static int builtin_help(char **argv); static int builtin_pwd(char **argv); static int builtin_read(char **argv); static int builtin_set(char **argv); @@ -387,10 +405,14 @@ static int parse_string_outer(const char *s, int flag); static int parse_file_outer(FILE *f); /* job management: */ static int checkjobs(struct pipe* fg_pipe); +#if ENABLE_HUSH_INTERACTIVE static int checkjobs_and_fg_shell(struct pipe* fg_pipe); static void insert_bg_job(struct pipe *pi); static void remove_bg_job(struct pipe *pi); static void delete_finished_bg_job(struct pipe *pi); +#else +int checkjobs_and_fg_shell(struct pipe* fg_pipe); /* never called */ +#endif /* local variable support */ static char **make_list_in(char **inp, char *name); static char *insert_var_value(char *inp); @@ -405,7 +427,9 @@ static void unset_local_var(const char *name); * For example, 'unset foo | whatever' will parse and run, but foo will * still be set at the end. */ static const struct built_in_command bltins[] = { +#if ENABLE_HUSH_INTERACTIVE { "bg", "Resume a job in the background", builtin_fg_bg }, +#endif { "break", "Exit for, while or until loop", builtin_not_written }, { "cd", "Change working directory", builtin_cd }, { "continue", "Continue for, while or until loop", builtin_not_written }, @@ -415,8 +439,10 @@ static const struct built_in_command bltins[] = { builtin_exec }, { "exit", "Exit from shell()", builtin_exit }, { "export", "Set environment variable", builtin_export }, +#if ENABLE_HUSH_INTERACTIVE { "fg", "Bring job into the foreground", builtin_fg_bg }, { "jobs", "Lists the active jobs", builtin_jobs }, +#endif { "pwd", "Print current directory", builtin_pwd }, { "read", "Input environment variable", builtin_read }, { "return", "Return from a function", builtin_not_written }, @@ -431,6 +457,9 @@ static const struct built_in_command bltins[] = { { NULL, NULL, NULL } }; +#if ENABLE_HUSH_INTERACTIVE + +#if ENABLE_FEATURE_SH_STANDALONE /* move to libbb? */ static void signal_SA_RESTART(int sig, void (*handler)(int)) { @@ -440,6 +469,7 @@ static void signal_SA_RESTART(int sig, void (*handler)(int)) sigemptyset(&sa.sa_mask); sigaction(sig, &sa, NULL); } +#endif /* Signals are grouped, we handle them in batches */ static void set_fatal_sighandler(void (*handler)(int)) @@ -469,6 +499,7 @@ static void set_misc_sighandler(void (*handler)(int)) } /* SIGCHLD is special and handled separately */ +#if ENABLE_FEATURE_SH_STANDALONE static void set_every_sighandler(void (*handler)(int)) { set_fatal_sighandler(handler); @@ -477,9 +508,9 @@ static void set_every_sighandler(void (*handler)(int)) signal(SIGCHLD, handler); } +static struct pipe *nofork_pipe; struct nofork_save_area nofork_save; static sigjmp_buf nofork_jb; -static struct pipe *nofork_pipe; static void handler_ctrl_c(int sig) { @@ -522,6 +553,8 @@ static void handler_ctrl_z(int sig) siglongjmp(nofork_jb, 1); } +#endif + /* Restores tty foreground process group, and exits. * May be called as signal handler for fatal signal * (will faithfully resend signal to itself, producing correct exit state) @@ -559,6 +592,16 @@ static void hush_exit(int exitcode) sigexit(- (exitcode & 0xff)); } +#else /* !INTERACTIVE */ + +#define set_fatal_sighandler(handler) ((void)0) +#define set_jobctrl_sighandler(handler) ((void)0) +#define set_misc_sighandler(handler) ((void)0) +#define hush_exit(e) exit(-(e)) + +#endif /* INTERACTIVE */ + + static const char *set_cwd(void) { if (cwd == bb_msg_unknown) @@ -684,6 +727,7 @@ static int builtin_export(char **argv) return res; } +#if ENABLE_HUSH_INTERACTIVE /* built-in 'fg' and 'bg' handler */ static int builtin_fg_bg(char **argv) { @@ -745,6 +789,7 @@ static int builtin_fg_bg(char **argv) } return EXIT_SUCCESS; } +#endif /* built-in 'help' handler */ static int builtin_help(char **argv ATTRIBUTE_UNUSED) @@ -762,6 +807,7 @@ static int builtin_help(char **argv ATTRIBUTE_UNUSED) return EXIT_SUCCESS; } +#if ENABLE_HUSH_INTERACTIVE /* built-in 'jobs' handler */ static int builtin_jobs(char **argv ATTRIBUTE_UNUSED) { @@ -778,6 +824,7 @@ static int builtin_jobs(char **argv ATTRIBUTE_UNUSED) } return EXIT_SUCCESS; } +#endif /* built-in 'pwd' handler */ static int builtin_pwd(char **argv ATTRIBUTE_UNUSED) @@ -1272,7 +1319,9 @@ static void pseudo_exec(struct child_prog *child) if (child->group) { debug_printf("runtime nesting to group\n"); // FIXME: do not modify globals! Think vfork! +#if ENABLE_HUSH_INTERACTIVE interactive_fd = 0; /* crucial!!!! */ +#endif rcode = run_list_real(child->group); /* OK to leak memory by not calling free_pipe_list, * since this process is about to exit */ @@ -1284,6 +1333,7 @@ static void pseudo_exec(struct child_prog *child) _exit(EXIT_SUCCESS); } +#if ENABLE_HUSH_INTERACTIVE static const char *get_cmdtext(struct pipe *pi) { char **argv; @@ -1312,7 +1362,9 @@ static const char *get_cmdtext(struct pipe *pi) p[-1] = '\0'; return pi->cmdtext; } +#endif +#if ENABLE_HUSH_INTERACTIVE static void insert_bg_job(struct pipe *pi) { struct pipe *thejob; @@ -1376,6 +1428,7 @@ static void delete_finished_bg_job(struct pipe *pi) free_pipe(pi, 0); free(pi); } +#endif /* Checks to see if any processes have exited -- if they have, figure out why and see if a job has completed */ @@ -1383,8 +1436,10 @@ static int checkjobs(struct pipe* fg_pipe) { int attributes; int status; +#if ENABLE_HUSH_INTERACTIVE int prognum = 0; struct pipe *pi; +#endif pid_t childpid; int rcode = 0; @@ -1401,6 +1456,10 @@ static int checkjobs(struct pipe* fg_pipe) * 1 <========== bg pipe is not fully done, but exitcode is already known! */ +//FIXME: non-interactive bash does not continue even if all processes in fg pipe +//are stopped. Testcase: "cat | cat" in a script (not on command line) +// + killall -STOP cat + wait_more: while ((childpid = waitpid(-1, &status, attributes)) > 0) { const int dead = WIFEXITED(status) || WIFSIGNALED(status); @@ -1437,8 +1496,10 @@ static int checkjobs(struct pipe* fg_pipe) 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 ENABLE_HUSH_INTERACTIVE if (fg_pipe->running_progs) insert_bg_job(fg_pipe); +#endif return rcode; } /* There are still running processes in the fg pipe */ @@ -1448,6 +1509,7 @@ static int checkjobs(struct pipe* fg_pipe) /* fall through to searching process in bg pipes */ } +#if ENABLE_HUSH_INTERACTIVE /* We asked to wait for bg or orphaned children */ /* No need to remember exitcode in this case */ for (pi = job_list; pi; pi = pi->next) { @@ -1458,11 +1520,13 @@ static int checkjobs(struct pipe* fg_pipe) prognum++; } } +#endif /* Happens when shell is used as init process (init=/bin/sh) */ debug_printf("checkjobs: pid %d was not in our list!\n", childpid); goto wait_more; +#if ENABLE_HUSH_INTERACTIVE found_pi_and_prognum: if (dead) { /* child exited */ @@ -1478,6 +1542,7 @@ static int checkjobs(struct pipe* fg_pipe) pi->stopped_progs++; pi->progs[prognum].is_stopped = 1; } +#endif } /* wait found no children or failed */ @@ -1491,6 +1556,7 @@ static int checkjobs(struct pipe* fg_pipe) return rcode; } +#if ENABLE_HUSH_INTERACTIVE static int checkjobs_and_fg_shell(struct pipe* fg_pipe) { pid_t p; @@ -1502,11 +1568,14 @@ static int checkjobs_and_fg_shell(struct pipe* fg_pipe) bb_perror_msg("tcsetpgrp-4a"); return rcode; } +#endif +#if ENABLE_FEATURE_SH_STANDALONE /* run_pipe_real's helper */ static int run_single_fg_nofork(struct pipe *pi, const struct bb_applet *a, char **argv) { +#if ENABLE_HUSH_INTERACTIVE int rcode; /* TSTP handler will store pid etc in pi */ nofork_pipe = pi; @@ -1532,7 +1601,11 @@ static int run_single_fg_nofork(struct pipe *pi, const struct bb_applet *a, else putchar('\n'); /* bash does this on Ctrl-C */ return 0; +#else + return run_nofork_applet(a, argv); +#endif } +#endif /* run_pipe_real() starts all the jobs, but doesn't wait for anything * to finish. See checkjobs(). @@ -1564,7 +1637,9 @@ static int run_pipe_real(struct pipe *pi) const int single_fg = (pi->num_progs == 1 && pi->followup != PIPE_BG); nextin = 0; +#if ENABLE_HUSH_INTERACTIVE pi->pgrp = -1; +#endif pi->running_progs = 0; pi->stopped_progs = 0; @@ -1691,6 +1766,7 @@ static int run_pipe_real(struct pipe *pi) if (!child->pid) { /* child */ /* Every child adds itself to new process group * with pgid == pid of first child in pipe */ +#if ENABLE_HUSH_INTERACTIVE if (interactive_fd) { /* Don't do pgrp restore anymore on fatal signals */ set_fatal_sighandler(SIG_DFL); @@ -1702,6 +1778,7 @@ static int run_pipe_real(struct pipe *pi) tcsetpgrp(interactive_fd, pi->pgrp); } } +#endif // in non-interactive case fatal sigs are already SIG_DFL close_all(); if (nextin != 0) { @@ -1728,9 +1805,11 @@ static int run_pipe_real(struct pipe *pi) pi->running_progs++; +#if ENABLE_HUSH_INTERACTIVE /* Second and next children need to know pid of first one */ if (pi->pgrp < 0) pi->pgrp = child->pid; +#endif /* Don't check for errors. The child may be dead already, * in which case setpgid returns error code EACCES. */ @@ -1858,7 +1937,9 @@ static int run_list_real(struct pipe *pi) /* XXX check bash's behavior with nontrivial pipes */ /* XXX compute jobid */ /* XXX what does bash do with attempts to background builtins? */ +#if ENABLE_HUSH_INTERACTIVE insert_bg_job(pi); +#endif rcode = EXIT_SUCCESS; } else { if (interactive_fd) { @@ -1931,8 +2012,10 @@ static int free_pipe(struct pipe *pi, int indent) } free(pi->progs); /* children are an array, they get freed all at once */ pi->progs = NULL; +#if ENABLE_HUSH_INTERACTIVE free(pi->cmdtext); pi->cmdtext = NULL; +#endif return ret_code; } @@ -3041,6 +3124,7 @@ static int parse_file_outer(FILE *f) return rcode; } +#if ENABLE_HUSH_INTERACTIVE /* Make sure we have a controlling tty. If we get started under a job * aware app (like bash for example), make sure we are now in charge so * we don't fight over who gets the foreground */ @@ -3055,7 +3139,7 @@ static void setup_job_control(void) /* If we were ran as 'hush &', * sleep until we are in the foreground. */ while (tcgetpgrp(interactive_fd) != shell_pgrp) { - /* Send TTIN to ourself (will stop us) */ + /* Send TTIN to ourself (should stop us) */ kill(- shell_pgrp, SIGTTIN); shell_pgrp = getpgrp(); } @@ -3073,6 +3157,7 @@ static void setup_job_control(void) /* Grab control of the terminal. */ tcsetpgrp(interactive_fd, getpid()); } +#endif int hush_main(int argc, char **argv); int hush_main(int argc, char **argv) @@ -3095,11 +3180,13 @@ int hush_main(int argc, char **argv) ifs = NULL; /* map[] is taken care of with call to update_ifs_map() */ fake_mode = 0; - interactive_fd = 0; close_me_head = NULL; +#if ENABLE_HUSH_INTERACTIVE + interactive_fd = 0; last_bg_pid = 0; job_list = NULL; last_jobid = 0; +#endif /* Initialize some more globals to non-zero values */ set_cwd(); @@ -3154,6 +3241,7 @@ int hush_main(int argc, char **argv) #endif } } +#if ENABLE_HUSH_INTERACTIVE /* A shell is interactive if the '-i' flag was given, or if all of * the following conditions are met: * no -c command @@ -3180,7 +3268,6 @@ int hush_main(int argc, char **argv) // to (inadvertently) close/redirect it } } - debug_printf("\ninteractive_fd=%d\n", interactive_fd); if (interactive_fd) { /* Looks like they want an interactive shell */ @@ -3196,6 +3283,7 @@ int hush_main(int argc, char **argv) printf("Enter 'help' for a list of built-in commands.\n\n"); #endif } +#endif if (argv[optind] == NULL) { opt = parse_file_outer(stdin); -- 2.7.4