Blame | Last modification | View Log | Download | RSS feed
/** 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 declarationsstatic 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 shellstatic 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) terminalsys_tty = esh_sys_tty_init();// Store the process group of the shellshell_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 promptinggive_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 commandswhile (!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 pipelinepipeline->jid = 0;pipeline->pgrp = 0;memset(pipeline->cmd_string, 0, 256);// Initialize array of pipesint 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 commandsstruct 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 commandc = 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 commandif (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 commandfor (; 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 runif ((cmd->pid = fork()) == 0) {// Set the group process ID to the same value for each pipelinesetpgid(0,pipeline->pgrp);esh_signal_unblock(SIGCHLD);if (p_pipe_max) {// Set up the pipelines if there are more than one commandif (p_pipe_ind == 0) {// First commandif (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 specifiedif (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 commandif (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 specifiedif (pipeline->iored_output) {// Check if we are creating a new file or appending to an existing fileif (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 betweenif (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 pipesfor (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 specifiedif (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 specifiedif (pipeline->iored_output) {// Check if we are creating a new file or appending to an existing fileif (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 groupif (setpgid(0,0) == -1)esh_sys_error("setpgid: ");// Store the process group IDif (pipeline->pgrp == 0)pipeline->pgrp = cmd->pid;// Increment the pipe counterif (p_pipe_max) {p_pipe_ind++;}}// Close all pipesfor (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 terminalpipeline->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 onpipeline->status = BACKGROUND;// Assign the pipeline a job IDpipeline->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 ");elseprintf("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 emptyif (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 executingwhile (pipeline->status == FOREGROUND && !list_empty(&pipeline->commands)) {int status;// Wait till a child process exitspid_t child = waitpid(-1, &status, WUNTRACED);if (child != -1) {// If child has exited or terminated, remove the child from the pipelineif (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 statusif (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 terminalgive_terminal_to(pipe->pgrp, NULL);// Resume the processsend_signal_to_job(jid, SIGCONT);// Wait for process to finishwait_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 accordinglyif (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;}}}