hush: make job control and interactiveness configurable, part 2

This commit is contained in:
Denis Vlasenko 2007-04-28 16:48:27 +00:00
parent b81b3df1fa
commit e3f2f89891
2 changed files with 99 additions and 52 deletions

View File

@ -181,9 +181,21 @@ config HUSH_INTERACTIVE
default y default y
depends on HUSH depends on HUSH
help help
Enable interactive mode (Ctrl-Z, Ctrl-C, command editing) Enable interactive mode (prompt and command editing).
in the hush shell. Without this, hush reads and executes Without this, hush simply reads and executes commands
stdin just like a shell script from the file. from stdin just like a shell script from the file.
No prompt, no PS1/PS2 magic shell variables.
config HUSH_JOB
bool "Job control"
default n
depends on HUSH_INTERACTIVE
help
Enable job control: Ctrl-Z backgrounds, Ctrl-C interrupts current
command (not entire shell), fg/bg builtins work. Without this option,
"cmd &" still works by simply spawning a process and immediately
prompting for next command (or executing next command in a script),
but no separate process group is formed.
config LASH config LASH

View File

@ -86,10 +86,6 @@
/* Finer-grained debug switch */ /* Finer-grained debug switch */
//#define DEBUG_SHELL_JOBS //#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 #if !ENABLE_HUSH_INTERACTIVE
#undef ENABLE_FEATURE_EDITING #undef ENABLE_FEATURE_EDITING
#define ENABLE_FEATURE_EDITING 0 #define ENABLE_FEATURE_EDITING 0
@ -204,7 +200,7 @@ struct pipe {
int num_progs; /* total number of programs in job */ int num_progs; /* total number of programs in job */
int running_progs; /* number of programs running (not exited) */ int running_progs; /* number of programs running (not exited) */
char *cmdbuf; /* buffer various argv's point into */ char *cmdbuf; /* buffer various argv's point into */
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_JOB
int jobid; /* job number */ int jobid; /* job number */
char *cmdtext; /* name of job */ char *cmdtext; /* name of job */
pid_t pgrp; /* process group ID for the job */ pid_t pgrp; /* process group ID for the job */
@ -243,25 +239,26 @@ static int fake_mode;
static struct close_me *close_me_head; static struct close_me *close_me_head;
static const char *cwd; static const char *cwd;
static unsigned last_bg_pid; static unsigned last_bg_pid;
#if ENABLE_HUSH_INTERACTIVE #if !ENABLE_HUSH_INTERACTIVE
static int last_jobid; enum { interactive_fd = 0 };
static struct pipe *job_list; #else
/* 'interactive_fd' is a fd# open to ctty, if we have one /* 'interactive_fd' is a fd# open to ctty, if we have one
* _AND_ if we decided to mess with job control */ * _AND_ if we decided to mess with job control */
static int interactive_fd; static int interactive_fd;
#if ENABLE_HUSH_JOB
static pid_t saved_task_pgrp; static pid_t saved_task_pgrp;
static pid_t saved_tty_pgrp; static pid_t saved_tty_pgrp;
#else static int last_jobid;
enum { interactive_fd = 0 }; static struct pipe *job_list;
#endif #endif
static const char *PS1; static const char *PS1;
static const char *PS2; static const char *PS2;
#endif
static struct variables shell_ver = { NULL, "HUSH_VERSION", "0.01", 1, 1 }; static struct variables shell_ver = { NULL, "HUSH_VERSION", "0.01", 1, 1 };
static struct variables *top_vars = &shell_ver; static struct variables *top_vars = &shell_ver;
#define B_CHUNK 100
#define B_CHUNK (100)
#define B_NOSPAC 1 #define B_NOSPAC 1
typedef struct { typedef struct {
@ -280,8 +277,10 @@ typedef struct {
struct in_str { struct in_str {
const char *p; const char *p;
char peek_buf[2]; char peek_buf[2];
#if ENABLE_HUSH_INTERACTIVE
int __promptme; int __promptme;
int promptmode; int promptmode;
#endif
FILE *file; FILE *file;
int (*get) (struct in_str *); int (*get) (struct in_str *);
int (*peek) (struct in_str *); int (*peek) (struct in_str *);
@ -323,12 +322,11 @@ static char *indenter(int i)
#define final_printf debug_printf #define final_printf debug_printf
static void __syntax(const char *file, int line) static void __syntax(int line)
{ {
bb_error_msg("syntax error %s:%d", file, line); bb_error_msg("syntax error hush.c:%d", line);
} }
/* NB: was __FILE__, but that produces full path sometimes, so... */ #define syntax() __syntax(__LINE__)
#define syntax() __syntax("hush.c", __LINE__)
/* Index of subroutines: */ /* Index of subroutines: */
/* function prototypes for builtins */ /* function prototypes for builtins */
@ -338,7 +336,7 @@ static int builtin_eval(char **argv);
static int builtin_exec(char **argv); static int builtin_exec(char **argv);
static int builtin_exit(char **argv); static int builtin_exit(char **argv);
static int builtin_export(char **argv); static int builtin_export(char **argv);
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_JOB
static int builtin_fg_bg(char **argv); static int builtin_fg_bg(char **argv);
static int builtin_jobs(char **argv); static int builtin_jobs(char **argv);
#endif #endif
@ -405,7 +403,7 @@ static int parse_string_outer(const char *s, int flag);
static int parse_file_outer(FILE *f); static int parse_file_outer(FILE *f);
/* job management: */ /* job management: */
static int checkjobs(struct pipe* fg_pipe); static int checkjobs(struct pipe* fg_pipe);
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_JOB
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);
@ -427,7 +425,7 @@ static void unset_local_var(const char *name);
* For example, 'unset foo | whatever' will parse and run, but foo will * For example, 'unset foo | whatever' will parse and run, but foo will
* still be set at the end. */ * still be set at the end. */
static const struct built_in_command bltins[] = { static const struct built_in_command bltins[] = {
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_JOB
{ "bg", "Resume a job in the background", builtin_fg_bg }, { "bg", "Resume a job in the background", builtin_fg_bg },
#endif #endif
{ "break", "Exit for, while or until loop", builtin_not_written }, { "break", "Exit for, while or until loop", builtin_not_written },
@ -439,7 +437,7 @@ static const struct built_in_command bltins[] = {
builtin_exec }, builtin_exec },
{ "exit", "Exit from shell()", builtin_exit }, { "exit", "Exit from shell()", builtin_exit },
{ "export", "Set environment variable", builtin_export }, { "export", "Set environment variable", builtin_export },
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_JOB
{ "fg", "Bring job into the foreground", builtin_fg_bg }, { "fg", "Bring job into the foreground", builtin_fg_bg },
{ "jobs", "Lists the active jobs", builtin_jobs }, { "jobs", "Lists the active jobs", builtin_jobs },
#endif #endif
@ -457,7 +455,7 @@ static const struct built_in_command bltins[] = {
{ NULL, NULL, NULL } { NULL, NULL, NULL }
}; };
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_JOB
#if ENABLE_FEATURE_SH_STANDALONE #if ENABLE_FEATURE_SH_STANDALONE
/* move to libbb? */ /* move to libbb? */
@ -727,7 +725,7 @@ static int builtin_export(char **argv)
return res; return res;
} }
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_JOB
/* built-in 'fg' and 'bg' handler */ /* built-in 'fg' and 'bg' handler */
static int builtin_fg_bg(char **argv) static int builtin_fg_bg(char **argv)
{ {
@ -807,7 +805,7 @@ static int builtin_help(char **argv ATTRIBUTE_UNUSED)
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_JOB
/* built-in 'jobs' handler */ /* built-in 'jobs' handler */
static int builtin_jobs(char **argv ATTRIBUTE_UNUSED) static int builtin_jobs(char **argv ATTRIBUTE_UNUSED)
{ {
@ -1032,6 +1030,7 @@ static int static_peek(struct in_str *i)
return *i->p; return *i->p;
} }
#if ENABLE_HUSH_INTERACTIVE
static void cmdedit_set_initial_prompt(void) static void cmdedit_set_initial_prompt(void)
{ {
#if !ENABLE_FEATURE_EDITING_FANCY_PROMPT #if !ENABLE_FEATURE_EDITING_FANCY_PROMPT
@ -1065,17 +1064,19 @@ static const char* setup_prompt_string(int promptmode)
debug_printf("result %s\n", prompt_str); debug_printf("result %s\n", prompt_str);
return prompt_str; return prompt_str;
} }
#endif
#if ENABLE_FEATURE_EDITING #if ENABLE_FEATURE_EDITING
static line_input_t *line_input_state; static line_input_t *line_input_state;
#endif #endif
#if ENABLE_HUSH_INTERACTIVE
static int get_user_input(struct in_str *i) static int get_user_input(struct in_str *i)
{ {
static char the_command[ENABLE_FEATURE_EDITING ? BUFSIZ : 2];
int r; int r;
const char *prompt_str; const char *prompt_str;
static char the_command[BUFSIZ];
prompt_str = setup_prompt_string(i->promptmode); prompt_str = setup_prompt_string(i->promptmode);
#if ENABLE_FEATURE_EDITING #if ENABLE_FEATURE_EDITING
/* /*
@ -1095,6 +1096,7 @@ static int get_user_input(struct in_str *i)
i->p = the_command; i->p = the_command;
return r; /* < 0 == EOF. Not meaningful otherwise */ return r; /* < 0 == EOF. Not meaningful otherwise */
} }
#endif
/* This is the magic location that prints prompts /* This is the magic location that prints prompts
* and gets data back from the user */ * and gets data back from the user */
@ -1109,7 +1111,8 @@ static int file_get(struct in_str *i)
} else { } else {
/* need to double check i->file because we might be doing something /* need to double check i->file because we might be doing something
* more complicated by now, like sourcing or substituting. */ * more complicated by now, like sourcing or substituting. */
if (i->__promptme && interactive_fd && i->file == stdin) { #if ENABLE_HUSH_INTERACTIVE
if (interactive_fd && i->__promptme && i->file == stdin) {
while (!i->p || !(interactive_fd && i->p[0])) { while (!i->p || !(interactive_fd && i->p[0])) {
if (get_user_input(i) < 0) if (get_user_input(i) < 0)
return EOF; return EOF;
@ -1119,14 +1122,17 @@ static int file_get(struct in_str *i)
if (i->p && *i->p) { if (i->p && *i->p) {
ch = *i->p++; ch = *i->p++;
} }
} else { } else
#endif
{
ch = fgetc(i->file); ch = fgetc(i->file);
} }
debug_printf("b_getch: got a %d\n", ch); debug_printf("b_getch: got a %d\n", ch);
} }
#if ENABLE_HUSH_INTERACTIVE
if (ch == '\n') if (ch == '\n')
i->__promptme = 1; i->__promptme = 1;
#endif
return ch; return ch;
} }
@ -1149,8 +1155,10 @@ static void setup_file_in_str(struct in_str *i, FILE *f)
{ {
i->peek = file_peek; i->peek = file_peek;
i->get = file_get; i->get = file_get;
#if ENABLE_HUSH_INTERACTIVE
i->__promptme = 1; i->__promptme = 1;
i->promptmode = 1; i->promptmode = 1;
#endif
i->file = f; i->file = f;
i->p = NULL; i->p = NULL;
} }
@ -1159,8 +1167,10 @@ static void setup_string_in_str(struct in_str *i, const char *s)
{ {
i->peek = static_peek; i->peek = static_peek;
i->get = static_get; i->get = static_get;
#if ENABLE_HUSH_INTERACTIVE
i->__promptme = 1; i->__promptme = 1;
i->promptmode = 1; i->promptmode = 1;
#endif
i->p = s; i->p = s;
} }
@ -1333,7 +1343,7 @@ static void pseudo_exec(struct child_prog *child)
_exit(EXIT_SUCCESS); _exit(EXIT_SUCCESS);
} }
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_JOB
static const char *get_cmdtext(struct pipe *pi) static const char *get_cmdtext(struct pipe *pi)
{ {
char **argv; char **argv;
@ -1364,7 +1374,7 @@ static const char *get_cmdtext(struct pipe *pi)
} }
#endif #endif
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_JOB
static void insert_bg_job(struct pipe *pi) static void insert_bg_job(struct pipe *pi)
{ {
struct pipe *thejob; struct pipe *thejob;
@ -1436,7 +1446,7 @@ static int checkjobs(struct pipe* fg_pipe)
{ {
int attributes; int attributes;
int status; int status;
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_JOB
int prognum = 0; int prognum = 0;
struct pipe *pi; struct pipe *pi;
#endif #endif
@ -1496,7 +1506,7 @@ static int checkjobs(struct pipe* fg_pipe)
fg_pipe->running_progs, fg_pipe->stopped_progs); fg_pipe->running_progs, fg_pipe->stopped_progs);
if (fg_pipe->running_progs - fg_pipe->stopped_progs <= 0) { if (fg_pipe->running_progs - fg_pipe->stopped_progs <= 0) {
/* All processes in fg pipe have exited/stopped */ /* All processes in fg pipe have exited/stopped */
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_JOB
if (fg_pipe->running_progs) if (fg_pipe->running_progs)
insert_bg_job(fg_pipe); insert_bg_job(fg_pipe);
#endif #endif
@ -1509,7 +1519,7 @@ static int checkjobs(struct pipe* fg_pipe)
/* fall through to searching process in bg pipes */ /* fall through to searching process in bg pipes */
} }
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_JOB
/* 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) {
@ -1526,7 +1536,7 @@ static int checkjobs(struct pipe* fg_pipe)
debug_printf("checkjobs: pid %d was not in our list!\n", childpid); debug_printf("checkjobs: pid %d was not in our list!\n", childpid);
goto wait_more; goto wait_more;
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_JOB
found_pi_and_prognum: found_pi_and_prognum:
if (dead) { if (dead) {
/* child exited */ /* child exited */
@ -1556,7 +1566,7 @@ static int checkjobs(struct pipe* fg_pipe)
return rcode; return rcode;
} }
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_JOB
static int checkjobs_and_fg_shell(struct pipe* fg_pipe) static int checkjobs_and_fg_shell(struct pipe* fg_pipe)
{ {
pid_t p; pid_t p;
@ -1575,7 +1585,7 @@ static int checkjobs_and_fg_shell(struct pipe* fg_pipe)
static int run_single_fg_nofork(struct pipe *pi, const struct bb_applet *a, static int run_single_fg_nofork(struct pipe *pi, const struct bb_applet *a,
char **argv) char **argv)
{ {
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_JOB
int rcode; int rcode;
/* TSTP handler will store pid etc in pi */ /* TSTP handler will store pid etc in pi */
nofork_pipe = pi; nofork_pipe = pi;
@ -1637,7 +1647,7 @@ static int run_pipe_real(struct pipe *pi)
const int single_fg = (pi->num_progs == 1 && pi->followup != PIPE_BG); const int single_fg = (pi->num_progs == 1 && pi->followup != PIPE_BG);
nextin = 0; nextin = 0;
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_JOB
pi->pgrp = -1; pi->pgrp = -1;
#endif #endif
pi->running_progs = 0; pi->running_progs = 0;
@ -1766,7 +1776,7 @@ static int run_pipe_real(struct pipe *pi)
if (!child->pid) { /* child */ if (!child->pid) { /* child */
/* Every child adds itself to new process group /* Every child adds itself to new process group
* with pgid == pid of first child in pipe */ * with pgid == pid of first child in pipe */
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_JOB
if (interactive_fd) { if (interactive_fd) {
/* Don't do pgrp restore anymore on fatal signals */ /* Don't do pgrp restore anymore on fatal signals */
set_fatal_sighandler(SIG_DFL); set_fatal_sighandler(SIG_DFL);
@ -1805,7 +1815,7 @@ static int run_pipe_real(struct pipe *pi)
pi->running_progs++; pi->running_progs++;
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_JOB
/* Second and next children need to know pid of first one */ /* Second and next children need to know pid of first one */
if (pi->pgrp < 0) if (pi->pgrp < 0)
pi->pgrp = child->pid; pi->pgrp = child->pid;
@ -1937,14 +1947,17 @@ static int run_list_real(struct pipe *pi)
/* XXX check bash's behavior with nontrivial pipes */ /* XXX check bash's behavior with nontrivial pipes */
/* XXX compute jobid */ /* XXX compute jobid */
/* XXX what does bash do with attempts to background builtins? */ /* XXX what does bash do with attempts to background builtins? */
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_JOB
insert_bg_job(pi); insert_bg_job(pi);
#endif #endif
rcode = EXIT_SUCCESS; rcode = EXIT_SUCCESS;
} else { } else {
#if ENABLE_HUSH_JOB
if (interactive_fd) { if (interactive_fd) {
rcode = checkjobs_and_fg_shell(pi); rcode = checkjobs_and_fg_shell(pi);
} else { } else
#endif
{
rcode = checkjobs(pi); rcode = checkjobs(pi);
} }
debug_printf("checkjobs returned %d\n", rcode); debug_printf("checkjobs returned %d\n", rcode);
@ -2012,7 +2025,7 @@ static int free_pipe(struct pipe *pi, int indent)
} }
free(pi->progs); /* children are an array, they get freed all at once */ free(pi->progs); /* children are an array, they get freed all at once */
pi->progs = NULL; pi->progs = NULL;
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_JOB
free(pi->cmdtext); free(pi->cmdtext);
pi->cmdtext = NULL; pi->cmdtext = NULL;
#endif #endif
@ -3084,7 +3097,9 @@ static int parse_stream_outer(struct in_str *inp, int flag)
update_ifs_map(); update_ifs_map();
if (!(flag & FLAG_PARSE_SEMICOLON) || (flag & FLAG_REPARSING)) if (!(flag & FLAG_PARSE_SEMICOLON) || (flag & FLAG_REPARSING))
mapset(";$&|", 0); mapset(";$&|", 0);
#if ENABLE_HUSH_INTERACTIVE
inp->promptmode = 1; inp->promptmode = 1;
#endif
rcode = parse_stream(&temp, &ctx, inp, '\n'); rcode = parse_stream(&temp, &ctx, inp, '\n');
if (rcode != 1 && ctx.old_flag != 0) { if (rcode != 1 && ctx.old_flag != 0) {
syntax(); syntax();
@ -3124,7 +3139,7 @@ static int parse_file_outer(FILE *f)
return rcode; return rcode;
} }
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_JOB
/* Make sure we have a controlling tty. If we get started under a job /* 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 * aware app (like bash for example), make sure we are now in charge so
* we don't fight over who gets the foreground */ * we don't fight over who gets the foreground */
@ -3183,6 +3198,8 @@ int hush_main(int argc, char **argv)
close_me_head = NULL; close_me_head = NULL;
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_INTERACTIVE
interactive_fd = 0; interactive_fd = 0;
#endif
#if ENABLE_HUSH_JOB
last_bg_pid = 0; last_bg_pid = 0;
job_list = NULL; job_list = NULL;
last_jobid = 0; last_jobid = 0;
@ -3190,11 +3207,14 @@ int hush_main(int argc, char **argv)
/* Initialize some more globals to non-zero values */ /* Initialize some more globals to non-zero values */
set_cwd(); set_cwd();
if (ENABLE_FEATURE_EDITING) #if ENABLE_HUSH_INTERACTIVE
cmdedit_set_initial_prompt(); #if ENABLE_FEATURE_EDITING
else PS1 = NULL; cmdedit_set_initial_prompt();
#else
PS1 = NULL;
#endif
PS2 = "> "; PS2 = "> ";
#endif
/* initialize our shell local variables with the values /* initialize our shell local variables with the values
* currently living in the environment */ * currently living in the environment */
e = environ; e = environ;
@ -3241,7 +3261,7 @@ int hush_main(int argc, char **argv)
#endif #endif
} }
} }
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_JOB
/* A shell is interactive if the '-i' flag was given, or if all of /* A shell is interactive if the '-i' flag was given, or if all of
* the following conditions are met: * the following conditions are met:
* no -c command * no -c command
@ -3283,6 +3303,21 @@ int hush_main(int argc, char **argv)
printf("Enter 'help' for a list of built-in commands.\n\n"); printf("Enter 'help' for a list of built-in commands.\n\n");
#endif #endif
} }
#elif ENABLE_HUSH_INTERACTIVE
/* no job control compiled, only prompt/line editing */
if (argv[optind] == NULL && input == stdin
&& isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)
) {
interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255);
if (interactive_fd < 0) {
/* try to dup to any fd */
interactive_fd = dup(STDIN_FILENO);
if (interactive_fd < 0)
/* give up */
interactive_fd = 0;
}
}
#endif #endif
if (argv[optind] == NULL) { if (argv[optind] == NULL) {