0,0 → 1,507 |
/* |
* esh - the 'pluggable' shell. |
* |
* Developed by Godmar Back for CS 3214 Fall 2009 |
* Virginia Tech. |
*/ |
#include <stdio.h> |
#include <readline/readline.h> |
#include <unistd.h> |
#include <sys/wait.h> |
#include <assert.h> |
#include <sys/types.h> |
#include <sys/stat.h> |
#include <fcntl.h> |
|
#include "esh.h" |
#include "esh-sys-utils.h" |
|
// Function declarations |
static void usage(char *progname); |
static char * build_prompt_from_plugins(void); |
|
static struct list *get_job_list(void); |
static struct esh_pipeline *job_from_jid(int jid); |
static struct esh_pipeline *job_from_pgrp(pid_t pgrp); |
static struct esh_command *cmd_from_pid(pid_t pid); |
|
static void print_jobs(void); |
static void purge_jobs(void); |
static void wait_for_job(struct esh_pipeline *pipeline,bool exists_in_job_list); |
static void send_signal_to_job(int jid, int sig); |
static void bring_to_foreground(int jid); |
static void give_terminal_to(pid_t pgrp, struct termios *pg_tty_state); |
|
static void sigchld_handler(int sig, siginfo_t *info, void *_ctxt); |
|
static int job_next_id = 1; |
static struct termios *sys_tty; |
static pid_t shell_pgrp; |
|
// List of all jobs running in the current shell |
static struct esh_command_line *job_list; |
|
/* The shell object plugins use. |
* Some methods are set to defaults. */ |
struct esh_shell shell = { |
.get_jobs = get_job_list, |
.get_job_from_jid = job_from_jid, |
.get_job_from_pgrp = job_from_pgrp, |
.get_cmd_from_pid = cmd_from_pid, |
.build_prompt = build_prompt_from_plugins, |
.readline = readline, /* GNU readline(3) */ |
.parse_command_line = esh_parse_command_line /* Default parser */ |
}; |
|
int main(int ac, char *av[]) { |
int opt; |
list_init(&esh_plugin_list); |
int i = 0; |
|
// Create an empty list of pipelines (jobs) |
job_list = esh_command_line_create_empty(); |
// Obtain an empty (sane) terminal |
sys_tty = esh_sys_tty_init(); |
// Store the process group of the shell |
shell_pgrp = getpgrp(); |
|
/* Process command-line arguments. See getopt(3) */ |
while ((opt = getopt(ac, av, "hp:")) > 0) { |
switch (opt) { |
case 'h': |
usage(av[0]); |
break; |
|
case 'p': |
esh_plugin_load_from_directory(optarg); |
break; |
} |
} |
|
esh_plugin_initialize(&shell); |
|
/* Read/eval loop. */ |
for (;;) { |
// Ensure that the shell has access to input/output before prompting |
give_terminal_to(shell_pgrp, sys_tty); |
|
/* Do not output a prompt unless shell's stdin is a terminal */ |
char * prompt = isatty(0) ? shell.build_prompt() : NULL; |
char * cmdline = shell.readline(prompt); |
free (prompt); |
|
if (cmdline == NULL) /* User typed EOF */ |
break; |
|
struct esh_command_line * cline = shell.parse_command_line(cmdline); |
free (cmdline); |
if (cline == NULL) /* Error in command line */ |
continue; |
|
if (list_empty(&cline->pipes)) { /* User hit enter */ |
esh_command_line_free(cline); |
continue; |
} |
|
esh_signal_sethandler(SIGCHLD, sigchld_handler); |
esh_signal_block(SIGCHLD); |
|
// Pop each pipeline, parse it, then execute the commands |
while (!list_empty(&cline->pipes)) { |
struct list_elem *l = list_pop_front(&cline->pipes); |
struct esh_pipeline *pipeline = list_entry(l, struct esh_pipeline, elem); |
|
// Initialize some variables in the pipeline |
pipeline->jid = 0; |
pipeline->pgrp = 0; |
memset(pipeline->cmd_string, 0, 256); |
|
// Initialize array of pipes |
int p_pipe_ind = 0; |
int p_pipe_max = list_size(&pipeline->commands) - 1; |
int **pipes = (int**) malloc(p_pipe_max*sizeof(int*)); |
if (pipes == NULL) esh_sys_fatal_error("malloc: "); |
for (i = 0; i < p_pipe_max; i++) { |
pipes[i] = (int *) malloc(2); |
if (pipes[i] == NULL) esh_sys_fatal_error("malloc: "); |
if (pipe(pipes[i]) == -1) esh_sys_fatal_error("pipe: "); |
} |
|
// Concat the command string containing the parsed commands |
struct list_elem *c = list_begin(&pipeline->commands); |
for (; c != list_end(&pipeline->commands); c = list_next(c)) { |
struct esh_command *cmd = list_entry(c, struct esh_command, elem); |
char **p = cmd->argv; |
while (*p) { |
strcat(pipeline->cmd_string, *p++); |
if (*p) |
strcat(pipeline->cmd_string, " "); |
} |
if (list_next(c) != list_tail(&pipeline->commands)) |
strcat(pipeline->cmd_string, " | "); |
} |
// printf("Command string: %s\n", pipeline->cmd_string); |
|
// Get the first command |
c = list_begin(&pipeline->commands); |
struct esh_command *cmd = list_entry(c, struct esh_command, elem); |
char **p = cmd->argv; |
|
// Check if the first command is a built in command |
if (strcmp(*p, "jobs") == 0) { |
print_jobs(); |
purge_jobs(); |
continue; |
} else if (strcmp(*p, "fg") == 0) { |
bring_to_foreground(atoi(cmd->argv[1])); |
continue; |
} else if (strcmp(*p, "bg") == 0) { |
send_signal_to_job(atoi(cmd->argv[1]), SIGCONT); |
continue; |
} else if (strcmp(*p, "kill") == 0) { |
send_signal_to_job(atoi(cmd->argv[1]), SIGKILL); |
continue; |
} else if (strcmp(*p, "stop") == 0) { |
send_signal_to_job(atoi(cmd->argv[1]), SIGSTOP); |
continue; |
} else if (strcmp(*p, "exit") == 0) { |
exit(0); |
} |
|
// Otherwise parse and execute each command |
for (; c != list_end(&pipeline->commands); c = list_next(c)) { |
cmd = list_entry(c, struct esh_command, elem); |
p = cmd->argv; |
|
// If it is not a built in command, fork the process and let it run |
if ((cmd->pid = fork()) == 0) { |
// Set the group process ID to the same value for each pipeline |
setpgid(0,pipeline->pgrp); |
esh_signal_unblock(SIGCHLD); |
|
if (p_pipe_max) { |
// Set up the pipelines if there are more than one command |
if (p_pipe_ind == 0) { |
// First command |
if (dup2(pipes[p_pipe_ind][1], 1) < 0) |
esh_sys_fatal_error("dup2: "); |
// Set stdin of the first process to the input file if it is specified |
if (pipeline->iored_input) { |
if (dup2(open(pipeline->iored_input, O_RDONLY), 0) < 0) |
esh_sys_fatal_error("dup2: "); |
} |
} else if (p_pipe_ind == p_pipe_max) { |
// Last command |
if (dup2(pipes[p_pipe_ind-1][0], 0) < 0) |
esh_sys_fatal_error("dup2: "); |
// Set stdout of the final process to the output file if it is specified |
if (pipeline->iored_output) { |
// Check if we are creating a new file or appending to an existing file |
if (pipeline->append_to_output) { |
if (dup2(open(pipeline->iored_output, O_WRONLY|O_APPEND), 1) < 0) |
esh_sys_fatal_error("dup2: "); |
} else { |
if (dup2(creat(pipeline->iored_output, 0600), 1) < 0) |
esh_sys_fatal_error("dup2: "); |
} |
} |
} else { |
// All other commands in between |
if (dup2(pipes[p_pipe_ind-1][0], 0) < 0) |
esh_sys_fatal_error("dup2: "); |
if (dup2(pipes[p_pipe_ind][1], 1) < 0) |
esh_sys_fatal_error("dup2: "); |
} |
// Close all pipes |
for (i = 0; i < p_pipe_max; i++) { |
if (close(pipes[i][0]) == -1) |
esh_sys_fatal_error("close: "); |
if (close(pipes[i][1]) == -1) |
esh_sys_fatal_error("close: "); |
} |
} else { |
// If there is only one command, set stdin to input file if it is specified |
if (pipeline->iored_input) { |
if (dup2(open(pipeline->iored_input, O_RDONLY), 0) < 0) |
esh_sys_fatal_error("dup2: "); |
} |
// Set stdout of the process to the output file if it is specified |
if (pipeline->iored_output) { |
// Check if we are creating a new file or appending to an existing file |
if (pipeline->append_to_output) { |
if (dup2(open(pipeline->iored_output, O_WRONLY|O_APPEND), 1) < 0) |
esh_sys_fatal_error("dup2: "); |
} else { |
if (dup2(creat(pipeline->iored_output, 0600), 1) < 0) |
esh_sys_fatal_error("dup2: "); |
} |
} |
} |
|
if (execvp(cmd->argv[0],&(cmd->argv[0])) < 0) |
printf("%s: Command not found.\n",cmd->argv[0]); |
return 1; |
} |
if (cmd->pid == -1) |
esh_sys_fatal_error("fork: "); |
|
// Make sure the terminal is in its own process group |
if (setpgid(0,0) == -1) |
esh_sys_error("setpgid: "); |
|
// Store the process group ID |
if (pipeline->pgrp == 0) |
pipeline->pgrp = cmd->pid; |
|
// Increment the pipe counter |
if (p_pipe_max) { |
p_pipe_ind++; |
} |
} |
|
// Close all pipes |
for (i = 0; i < p_pipe_max; i++) { |
if (close(pipes[i][0]) == -1) |
esh_sys_fatal_error("close: "); |
if (close(pipes[i][1]) == -1) |
esh_sys_fatal_error("close: "); |
} |
|
if (!pipeline->bg_job) { |
// If the process is going to be executed in the background, give it the terminal |
pipeline->status = FOREGROUND; |
esh_sys_tty_save(sys_tty); |
give_terminal_to(pipeline->pgrp, NULL); |
wait_for_job(pipeline, false); |
} else { |
// Otherwise save the job in the job list and move on |
pipeline->status = BACKGROUND; |
// Assign the pipeline a job ID |
pipeline->jid = job_next_id; |
job_next_id++; |
list_push_back(shell.get_jobs(),l); |
printf("[%d] %d\n", pipeline->jid, pipeline->pgrp); |
} |
} |
|
esh_signal_unblock(SIGCHLD); |
esh_command_line_free(cline); |
} |
return 0; |
} |
|
static void usage(char *progname) { |
printf("Usage: %s -h\n" |
" -h print this help\n" |
" -p plugindir directory from which to load plug-ins\n", |
progname); |
|
exit(EXIT_SUCCESS); |
} |
|
/* Build a prompt by assembling fragments from loaded plugins that |
* implement 'make_prompt.' |
* |
* This function demonstrates how to iterate over all loaded plugins. |
*/ |
static char * build_prompt_from_plugins(void) { |
char *prompt = NULL; |
struct list_elem * e = list_begin(&esh_plugin_list); |
|
for (; e != list_end(&esh_plugin_list); e = list_next(e)) { |
struct esh_plugin *plugin = list_entry(e, struct esh_plugin, elem); |
|
if (plugin->make_prompt == NULL) |
continue; |
|
/* append prompt fragment created by plug-in */ |
char * p = plugin->make_prompt(); |
if (prompt == NULL) { |
prompt = p; |
} else { |
prompt = realloc(prompt, strlen(prompt) + strlen(p) + 1); |
strcat(prompt, p); |
free(p); |
} |
} |
|
/* default prompt */ |
if (prompt == NULL) |
prompt = strdup("esh> "); |
|
return prompt; |
} |
|
/* Shell function to return the list of jobs running in the shell */ |
static struct list *get_job_list(void) { |
return &job_list->pipes; |
} |
|
/* Shell function to return a job given a job ID */ |
static struct esh_pipeline *job_from_jid(int jid) { |
struct list_elem * p = list_begin (get_job_list()); |
for (; p != list_end (get_job_list()); p = list_next (p)) { |
struct esh_pipeline *pipe = list_entry(p, struct esh_pipeline, elem); |
if (pipe->jid == jid) |
return pipe; |
} |
return NULL; |
} |
|
/* Shell function to return a job given a process group */ |
static struct esh_pipeline *job_from_pgrp(pid_t pgrp) { |
struct list_elem * p = list_begin (get_job_list()); |
for (; p != list_end (get_job_list()); p = list_next (p)) { |
struct esh_pipeline *pipe = list_entry(p, struct esh_pipeline, elem); |
if (pipe->pgrp == pgrp) |
return pipe; |
} |
return NULL; |
} |
|
/* Shell function to return a command given a PID */ |
static struct esh_command *cmd_from_pid(pid_t pid) { |
struct list_elem * p = list_begin (get_job_list()); |
for (; p != list_end (get_job_list()); p = list_next (p)) { |
struct esh_pipeline *pipe = list_entry(p, struct esh_pipeline, elem); |
struct list_elem *c = list_begin(&pipe->commands); |
for (; c != list_end(&pipe->commands); c = list_next(c)) { |
struct esh_command *cmd = list_entry(c, struct esh_command, elem); |
if (cmd->pid == pid) |
return cmd; |
} |
} |
return NULL; |
} |
|
/* Prints out the current list of jobs in the shell and their statuses */ |
static void print_jobs(void) { |
struct list_elem * p = list_begin (shell.get_jobs()); |
for (; p != list_end (shell.get_jobs()); p = list_next (p)) { |
struct esh_pipeline *pipe = list_entry(p, struct esh_pipeline, elem); |
|
// For each job, print out [JobID] Status (Command String) |
printf("[%d] ", pipe->jid); |
if (pipe->status == FOREGROUND || pipe->status == BACKGROUND) |
printf("Running "); |
else if (pipe->status == COMPLETED) |
printf("Done "); |
else if (pipe->status == TERMINATED) |
printf("Terminated "); |
else if (pipe->status == STOPPED) |
printf("Stopped "); |
else |
printf("Unknown State "); |
|
printf("(%s)\n", pipe->cmd_string); |
} |
} |
|
/* Removes completed or terminated jobs from the job list */ |
static void purge_jobs(void) { |
struct list_elem * p = list_begin (shell.get_jobs()); |
for (; p != list_end (shell.get_jobs()); p = list_next (p)) { |
struct esh_pipeline *pipe = list_entry(p, struct esh_pipeline, elem); |
if (pipe->status == COMPLETED || pipe->status == TERMINATED) { |
list_remove(p); |
} |
} |
// Also reset the job ID if the job list is empty |
if (list_empty(shell.get_jobs())) { |
job_next_id = 1; |
} |
} |
|
/* Waits for the specified job to finish */ |
static void wait_for_job(struct esh_pipeline *pipeline, bool exists_in_job_list) { |
assert(esh_signal_is_blocked(SIGCHLD)); |
|
// Loop till all commands in the pipeline is done executing |
while (pipeline->status == FOREGROUND && !list_empty(&pipeline->commands)) { |
int status; |
|
// Wait till a child process exits |
pid_t child = waitpid(-1, &status, WUNTRACED); |
if (child != -1) { |
// If child has exited or terminated, remove the child from the pipeline |
if (WIFEXITED(status) || WIFSIGNALED(status)) { |
struct list_elem * e = list_begin (&pipeline->commands); |
for (; e != list_end (&pipeline->commands); e = list_next (e)) { |
struct esh_command *cmd = list_entry(e, struct esh_command, elem); |
if (child == cmd->pid) |
list_remove(e); |
} |
} |
// If child was stopped (INTPSTP), put it in the job list and print out status |
if (WIFSTOPPED(status)) { |
pipeline->status = STOPPED; |
if (pipeline->jid == 0) { |
pipeline->jid = job_next_id; |
job_next_id++; |
} |
if (!exists_in_job_list) |
list_push_back(shell.get_jobs(), &pipeline->elem); |
printf("\n[%d] Stopped (%s)\n", pipeline->jid, pipeline->cmd_string); |
return; |
} |
} |
} |
pipeline->status = COMPLETED; |
} |
|
/* Sends the specified signal to the specified job ID */ |
static void send_signal_to_job(int jid, int sig) { |
struct esh_pipeline *pipe = job_from_jid(jid); |
if (killpg(pipe->pgrp, sig) == -1) |
esh_sys_fatal_error("killpg: "); |
} |
|
/* Brings a job to the foreground */ |
static void bring_to_foreground(int jid) { |
struct esh_pipeline *pipe = job_from_jid(jid); |
printf("%s\r\n", pipe->cmd_string); |
pipe->status = FOREGROUND; |
|
// Give the foreground process the terminal |
give_terminal_to(pipe->pgrp, NULL); |
// Resume the process |
send_signal_to_job(jid, SIGCONT); |
// Wait for process to finish |
wait_for_job(pipe, true); |
} |
|
/* Gives the terminal to the specified process group */ |
static void give_terminal_to(pid_t pgrp, struct termios *pg_tty_state) { |
esh_signal_block(SIGTTOU); |
// printf("Terminal to process group %d\n", pgrp); |
int rc = tcsetpgrp(esh_sys_tty_getfd(), pgrp); |
if (rc == -1) |
esh_sys_fatal_error("tcsetpgrp: "); |
|
if (pg_tty_state) |
esh_sys_tty_restore(pg_tty_state); |
esh_signal_unblock(SIGTTOU); |
} |
|
/* Handler for SIGCHLD */ |
static void sigchld_handler(int sig, siginfo_t *info, void *_ctxt) { |
pid_t child; |
int status; |
|
assert(sig == SIGCHLD); |
|
while ((child = waitpid(-1, &status, WUNTRACED|WNOHANG|WCONTINUED)) > 0) { |
// printf("Received signal %d from process %d\n",sig, child); |
struct esh_command *cmd = shell.get_cmd_from_pid(child); |
// Check the status of the child and update status accordingly |
if (WIFEXITED(status)) { |
cmd->pipeline->status = COMPLETED; |
} else if (WIFSIGNALED(status)) { |
cmd->pipeline->status = TERMINATED; |
} else if (WIFCONTINUED(status)) { |
cmd->pipeline->status = BACKGROUND; |
} else if (WIFSTOPPED(status)) { |
cmd->pipeline->status = STOPPED; |
} |
} |
} |