/* * 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; }