/*
* cush - the customizable shell.
*
* Developed by Godmar Back for CS 3214 Summer 2020
* Virginia Tech. Augmented to use posix_spawn in Fall 2021.
*/
#define _GNU_SOURCE 1
#define PROMPT_LENGTH 100
#define W 1
#define R 0
#include <stdio.h>
#include <readline/readline.h>
#include <readline/history.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <sys/wait.h>
#include <assert.h>
#include <stdbool.h>
#include <fcntl.h>
/* Since the handed out code contains a number of unused functions. */
#pragma GCC diagnostic ignored "-Wunused-function"
#include "termstate_management.h"
#include "signal_support.h"
#include "shell-ast.h"
#include "utils.h"
enum job_status
{
FOREGROUND, /* job is running in foreground. Only one job can be
in the foreground state. */
BACKGROUND, /* job is running in background */
STOPPED, /* job is stopped via SIGSTOP */
NEEDSTERMINAL, /* job is stopped because it was a background job
and requires exclusive terminal access */
DONE /* jobs is completed and ready to be removed from the job_list*/
};
enum valid_commands
{
JOBS, /* displays a list of current jobs */
FG, /* brings job to the foreground */
BG, /* throws job to the background */
KILL, /* sends KILL signal to a specified job */
STOP, /* stops the specified job */
EXIT, /* exits the cush shell */
CD, /* switches to specified directory */
HISTORY, /* displays the command history list */
EXCLAMATION, /* helps run commands from command history */
INVALID
};
struct job
{
struct list_elem elem; /* Link element for jobs list. */
struct ast_pipeline *pipe; /* The pipeline of commands this job represents */
bool complete; /* Denotes whether all commands within the pipeline are
comepleted. */
int jid; /* Job id. */
enum job_status status; /* Job status. */
int num_processes_alive; /* The number of processes that we know to be alive */
struct termios *saved_tty_state; /* The state of the terminal when this job was
stopped after having been in foreground */
pid_t group; /* Group id */
pid_t *pid; /* holds all PID's for processes in a given job */
};
int pipe2(int pipefd[2], int flags);
static void handle_child_status(pid_t pid, int status);
enum valid_commands is_valid_command(struct list_elem *pipe);
static bool throw_to_execute(struct ast_command_line *cline, struct termios *shell_state);
static void execute_job(struct ast_pipeline *curr, struct ast_command *cmd, struct termios *shell_state);
static void execute_job_piped(struct ast_pipeline *curr, struct termios *tty_state);
static void sigstp_handler(int sig_num);
/* Utility functions for job list management.
* We use 2 data structures:
* (a) an array jid2job to quickly find a job based on its id
* (b) a linked list to support iteration
*/
#define MAXJOBS (1 << 16)
static struct list job_list;
static struct job *jid2job[MAXJOBS];
static int num_of_jobs; /* denotes the number of jobs currently in the job_list */
static struct job *most_rec; /* pointer to the most recent jobs if jid is not passed with certain commands */
static int comm_number = 0; /* tracks the command number of the current session */
static int fg_job_jid; /* the jid of the most recent fg job */
static void
usage(char *progname)
{
printf("Usage: %s -h\n"
" -h print this help\n",
progname);
exit(EXIT_SUCCESS);
}
/* Build a prompt */
static char *
build_prompt(void)
{
comm_number++;
char host[PROMPT_LENGTH / 2];
if (gethostname(host, PROMPT_LENGTH / 2) == -1)
perror("gethostname"), exit(EXIT_FAILURE);
char dir[PROMPT_LENGTH / 2], *dir_pt = dir;
char *cwd = get_current_dir_name();
dir_pt = basename(cwd);
char user[PROMPT_LENGTH / 2], *user_pt = user;
user_pt = getlogin();
char prompt[PROMPT_LENGTH * 2];
snprintf(prompt, PROMPT_LENGTH, "[%d] %s@%s in %s > ", comm_number, user_pt, host, dir_pt);
free(cwd);
return strdup(prompt);
}
/* Return job corresponding to jid */
static struct job *
get_job_from_jid(int jid)
{
if (jid > 0 && jid < MAXJOBS && jid2job[jid] != NULL)
return jid2job[jid];
return NULL;
}
/* Add a new job to the job list */
static struct job *
add_job(struct ast_pipeline *pipe)
{
struct job *job = malloc(sizeof(*job));
job->pipe = pipe;
job->complete = false;
job->pid = calloc(list_size(&pipe->commands), sizeof(pid_t));
job->status = (job->pipe->bg_job) ? BACKGROUND : FOREGROUND;
list_push_back(&job_list, &job->elem);
for (int i = 1; i < MAXJOBS; i++)
{
if (jid2job[i] == NULL)
{
jid2job[i] = job;
job->jid = i;
most_rec = job;
num_of_jobs++;
return job;
}
}
fprintf(stderr, "Maximum number of jobs exceeded\n");
abort();
return NULL;
}
/* Delete a job.
* This should be called only when all processes that were
* forked for this job are known to have terminated.
*/
static void
delete_job(struct job *job)
{
int jid = job->jid;
assert(jid != -1);
jid2job[jid]->jid = -1;
jid2job[jid] = NULL;
ast_pipeline_free(job->pipe);
free(job);
num_of_jobs--;
}
/* Returns a string representing the status of a job */
static const char *
get_status(enum job_status status)
{
switch (status)
{
case FOREGROUND:
return "Foreground";
case BACKGROUND:
return "Running";
case STOPPED:
return "Stopped";
case NEEDSTERMINAL:
return "Stopped (tty)";
case DONE:
return "DONE";
default:
return "Unknown";
}
}
/* Print the command line that belongs to one job. */
static void
print_cmdline(struct ast_pipeline *pipeline)
{
struct list_elem *e = list_begin(&pipeline->commands);
for (; e != list_end(&pipeline->commands); e = list_next(e))
{
struct ast_command *cmd = list_entry(e, struct ast_command, elem);
if (e != list_begin(&pipeline->commands))
printf("| ");
char **p = cmd->argv;
printf("%s", *p++);
while (*p)
printf(" %s", *p++);
}
}
/* Print a job */
static void
print_job(struct job *job)
{
printf("[%d]\t%s\t\t(", job->jid, get_status(job->status));
for (struct list_elem *cmdLink = list_begin(&job->pipe->commands);
cmdLink != list_end(&job->pipe->commands); cmdLink = list_next(cmdLink))
{
struct ast_command *cmd = list_entry(cmdLink, struct ast_command, elem);
int i = 0;
while (cmd->argv[i])
{
printf("%s", cmd->argv[i]);
if ((cmd->argv[++i]))
printf(" ");
}
if (list_size(&job->pipe->commands) > 1 && cmdLink->next != list_end(&job->pipe->commands))
printf(" | ");
}
printf(")\n");
}
/* checks if all commands within the job pipeline are complete*/
static bool
is_job_complete(struct job *job)
{
return (list_empty(&job->pipe->commands));
}
/* Removes jobs from the job_list and prints completion status if job was ran in background*/
static void
remove_completed()
{
if (list_empty(&job_list))
return;
struct list_elem *job_node = list_begin(&job_list);
while (!list_empty(&job_list) && list_end(&job_list) != job_node)
{
struct job *curr_job = list_entry(job_node, struct job, elem);
if (is_job_complete(curr_job))
{
curr_job->status = DONE;
if (curr_job->pipe->bg_job)
printf("[%d]\t%s\n", curr_job->jid, get_status(curr_job->status));
job_node = job_node->prev;
list_remove(job_node->next);
delete_job(curr_job);
}
job_node = list_next(job_node);
}
}
/*
* Wait for all processes in this job to complete, or for
* the job no longer to be in the foreground.
* You should call this function from a) where you wait for
* jobs started without the &; and b) where you implement the
* 'fg' command.
*
* Implement handle_child_status such that it records the
* information obtained from waitpid() for pid 'child.'
*
* If a process exited, it must find the job to which it
* belongs and decrement num_processes_alive.
*
* However, note that it is not safe to call delete_job
* in handle_child_status because wait_for_job assumes that
* even jobs with no more num_processes_alive haven't been
* deallocated. You should postpone deleting completed
* jobs from the job list until when your code will no
* longer touch them.
*
* The code below relies on `job->status` having been set to FOREGROUND
* and `job->num_processes_alive` having been set to the number of
* processes successfully forked for this job.
*/
static void
wait_for_job(struct job *job)
{
assert(signal_is_blocked(SIGCHLD));
int status;
pid_t child = waitpid(-1, &status, WUNTRACED);
// When called here, any error returned by waitpid indicates a logic
// bug in the shell.
// In particular, ECHILD "No child process" means that there has
// already been a successful waitpid() call that reaped the child, so
// there's likely a bug in handle_child_status where it failed to update
// the "job" status and/or num_processes_alive fields in the required
// fashion.
// Since SIGCHLD is blocked, there cannot be races where a child's exit
// was handled via the SIGCHLD signal handler.
if (child != -1)
handle_child_status(child, status);
else
utils_fatal_error("waitpid failed, see code for explanation");
}
/*
* Suggested SIGCHLD handler.
*
* Call waitpid() to learn about any child processes that
* have exited or changed status (been stopped, needed the
* terminal, etc.)
* Just record the information by updating the job list
* data structures. Since the call may be spurious (e.g.
* an already pending SIGCHLD is delivered even though
* a foreground process was already reaped), ignore when
* waitpid returns -1.
* Use a loop with WNOHANG since only a single SIGCHLD
* signal may be delivered for multiple children that have
* exited. All of them need to be reaped.
*/
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)) > 0)
handle_child_status(child, status);
}
/* Signal handler for all child processes */
static void
handle_child_status(pid_t pid, int status)
{
assert(signal_is_blocked(SIGCHLD));
for (struct list_elem *job_node = list_begin(&job_list);
job_node != list_end(&job_list); job_node = list_next(job_node))
{
struct job *job = list_entry(job_node, struct job, elem);
for (struct list_elem *command = list_begin(&job->pipe->commands);
command != list_end(&job->pipe->commands); command = list_next(command))
{
struct ast_command *cmd = list_entry(command, struct ast_command, elem);
if (pid == cmd->pid)
{
if (WIFEXITED(status))
list_remove(command);
else if (WSTOPSIG(status) == SIGTSTP) // Ctrl Z
{
cmd->stopped = true;
job->status = STOPPED;
job->pipe->bg_job = true;
print_job(job);
if (killpg(job->group, SIGTSTP) != 0)
perror("killpg");
fg_job_jid = -1;
termstate_give_terminal_back_to_shell();
}
else if (WTERMSIG(status) == SIGINT) // Ctrl C
{
job->complete = true;
job->status = DONE;
if (killpg(job->group, SIGINT) != 0)
perror("killpg");
printf("%d\n", WIFSIGNALED(status));
fg_job_jid = -1;
termstate_give_terminal_back_to_shell();
}
else if (WTERMSIG(status) == SIGKILL) // kill()
{
job->complete = true;
job->status = DONE;
if (killpg(job->group, SIGKILL) != 0)
perror("killpg");
printf("killed\n");
termstate_give_terminal_back_to_shell();
}
else if (WTERMSIG(status) == SIGABRT) // abort()
{
job->complete = true;
job->status = DONE;
if (killpg(job->group, SIGABRT) != 0)
perror("killpg");
printf("aborted\n");
termstate_give_terminal_back_to_shell();
}
else if (WTERMSIG(status) == SIGSEGV) // seg fault
{
job->complete = true;
job->status = DONE;
if (killpg(job->group, SIGSEGV) != 0)
perror("killpg");
printf("segmentation fault\n");
termstate_give_terminal_back_to_shell();
}
else if (WTERMSIG(status) == SIGFPE) // FPE
{
job->complete = true;
job->status = DONE;
if (killpg(job->group, SIGFPE) != 0)
perror("killpg");
printf("floating point exception\n");
termstate_give_terminal_back_to_shell();
}
else if (WTERMSIG(status) == SIGSYS) // killed
{
job->complete = true;
job->status = DONE;
if (killpg(job->group, SIGSYS) != 0)
perror("killpg");
printf("killed\n");
termstate_give_terminal_back_to_shell();
}
else if (WTERMSIG(status) == SIGTERM) // terminated
{
job->complete = true;
job->status = DONE;
if (killpg(job->group, SIGTERM) != 0)
perror("killpg");
printf("terminated\n");
termstate_give_terminal_back_to_shell();
}
else if (WSTOPSIG(status) == SIGTTOU || WSTOPSIG(status) == SIGTTIN) // needs terminal
{
job->complete = false;
job->status = NEEDSTERMINAL;
job->pipe->bg_job = true;
if (killpg(job->group, SIGTTOU) != 0)
perror("killpg");
termstate_give_terminal_back_to_shell();
}
}
}
}
}
/* Helper to convert list_elem within a pipe ot an ast_command */
static struct ast_command *
convert_to_ast_command(struct list_elem *pipe)
{
struct ast_pipeline *pipe_list = list_entry(pipe, struct ast_pipeline, elem);
struct list_elem *cmd = list_begin(&pipe_list->commands);
struct ast_command *command = list_entry(cmd, struct ast_command, elem);
return command;
}
/* Starts a specified job in the background */
static void
push_to_bg(struct job *curr_job)
{
curr_job->status = BACKGROUND;
curr_job->pipe->bg_job = true;
fg_job_jid = -1;
printf("[%d] %d\n", curr_job->jid, curr_job->group);
if (killpg(curr_job->group, SIGCONT) != 0)
perror("killpg"), exit(EXIT_FAILURE);
}
/* Starts a specified job in the forground */
static void
push_to_fg(struct job *curr_job, struct termios *tty_state)
{
print_cmdline(curr_job->pipe);
printf("\n");
fg_job_jid = curr_job->jid;
if (killpg(curr_job->group, SIGCONT) != 0)
perror("killpg"), exit(EXIT_FAILURE);
curr_job->status = FOREGROUND;
if (curr_job->pipe->bg_job)
{
termstate_give_terminal_to(NULL, curr_job->group);
curr_job->saved_tty_state = get_save();
curr_job->pipe->bg_job = false;
}
else
termstate_restore(curr_job->saved_tty_state);
signal_block(SIGCHLD);
wait_for_job(curr_job);
signal_unblock(SIGCHLD);
termstate_give_terminal_back_to_shell();
}
// Signal Handler for SIGTSTP (Ctr Z)
static void
sigstp_handler(int sig_num)
{
if (fg_job_jid == -1)
exit(EXIT_SUCCESS);
}
// Signal Handler for SIGINT (Ctr C)
static void
sigint_handler(int sig_num)
{
if (fg_job_jid == -1)
return;
}
/* checks if argument is a valid, built-in command */
enum valid_commands
is_valid_command(struct list_elem *pipe)
{
struct ast_command *entry = convert_to_ast_command(pipe);
char *command = entry->argv[0];
if (!strcmp(command, "jobs")) /* only needs command */
return JOBS;
if (!strcmp(command, "fg")) /* fg JobID or fg JobID, ... */
return FG;
if (!strcmp(command, "bg")) /* bg JobID or bg JobID, ... */
return BG;
if (!strcmp(command, "exit")) /* exit [N] if just exit use last command executed status. */
return EXIT;
if (!strcmp(command, "kill")) /* kill JobID */
return KILL;
if (!strcmp(command, "stop"))
return STOP;
if (!strcmp(command, "cd"))
return CD;
if (!strcmp(command, "history"))
return HISTORY;
if (strchr(command, '!'))
return EXCLAMATION;
return INVALID;
}
/* Executes all builtin commands */
static bool
execute_command(enum valid_commands command,
char **command_args,
struct list_elem *pipe,
struct termios *tty_state,
int jid)
{
struct job *job = get_job_from_jid(jid);
switch (command)
{
case EXIT:
return true;
case JOBS:
for (struct list_elem *job_node = list_begin(&job_list);
job_node != list_end(&job_list); job_node = list_next(job_node))
{
struct job *curr_job = list_entry(job_node, struct job, elem);
if (!is_job_complete(curr_job))
print_job(curr_job);
}
break;
case FG:
if (job->status == FOREGROUND)
printf("%d already in forground.\n", jid);
else
push_to_fg(job, tty_state);
break;
case BG:
switch (job->status)
{
case FOREGROUND:
push_to_bg(job);
break;
case BACKGROUND:
printf("%d already in background.\n", jid);
break;
case STOPPED:
push_to_bg(job);
break;
case NEEDSTERMINAL:
printf("%d must be in foreground.\n", jid);
break;
case DONE:
break;
}
break;
case STOP:
if (killpg(job->group, SIGTSTP) != 0)
perror("killpg"), exit(EXIT_FAILURE);
job->status = STOPPED;
signal_block(SIGCHLD);
wait_for_job(job);
signal_unblock(SIGCHLD);
break;
case KILL:
if (!is_job_complete(job))
{
if (killpg(job->group, SIGKILL) != 0)
perror("killpg"), exit(EXIT_FAILURE);
list_remove(&job->elem);
}
break;
case CD:
(command_args[1] != NULL) ? chdir(command_args[1]) : chdir(getenv("HOME"));
break;
case HISTORY:;
register HIST_ENTRY **history;
if ((history = history_list()))
{
int range = (command_args[1] != NULL) ? atoi(command_args[1]) : history_length;
range = (!range || range > history_length) ? history_length : range;
int start = (history_length - range);
if (start >= 0 && command_args[1] != NULL)
for (register int i = start; i < history_length; i++)
printf("%d: %s\n", i + 1, history[i]->line);
else
for (register int i = 0; history[i]; i++)
printf("%d: %s\n", i + history_base, history[i]->line);
}
break;
case EXCLAMATION:;
char *arg = command_args[0] + 1;
register HIST_ENTRY *cmd;
int n, start_offset = where_history();
if (!strcmp(arg, "!"))
{
cmd = previous_history();
history_set_pos(start_offset);
}
else if ((n = atoi(arg)) != 0)
cmd = history_get(n);
else
{
while ((cmd = previous_history()))
if ((strstr(cmd->line, arg) != NULL))
break;
history_set_pos(start_offset);
}
if (cmd)
{
struct ast_command_line *line = ast_parse_command_line(cmd->line);
throw_to_execute(line, tty_state);
}
break;
case INVALID:
break;
default:
return false;
}
return false;
}
/*
* Forks a new process and calls launch command to start a job.
* Sets the process group id and pid for the child process in our jobs list
*/
static void execute_job(struct ast_pipeline *curr, struct ast_command *cmd, struct termios *shell_state)
{
struct job *new_job = add_job(curr);
new_job->pid = calloc(1, sizeof(pid_t));
fg_job_jid = (new_job->pipe->bg_job) ? -1 : new_job->jid;
cmd->pid = fork();
// in child
if (cmd->pid == 0)
{
cmd->pid = getpid();
new_job->pid[0] = cmd->pid;
new_job->group = (new_job->group == 0) ? getpid() : new_job->group;
if (curr->iored_input != NULL)
{
FILE *input = fopen(curr->iored_input, "r");
if (dup2(fileno(input), STDIN_FILENO) < 0)
perror("dup2"), exit(EXIT_FAILURE);
curr->iored_input = NULL;
close(fileno(input));
}
if (curr->iored_output)
{
char *mode = (curr->append_to_output || cmd->dup_stderr_to_stdout) ? "a" : "w";
FILE *output = fopen(curr->iored_output, mode);
dup2(fileno(output), STDOUT_FILENO);
if (cmd->dup_stderr_to_stdout)
if (dup2(STDOUT_FILENO, STDERR_FILENO) < 0)
perror("dup2"), exit(EXIT_FAILURE);
close(fileno(output));
}
if (execvp(cmd->argv[0], cmd->argv) < 0)
{
delete_job(new_job);
perror("execvp");
exit(EXIT_SUCCESS);
}
}
// in parent
else
{
new_job->group = cmd->pid;
if (setpgid(cmd->pid, new_job->group) < 0)
perror("setpgid"), exit(EXIT_FAILURE);
new_job->status = (new_job->pipe->bg_job) ? BACKGROUND : FOREGROUND;
// foreground child
if (!new_job->pipe->bg_job)
{
termstate_give_terminal_to(NULL, cmd->pid);
new_job->saved_tty_state = get_save();
signal_block(SIGCHLD);
wait_for_job(new_job);
signal_unblock(SIGCHLD);
}
// background child
else
printf("[%d] %d\n", new_job->jid, new_job->group);
termstate_give_terminal_back_to_shell();
}
}
/*
* Forks a new process and calls launch command to start a job.
* Sets the process group id and pids for the child processes in our jobs list
* This accounts for piped commands and redirected io
*/
static void
execute_job_piped(struct ast_pipeline *curr, struct termios *tty_state)
{
struct job *new_job = add_job(curr);
new_job->pid = calloc(list_size(&curr->commands), sizeof(pid_t));
bool append_output = false;
int num_pipes = list_size(&curr->commands) - 1;
int pipe_arr[num_pipes][2];
for (int i = 0; i < num_pipes; i++)
{
if (pipe2(pipe_arr[i], O_CLOEXEC) == -1)
perror("pipe2"), exit(EXIT_FAILURE);
}
int job_cmd_num = 0;
struct list_elem *command = list_begin(&curr->commands);
while (command != list_end(&curr->commands))
{
struct ast_command *cmd = list_entry(command, struct ast_command, elem);
// in child
cmd->pid = fork();
if (cmd->pid == 0)
{
if (job_cmd_num != 0) // not first
{
if (dup2(pipe_arr[job_cmd_num - 1][R], R) < 0)
perror("dup2"), exit(EXIT_FAILURE);
}
if (job_cmd_num != num_pipes) // not last
{
if (dup2(pipe_arr[job_cmd_num][W], W) < 0)
perror("dup2"), exit(EXIT_FAILURE);
}
// iored input only on first command
if (curr->iored_input && job_cmd_num == 0)
{
FILE *input = fopen(curr->iored_input, "r");
if (dup2(fileno(input), STDIN_FILENO) < 0)
perror("dup2"), exit(EXIT_FAILURE);
}
// iored
if (curr->iored_output && job_cmd_num == num_pipes)
{
char *mode = (curr->append_to_output || cmd->dup_stderr_to_stdout) ? "a" : "w";
append_output = cmd->dup_stderr_to_stdout ? true : append_output;
FILE *output = fopen(curr->iored_output, mode);
if (dup2(fileno(output), STDOUT_FILENO) < 0)
perror("dup2"), exit(EXIT_FAILURE);
if (cmd->dup_stderr_to_stdout)
{
if (dup2(STDOUT_FILENO, STDERR_FILENO) < 0)
perror("dup2"), exit(EXIT_FAILURE);
}
}
new_job->pid[job_cmd_num] = getpid();
if (job_cmd_num != 0)
{
if (setpgid(new_job->pid[job_cmd_num], new_job->group) < 0)
perror("setpgid"), exit(EXIT_FAILURE);
}
if (job_cmd_num == 0)
{
if (setpgid(cmd->pid, 0) < 0)
perror("setpgid"), exit(EXIT_FAILURE);
new_job->group = cmd->pid;
}
if (execvp(cmd->argv[0], cmd->argv) < 0)
perror("execvp"), exit(EXIT_SUCCESS);
}
if (job_cmd_num == 0)
{
if (setpgid(cmd->pid, 0) < 0)
perror("setpgid"), exit(EXIT_FAILURE);
new_job->group = cmd->pid;
}
job_cmd_num++;
command = list_next(command);
}
// in parent
for (int i = 0; i < num_pipes; i++)
{
close(pipe_arr[i][R]);
close(pipe_arr[i][W]);
}
// foreground child
if (!new_job->pipe->bg_job)
{
fg_job_jid = new_job->jid;
termstate_give_terminal_to(NULL, new_job->group);
new_job->saved_tty_state = get_save();
signal_block(SIGCHLD);
wait_for_job(new_job);
signal_unblock(SIGCHLD);
}
// background child
else
printf("[%d] %d\n", new_job->jid, new_job->group);
termstate_give_terminal_back_to_shell();
}
/* Decides whether user input is a job or builtin command and sends said input to its correct handler */
static bool
throw_to_execute(struct ast_command_line *cline, struct termios *shell_state)
{
struct list_elem *pipe_elem = list_begin(&cline->pipes);
int command;
if ((command = is_valid_command(pipe_elem)) != INVALID)
{
struct ast_command *cmd = convert_to_ast_command(pipe_elem);
int most_recent_jid = (most_rec == NULL) ? 0 : most_rec->jid;
int jid = (cmd->argv[1] != NULL) ? atoi(cmd->argv[1]) : most_recent_jid;
return execute_command(command, cmd->argv, pipe_elem, shell_state, jid);
}
else
{
struct ast_pipeline *job_pipe = list_entry(pipe_elem, struct ast_pipeline, elem);
struct ast_command *cmd = convert_to_ast_command(pipe_elem);
list_remove(pipe_elem);
bool piped = list_size(&job_pipe->commands) > 1;
if (piped)
execute_job_piped(job_pipe, shell_state);
else
execute_job(job_pipe, cmd, shell_state);
}
return false;
}
/*main method*/
int main(int ac, char *av[])
{
signal(SIGTTOU, SIG_IGN);
signal(SIGTSTP, sigstp_handler);
signal(SIGINT, sigint_handler);
signal_set_handler(SIGCHLD, sigchld_handler);
termstate_init();
struct termios *shell_state = get_save();
/* Process command-line arguments. See getopt(3) */
int opt;
while ((opt = getopt(ac, av, "h")) > 0)
{
switch (opt)
{
case 'h':
usage(av[0]);
break;
}
}
list_init(&job_list);
num_of_jobs = 0;
using_history();
/* Read/eval loop. */
for (;;)
{
remove_completed();
fg_job_jid = -1;
/* Do not output a prompt unless shell's stdin is a terminal */
char *prompt = isatty(0) ? build_prompt() : NULL;
char *cmdline = readline(prompt);
free(prompt);
if (cmdline == NULL) /* User typed EOF */
break;
struct ast_command_line *cline = ast_parse_command_line(cmdline);
if (cline == NULL) /* Error in command line */
{
free(cmdline);
continue;
}
if (list_empty(&cline->pipes)) /* User hit enter */
{
ast_command_line_free(cline);
free(cmdline);
continue;
}
/* Adds to command history */
char *expansion;
int result = history_expand(cmdline, &expansion);
if (result)
fprintf(stderr, "%s\n", expansion);
if (result < 0 || result == 2)
free(expansion);
else
add_history(expansion);
/* Executes the job or command */
if (throw_to_execute(cline, shell_state))
{
ast_command_line_free(cline);
free(cmdline);
rl_clear_history();
clear_history();
exit(EXIT_SUCCESS);
}
if (!expansion)
free(expansion);
ast_command_line_free(cline);
free(cmdline);
}
rl_clear_history();
return 0;
}